@omote/core 0.5.3 → 0.5.4

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/index.mjs CHANGED
@@ -2778,13 +2778,6 @@ function pcm16ToFloat32(buffer) {
2778
2778
  }
2779
2779
  return float32;
2780
2780
  }
2781
- function int16ToFloat32(int16) {
2782
- const float32 = new Float32Array(int16.length);
2783
- for (let i = 0; i < int16.length; i++) {
2784
- float32[i] = int16[i] / 32768;
2785
- }
2786
- return float32;
2787
- }
2788
2781
 
2789
2782
  // src/audio/FullFacePipeline.ts
2790
2783
  var logger4 = createLogger("FullFacePipeline");
@@ -3072,6 +3065,108 @@ var FullFacePipeline = class extends EventEmitter {
3072
3065
  }
3073
3066
  };
3074
3067
 
3068
+ // src/audio/InterruptionHandler.ts
3069
+ var InterruptionHandler = class extends EventEmitter {
3070
+ constructor(config = {}) {
3071
+ super();
3072
+ this.isSpeaking = false;
3073
+ this.speechStartTime = 0;
3074
+ this.lastSpeechTime = 0;
3075
+ this.silenceTimer = null;
3076
+ this.aiIsSpeaking = false;
3077
+ // Debouncing: only emit one interruption per speech session
3078
+ this.interruptionTriggeredThisSession = false;
3079
+ this.config = {
3080
+ vadThreshold: 0.5,
3081
+ // Silero VAD default
3082
+ minSpeechDurationMs: 200,
3083
+ // Google/Amazon barge-in standard
3084
+ silenceTimeoutMs: 500,
3085
+ // OpenAI Realtime API standard
3086
+ enabled: true,
3087
+ ...config
3088
+ };
3089
+ }
3090
+ /**
3091
+ * Process VAD result for interruption detection
3092
+ * @param vadProbability - Speech probability from VAD (0-1)
3093
+ * @param audioEnergy - Optional RMS energy for logging (default: 0)
3094
+ */
3095
+ processVADResult(vadProbability, audioEnergy = 0) {
3096
+ if (!this.config.enabled) return;
3097
+ if (vadProbability > this.config.vadThreshold) {
3098
+ this.onSpeechDetected(audioEnergy || vadProbability);
3099
+ } else {
3100
+ this.onSilenceDetected();
3101
+ }
3102
+ }
3103
+ /** Notify that AI started/stopped speaking */
3104
+ setAISpeaking(speaking) {
3105
+ this.aiIsSpeaking = speaking;
3106
+ }
3107
+ /** Enable/disable interruption detection */
3108
+ setEnabled(enabled) {
3109
+ this.config.enabled = enabled;
3110
+ if (!enabled) {
3111
+ this.reset();
3112
+ }
3113
+ }
3114
+ /** Update configuration */
3115
+ updateConfig(config) {
3116
+ this.config = { ...this.config, ...config };
3117
+ }
3118
+ /** Reset state */
3119
+ reset() {
3120
+ this.isSpeaking = false;
3121
+ this.speechStartTime = 0;
3122
+ this.lastSpeechTime = 0;
3123
+ this.interruptionTriggeredThisSession = false;
3124
+ if (this.silenceTimer) {
3125
+ clearTimeout(this.silenceTimer);
3126
+ this.silenceTimer = null;
3127
+ }
3128
+ }
3129
+ /** Get current state */
3130
+ getState() {
3131
+ return {
3132
+ isSpeaking: this.isSpeaking,
3133
+ speechDurationMs: this.isSpeaking ? Date.now() - this.speechStartTime : 0
3134
+ };
3135
+ }
3136
+ onSpeechDetected(rms) {
3137
+ const now = Date.now();
3138
+ this.lastSpeechTime = now;
3139
+ if (this.silenceTimer) {
3140
+ clearTimeout(this.silenceTimer);
3141
+ this.silenceTimer = null;
3142
+ }
3143
+ if (!this.isSpeaking) {
3144
+ this.isSpeaking = true;
3145
+ this.speechStartTime = now;
3146
+ this.emit("speech.detected", { rms });
3147
+ }
3148
+ if (this.aiIsSpeaking && !this.interruptionTriggeredThisSession) {
3149
+ const speechDuration = now - this.speechStartTime;
3150
+ if (speechDuration >= this.config.minSpeechDurationMs) {
3151
+ this.interruptionTriggeredThisSession = true;
3152
+ this.emit("interruption.triggered", { rms, durationMs: speechDuration });
3153
+ }
3154
+ }
3155
+ }
3156
+ onSilenceDetected() {
3157
+ if (!this.isSpeaking) return;
3158
+ if (!this.silenceTimer) {
3159
+ this.silenceTimer = setTimeout(() => {
3160
+ const durationMs = this.lastSpeechTime - this.speechStartTime;
3161
+ this.isSpeaking = false;
3162
+ this.silenceTimer = null;
3163
+ this.interruptionTriggeredThisSession = false;
3164
+ this.emit("speech.ended", { durationMs });
3165
+ }, this.config.silenceTimeoutMs);
3166
+ }
3167
+ }
3168
+ };
3169
+
3075
3170
  // src/inference/kaldiFbank.ts
3076
3171
  function fft(re, im) {
3077
3172
  const n = re.length;
@@ -8778,1214 +8873,6 @@ var EmotionController = class {
8778
8873
  }
8779
8874
  };
8780
8875
 
8781
- // src/ai/adapters/AgentCoreAdapter.ts
8782
- var AgentCoreAdapter = class extends EventEmitter {
8783
- constructor(config) {
8784
- super();
8785
- this.name = "AgentCore";
8786
- this._state = "disconnected";
8787
- this._sessionId = null;
8788
- this._isConnected = false;
8789
- // Sub-components
8790
- this.asr = null;
8791
- this.vad = null;
8792
- this.lam = null;
8793
- this.pipeline = null;
8794
- // WebSocket connection to AgentCore
8795
- this.ws = null;
8796
- this.wsReconnectAttempts = 0;
8797
- this.maxReconnectAttempts = 5;
8798
- // Audio buffers
8799
- this.audioBuffer = [];
8800
- // Conversation state
8801
- this.history = [];
8802
- this.currentConfig = null;
8803
- // Interruption handling
8804
- this.isSpeaking = false;
8805
- this.currentTtsAbortController = null;
8806
- // Auth token cache per tenant
8807
- this.tokenCache = /* @__PURE__ */ new Map();
8808
- this.agentCoreConfig = config;
8809
- this.emotionController = new EmotionController();
8810
- }
8811
- get state() {
8812
- return this._state;
8813
- }
8814
- get sessionId() {
8815
- return this._sessionId;
8816
- }
8817
- get isConnected() {
8818
- return this._isConnected;
8819
- }
8820
- /**
8821
- * Connect to AgentCore with session configuration
8822
- */
8823
- async connect(config) {
8824
- this.currentConfig = config;
8825
- this._sessionId = config.sessionId;
8826
- try {
8827
- const authToken = await this.getAuthToken(config.tenant);
8828
- await Promise.all([
8829
- this.initASR(),
8830
- this.initLAM()
8831
- ]);
8832
- await this.connectWebSocket(authToken, config);
8833
- this._isConnected = true;
8834
- this.setState("idle");
8835
- this.emit("connection.opened", { sessionId: this._sessionId, adapter: this.name });
8836
- } catch (error) {
8837
- this.setState("error");
8838
- this.emit("connection.error", {
8839
- error,
8840
- recoverable: true
8841
- });
8842
- throw error;
8843
- }
8844
- }
8845
- /**
8846
- * Disconnect and cleanup
8847
- */
8848
- async disconnect() {
8849
- this.currentTtsAbortController?.abort();
8850
- if (this.pipeline) {
8851
- this.pipeline.dispose();
8852
- this.pipeline = null;
8853
- }
8854
- if (this.ws) {
8855
- this.ws.close(1e3, "Client disconnect");
8856
- this.ws = null;
8857
- }
8858
- await Promise.all([
8859
- this.asr?.dispose(),
8860
- this.vad?.dispose(),
8861
- this.lam?.dispose()
8862
- ]);
8863
- this._isConnected = false;
8864
- this.setState("disconnected");
8865
- this.emit("connection.closed", { reason: "Client disconnect" });
8866
- }
8867
- /**
8868
- * Push user audio for processing
8869
- */
8870
- pushAudio(audio) {
8871
- if (!this._isConnected) return;
8872
- if (this.isSpeaking) {
8873
- this.detectVoiceActivity(audio).then((hasVoiceActivity) => {
8874
- if (hasVoiceActivity) {
8875
- this.interrupt();
8876
- }
8877
- }).catch((error) => {
8878
- console.error("[AgentCore] VAD error during interruption detection:", error);
8879
- });
8880
- }
8881
- const float32 = audio instanceof Float32Array ? audio : int16ToFloat32(audio);
8882
- this.audioBuffer.push(float32);
8883
- this.scheduleTranscription();
8884
- }
8885
- /**
8886
- * Send text directly to AgentCore
8887
- */
8888
- async sendText(text) {
8889
- if (!this._isConnected || !this.ws) {
8890
- throw new Error("Not connected to AgentCore");
8891
- }
8892
- this.addToHistory({
8893
- role: "user",
8894
- content: text,
8895
- timestamp: Date.now()
8896
- });
8897
- this.setState("thinking");
8898
- this.emit("ai.thinking.start", { timestamp: Date.now() });
8899
- this.ws.send(JSON.stringify({
8900
- type: "user_message",
8901
- sessionId: this._sessionId,
8902
- content: text,
8903
- context: {
8904
- history: this.history.slice(-10),
8905
- // Last 10 messages
8906
- emotion: Array.from(this.emotionController.emotion)
8907
- }
8908
- }));
8909
- }
8910
- /**
8911
- * Interrupt current AI response
8912
- */
8913
- interrupt() {
8914
- if (!this.isSpeaking) return;
8915
- this.emit("interruption.detected", { timestamp: Date.now() });
8916
- this.currentTtsAbortController?.abort();
8917
- this.currentTtsAbortController = null;
8918
- if (this.ws?.readyState === WebSocket.OPEN) {
8919
- this.ws.send(JSON.stringify({
8920
- type: "interrupt",
8921
- sessionId: this._sessionId,
8922
- timestamp: Date.now()
8923
- }));
8924
- }
8925
- this.isSpeaking = false;
8926
- this.setState("listening");
8927
- this.emit("interruption.handled", { timestamp: Date.now(), action: "stop" });
8928
- }
8929
- getHistory() {
8930
- return [...this.history];
8931
- }
8932
- clearHistory() {
8933
- this.history = [];
8934
- this.emit("memory.updated", { messageCount: 0 });
8935
- }
8936
- async healthCheck() {
8937
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
8938
- return false;
8939
- }
8940
- return new Promise((resolve) => {
8941
- const timeout = setTimeout(() => resolve(false), 5e3);
8942
- const handler = (event) => {
8943
- try {
8944
- const data = JSON.parse(event.data);
8945
- if (data.type === "pong") {
8946
- clearTimeout(timeout);
8947
- this.ws?.removeEventListener("message", handler);
8948
- resolve(true);
8949
- }
8950
- } catch {
8951
- }
8952
- };
8953
- this.ws?.addEventListener("message", handler);
8954
- this.ws?.send(JSON.stringify({ type: "ping" }));
8955
- });
8956
- }
8957
- // ==================== Private Methods ====================
8958
- setState(state) {
8959
- const previousState = this._state;
8960
- this._state = state;
8961
- this.emit("state.change", { state, previousState });
8962
- }
8963
- async getAuthToken(tenant) {
8964
- const cached = this.tokenCache.get(tenant.tenantId);
8965
- if (cached && cached.expiresAt > Date.now() + 6e4) {
8966
- return cached.token;
8967
- }
8968
- if (tenant.credentials.authToken) {
8969
- return tenant.credentials.authToken;
8970
- }
8971
- const endpoint = this.agentCoreConfig.endpoint;
8972
- if (endpoint.startsWith("ws://") || endpoint.includes("localhost")) {
8973
- return "local-dev-token";
8974
- }
8975
- const httpEndpoint = endpoint.replace("wss://", "https://").replace("ws://", "http://");
8976
- const response = await fetch(`${httpEndpoint}/auth/token`, {
8977
- method: "POST",
8978
- headers: { "Content-Type": "application/json" },
8979
- body: JSON.stringify({
8980
- tenantId: tenant.tenantId,
8981
- apiKey: tenant.credentials.apiKey
8982
- })
8983
- });
8984
- if (!response.ok) {
8985
- throw new Error(`Auth failed: ${response.statusText}`);
8986
- }
8987
- const { token, expiresIn } = await response.json();
8988
- this.tokenCache.set(tenant.tenantId, {
8989
- token,
8990
- expiresAt: Date.now() + expiresIn * 1e3
8991
- });
8992
- return token;
8993
- }
8994
- async initASR() {
8995
- await Promise.all([
8996
- // SenseVoice ASR
8997
- (async () => {
8998
- this.asr = new SenseVoiceInference({
8999
- modelUrl: "/models/sensevoice/model.int8.onnx",
9000
- language: "auto"
9001
- });
9002
- await this.asr.load();
9003
- })(),
9004
- // Silero VAD for accurate voice activity detection
9005
- (async () => {
9006
- this.vad = new SileroVADInference({
9007
- modelUrl: "/models/silero-vad.onnx",
9008
- backend: "webgpu",
9009
- sampleRate: 16e3,
9010
- threshold: 0.5
9011
- });
9012
- await this.vad.load();
9013
- })()
9014
- ]);
9015
- }
9016
- async initLAM() {
9017
- const lamUrl = this.agentCoreConfig.models?.lamUrl || "/models/unified_wav2vec2_asr_a2e.onnx";
9018
- this.lam = new Wav2Vec2Inference({
9019
- modelUrl: lamUrl,
9020
- backend: "auto"
9021
- });
9022
- await this.lam.load();
9023
- await this.initPipeline();
9024
- }
9025
- async initPipeline() {
9026
- if (!this.lam) {
9027
- throw new Error("LAM must be initialized before pipeline");
9028
- }
9029
- this.pipeline = new FullFacePipeline({
9030
- lam: this.lam,
9031
- sampleRate: 16e3,
9032
- chunkTargetMs: 200
9033
- });
9034
- await this.pipeline.initialize();
9035
- this.pipeline.on("full_frame_ready", (fullFrame) => {
9036
- const frame = fullFrame.blendshapes;
9037
- this.emit("animation", {
9038
- blendshapes: frame,
9039
- get: (name) => {
9040
- const idx = LAM_BLENDSHAPES.indexOf(name);
9041
- return idx >= 0 ? frame[idx] : 0;
9042
- },
9043
- timestamp: Date.now(),
9044
- // Wall clock for client-side logging only
9045
- inferenceMs: 0
9046
- // Pipeline handles LAM inference asynchronously
9047
- });
9048
- });
9049
- this.pipeline.on("playback_complete", () => {
9050
- this.isSpeaking = false;
9051
- this.setState("idle");
9052
- this.emit("audio.output.end", { durationMs: 0 });
9053
- });
9054
- this.pipeline.on("error", (error) => {
9055
- console.error("[AgentCore] Pipeline error:", error);
9056
- this.emit("connection.error", {
9057
- error,
9058
- recoverable: true
9059
- });
9060
- });
9061
- }
9062
- async connectWebSocket(authToken, config) {
9063
- return new Promise((resolve, reject) => {
9064
- const wsUrl = new URL(`${this.agentCoreConfig.endpoint.replace("http", "ws")}/ws`);
9065
- wsUrl.searchParams.set("sessionId", config.sessionId);
9066
- wsUrl.searchParams.set("characterId", config.tenant.characterId);
9067
- this.ws = new WebSocket(wsUrl.toString());
9068
- this.ws.onopen = () => {
9069
- this.ws?.send(JSON.stringify({
9070
- type: "auth",
9071
- token: authToken,
9072
- tenantId: config.tenant.tenantId,
9073
- systemPrompt: config.systemPrompt
9074
- }));
9075
- };
9076
- this.ws.onmessage = (event) => {
9077
- try {
9078
- this.handleAgentCoreMessage(JSON.parse(event.data));
9079
- } catch {
9080
- }
9081
- };
9082
- this.ws.onerror = () => {
9083
- reject(new Error("WebSocket connection failed"));
9084
- };
9085
- this.ws.onclose = (event) => {
9086
- this.handleDisconnect(event);
9087
- };
9088
- const authTimeout = setTimeout(() => {
9089
- reject(new Error("Auth timeout"));
9090
- }, 1e4);
9091
- const authHandler = (event) => {
9092
- try {
9093
- const data = JSON.parse(event.data);
9094
- if (data.type === "auth_success") {
9095
- clearTimeout(authTimeout);
9096
- this.ws?.removeEventListener("message", authHandler);
9097
- resolve();
9098
- } else if (data.type === "auth_failed") {
9099
- clearTimeout(authTimeout);
9100
- reject(new Error(data.message));
9101
- }
9102
- } catch {
9103
- }
9104
- };
9105
- this.ws.addEventListener("message", authHandler);
9106
- });
9107
- }
9108
- handleAgentCoreMessage(data) {
9109
- switch (data.type) {
9110
- case "response_start":
9111
- this.setState("speaking");
9112
- this.isSpeaking = true;
9113
- this.emit("ai.response.start", {
9114
- text: data.text,
9115
- emotion: data.emotion
9116
- });
9117
- if (data.emotion) {
9118
- this.emotionController.transitionTo(
9119
- { [data.emotion]: 0.7 },
9120
- 300
9121
- );
9122
- }
9123
- if (this.pipeline) {
9124
- this.pipeline.start();
9125
- }
9126
- break;
9127
- case "response_chunk":
9128
- this.emit("ai.response.chunk", {
9129
- text: data.text,
9130
- isLast: data.isLast
9131
- });
9132
- break;
9133
- case "audio_chunk":
9134
- if (data.audio && this.pipeline) {
9135
- const audioData = this.base64ToArrayBuffer(data.audio);
9136
- const uint8 = new Uint8Array(audioData);
9137
- this.pipeline.onAudioChunk(uint8).catch((error) => {
9138
- console.error("[AgentCore] Pipeline chunk error:", error);
9139
- });
9140
- }
9141
- break;
9142
- case "audio_end":
9143
- if (this.pipeline) {
9144
- this.pipeline.end().catch((error) => {
9145
- console.error("[AgentCore] Pipeline end error:", error);
9146
- });
9147
- }
9148
- break;
9149
- case "response_end":
9150
- this.addToHistory({
9151
- role: "assistant",
9152
- content: data.fullText,
9153
- timestamp: Date.now(),
9154
- emotion: data.emotion
9155
- });
9156
- this.emit("ai.response.end", {
9157
- fullText: data.fullText,
9158
- durationMs: data.durationMs || 0
9159
- });
9160
- break;
9161
- case "memory_updated":
9162
- this.emit("memory.updated", {
9163
- messageCount: data.messageCount,
9164
- tokenCount: data.tokenCount
9165
- });
9166
- break;
9167
- case "error":
9168
- this.emit("connection.error", {
9169
- error: new Error(data.message),
9170
- recoverable: data.recoverable ?? false
9171
- });
9172
- break;
9173
- }
9174
- }
9175
- scheduleTranscription() {
9176
- if (this.audioBuffer.length === 0) return;
9177
- const totalLength = this.audioBuffer.reduce((sum2, buf) => sum2 + buf.length, 0);
9178
- if (totalLength < 4e3) return;
9179
- const audio = new Float32Array(totalLength);
9180
- let offset = 0;
9181
- for (const buf of this.audioBuffer) {
9182
- audio.set(buf, offset);
9183
- offset += buf.length;
9184
- }
9185
- this.audioBuffer = [];
9186
- let sum = 0;
9187
- for (let i = 0; i < audio.length; i++) {
9188
- sum += audio[i] * audio[i];
9189
- }
9190
- const rms = Math.sqrt(sum / audio.length);
9191
- if (rms < 0.01) {
9192
- console.debug("[AgentCore] Skipping silent audio", { rms, samples: audio.length });
9193
- return;
9194
- }
9195
- if (this.asr) {
9196
- this.setState("listening");
9197
- this.emit("user.speech.start", { timestamp: Date.now() });
9198
- this.asr.transcribe(audio).then((result) => {
9199
- this.emit("user.transcript.final", {
9200
- text: result.text,
9201
- confidence: 1
9202
- });
9203
- this.emit("user.speech.end", { timestamp: Date.now(), durationMs: result.inferenceTimeMs });
9204
- const cleanText = result.text.trim();
9205
- if (cleanText) {
9206
- this.sendText(cleanText).catch((error) => {
9207
- console.error("[AgentCore] Send text error:", error);
9208
- });
9209
- }
9210
- }).catch((error) => {
9211
- console.error("[AgentCore] Transcription error:", error);
9212
- });
9213
- }
9214
- }
9215
- // REMOVED: processAudioForAnimation() - now handled by FullFacePipeline
9216
- // The pipeline manages audio scheduling, LAM inference, and frame synchronization
9217
- // Frames are emitted via pipeline.on('full_frame_ready') event (see initPipeline())
9218
- /**
9219
- * Detect voice activity using Silero VAD
9220
- * Falls back to simple RMS if VAD not available
9221
- */
9222
- async detectVoiceActivity(audio) {
9223
- const float32 = audio instanceof Float32Array ? audio : int16ToFloat32(audio);
9224
- if (this.vad) {
9225
- const chunkSize = this.vad.getChunkSize();
9226
- for (let i = 0; i + chunkSize <= float32.length; i += chunkSize) {
9227
- const chunk = float32.slice(i, i + chunkSize);
9228
- const result = await this.vad.process(chunk);
9229
- if (result.isSpeech) {
9230
- return true;
9231
- }
9232
- }
9233
- return false;
9234
- }
9235
- let sum = 0;
9236
- for (let i = 0; i < float32.length; i++) {
9237
- sum += float32[i] * float32[i];
9238
- }
9239
- const rms = Math.sqrt(sum / float32.length);
9240
- return rms > 0.02;
9241
- }
9242
- base64ToArrayBuffer(base64) {
9243
- const binaryString = atob(base64);
9244
- const bytes = new Uint8Array(binaryString.length);
9245
- for (let i = 0; i < binaryString.length; i++) {
9246
- bytes[i] = binaryString.charCodeAt(i);
9247
- }
9248
- return bytes.buffer;
9249
- }
9250
- addToHistory(message) {
9251
- this.history.push(message);
9252
- this.emit("memory.updated", { messageCount: this.history.length });
9253
- }
9254
- handleDisconnect(event) {
9255
- this._isConnected = false;
9256
- if (event.code !== 1e3) {
9257
- if (this.wsReconnectAttempts < this.maxReconnectAttempts) {
9258
- this.wsReconnectAttempts++;
9259
- setTimeout(() => {
9260
- if (this.currentConfig) {
9261
- this.connect(this.currentConfig).catch(() => {
9262
- });
9263
- }
9264
- }, Math.pow(2, this.wsReconnectAttempts) * 1e3);
9265
- } else {
9266
- this.setState("error");
9267
- this.emit("connection.error", {
9268
- error: new Error("Max reconnection attempts reached"),
9269
- recoverable: false
9270
- });
9271
- }
9272
- }
9273
- this.emit("connection.closed", { reason: event.reason || "Connection closed" });
9274
- }
9275
- };
9276
-
9277
- // src/ai/orchestration/ConversationOrchestrator.ts
9278
- var ConversationSessionImpl = class {
9279
- constructor(config, adapter) {
9280
- this._history = [];
9281
- this._context = /* @__PURE__ */ new Map();
9282
- this.sessionId = config.sessionId;
9283
- this._config = config;
9284
- this._adapter = adapter;
9285
- this.createdAt = Date.now();
9286
- this._lastActivityAt = Date.now();
9287
- this._emotionController = new EmotionController();
9288
- if (config.emotion) {
9289
- this._emotionController.setPreset(config.emotion);
9290
- }
9291
- }
9292
- get adapter() {
9293
- return this._adapter;
9294
- }
9295
- get config() {
9296
- return this._config;
9297
- }
9298
- get state() {
9299
- return this._adapter.state;
9300
- }
9301
- get history() {
9302
- return [...this._history];
9303
- }
9304
- get emotion() {
9305
- return {};
9306
- }
9307
- get lastActivityAt() {
9308
- return this._lastActivityAt;
9309
- }
9310
- async start() {
9311
- await this._adapter.connect(this._config);
9312
- this._lastActivityAt = Date.now();
9313
- }
9314
- async end() {
9315
- await this._adapter.disconnect();
9316
- }
9317
- pushAudio(audio) {
9318
- this._adapter.pushAudio(audio);
9319
- this._lastActivityAt = Date.now();
9320
- }
9321
- async sendText(text) {
9322
- await this._adapter.sendText(text);
9323
- this._lastActivityAt = Date.now();
9324
- }
9325
- interrupt() {
9326
- this._adapter.interrupt();
9327
- this._lastActivityAt = Date.now();
9328
- }
9329
- setEmotion(emotion) {
9330
- this._emotionController.set(emotion);
9331
- }
9332
- addContext(key, value) {
9333
- this._context.set(key, value);
9334
- }
9335
- removeContext(key) {
9336
- this._context.delete(key);
9337
- }
9338
- getContext() {
9339
- return Object.fromEntries(this._context);
9340
- }
9341
- export() {
9342
- return {
9343
- sessionId: this.sessionId,
9344
- tenantId: this._config.tenant.tenantId,
9345
- characterId: this._config.tenant.characterId,
9346
- history: this._history,
9347
- context: Object.fromEntries(this._context),
9348
- emotion: this.emotion,
9349
- createdAt: this.createdAt,
9350
- lastActivityAt: this._lastActivityAt
9351
- };
9352
- }
9353
- import(snapshot) {
9354
- this._history = [...snapshot.history];
9355
- this._context = new Map(Object.entries(snapshot.context));
9356
- this._lastActivityAt = snapshot.lastActivityAt;
9357
- }
9358
- syncHistory() {
9359
- this._history = this._adapter.getHistory();
9360
- }
9361
- };
9362
- var ConversationOrchestrator = class extends EventEmitter {
9363
- constructor(config) {
9364
- super();
9365
- // Sessions per tenant
9366
- this.sessions = /* @__PURE__ */ new Map();
9367
- // Tenant configurations
9368
- this.tenants = /* @__PURE__ */ new Map();
9369
- // Health monitoring
9370
- this.healthCheckInterval = null;
9371
- this.HEALTH_CHECK_INTERVAL_MS = 3e4;
9372
- this.config = {
9373
- connectionTimeoutMs: 5e3,
9374
- maxRetries: 3,
9375
- ...config
9376
- };
9377
- this.adapter = new AgentCoreAdapter(config.adapter);
9378
- }
9379
- /**
9380
- * Register a tenant
9381
- */
9382
- registerTenant(tenant) {
9383
- this.tenants.set(tenant.tenantId, tenant);
9384
- }
9385
- /**
9386
- * Unregister a tenant
9387
- */
9388
- unregisterTenant(tenantId) {
9389
- this.tenants.delete(tenantId);
9390
- }
9391
- /**
9392
- * Get tenant config
9393
- */
9394
- getTenant(tenantId) {
9395
- return this.tenants.get(tenantId);
9396
- }
9397
- /**
9398
- * Create a new conversation session for a tenant
9399
- */
9400
- async createSession(tenantId, options = {}) {
9401
- const tenant = this.tenants.get(tenantId);
9402
- if (!tenant) {
9403
- throw new Error(`Tenant not found: ${tenantId}`);
9404
- }
9405
- const sessionId = options.sessionId || this.generateSessionId();
9406
- const sessionConfig = {
9407
- sessionId,
9408
- tenant,
9409
- systemPrompt: options.systemPrompt,
9410
- voice: options.voice,
9411
- emotion: options.emotion,
9412
- language: options.language
9413
- };
9414
- const session = new ConversationSessionImpl(sessionConfig, this.adapter);
9415
- this.sessions.set(sessionId, session);
9416
- this.forwardAdapterEvents(this.adapter, sessionId);
9417
- await session.start();
9418
- this.emit("session.created", { sessionId, tenantId });
9419
- return session;
9420
- }
9421
- /**
9422
- * End a session
9423
- */
9424
- async endSession(sessionId) {
9425
- const session = this.sessions.get(sessionId);
9426
- if (session) {
9427
- await session.end();
9428
- this.sessions.delete(sessionId);
9429
- this.emit("session.ended", { sessionId, reason: "Client requested" });
9430
- }
9431
- }
9432
- /**
9433
- * Get session by ID
9434
- */
9435
- getSession(sessionId) {
9436
- return this.sessions.get(sessionId);
9437
- }
9438
- /**
9439
- * Get all sessions for a tenant
9440
- */
9441
- getTenantSessions(tenantId) {
9442
- return Array.from(this.sessions.values()).filter((s) => s.config.tenant.tenantId === tenantId);
9443
- }
9444
- /**
9445
- * Start health monitoring
9446
- */
9447
- startHealthMonitoring() {
9448
- if (this.healthCheckInterval) return;
9449
- this.healthCheckInterval = setInterval(async () => {
9450
- await this.performHealthCheck();
9451
- }, this.HEALTH_CHECK_INTERVAL_MS);
9452
- }
9453
- /**
9454
- * Stop health monitoring
9455
- */
9456
- stopHealthMonitoring() {
9457
- if (this.healthCheckInterval) {
9458
- clearInterval(this.healthCheckInterval);
9459
- this.healthCheckInterval = null;
9460
- }
9461
- }
9462
- /**
9463
- * Dispose all resources
9464
- */
9465
- async dispose() {
9466
- this.stopHealthMonitoring();
9467
- const endPromises = Array.from(this.sessions.values()).map((s) => s.end());
9468
- await Promise.all(endPromises);
9469
- this.sessions.clear();
9470
- await this.adapter.disconnect();
9471
- }
9472
- // ==================== Private Methods ====================
9473
- generateSessionId() {
9474
- return `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
9475
- }
9476
- forwardAdapterEvents(adapter, sessionId) {
9477
- const events = [
9478
- "state.change",
9479
- "user.speech.start",
9480
- "user.speech.end",
9481
- "user.transcript.partial",
9482
- "user.transcript.final",
9483
- "ai.thinking.start",
9484
- "ai.response.start",
9485
- "ai.response.chunk",
9486
- "ai.response.end",
9487
- "audio.output.chunk",
9488
- "audio.output.end",
9489
- "animation",
9490
- "memory.updated",
9491
- "connection.error",
9492
- "interruption.detected",
9493
- "interruption.handled"
9494
- ];
9495
- for (const event of events) {
9496
- adapter.on(event, (data) => {
9497
- const eventData = data;
9498
- this.emit(event, { ...eventData, sessionId });
9499
- });
9500
- }
9501
- }
9502
- async performHealthCheck() {
9503
- try {
9504
- await this.adapter.healthCheck();
9505
- } catch {
9506
- }
9507
- }
9508
- };
9509
-
9510
- // src/ai/tenancy/TenantManager.ts
9511
- var _TenantManager = class _TenantManager {
9512
- constructor() {
9513
- this.tenants = /* @__PURE__ */ new Map();
9514
- this.quotas = /* @__PURE__ */ new Map();
9515
- this.usage = /* @__PURE__ */ new Map();
9516
- this.tokenRefreshCallbacks = /* @__PURE__ */ new Map();
9517
- }
9518
- /**
9519
- * Register a tenant with quota
9520
- */
9521
- register(tenant, quota = _TenantManager.DEFAULT_QUOTA, tokenRefreshCallback) {
9522
- this.tenants.set(tenant.tenantId, tenant);
9523
- this.quotas.set(tenant.tenantId, quota);
9524
- this.usage.set(tenant.tenantId, {
9525
- currentSessions: 0,
9526
- requestsThisMinute: 0,
9527
- tokensUsed: 0,
9528
- audioMinutesToday: 0,
9529
- lastMinuteReset: Date.now(),
9530
- lastDailyReset: Date.now()
9531
- });
9532
- if (tokenRefreshCallback) {
9533
- this.tokenRefreshCallbacks.set(tenant.tenantId, tokenRefreshCallback);
9534
- }
9535
- }
9536
- /**
9537
- * Unregister a tenant
9538
- */
9539
- unregister(tenantId) {
9540
- this.tenants.delete(tenantId);
9541
- this.quotas.delete(tenantId);
9542
- this.usage.delete(tenantId);
9543
- this.tokenRefreshCallbacks.delete(tenantId);
9544
- }
9545
- /**
9546
- * Get tenant config
9547
- */
9548
- get(tenantId) {
9549
- return this.tenants.get(tenantId);
9550
- }
9551
- /**
9552
- * Check if tenant exists
9553
- */
9554
- has(tenantId) {
9555
- return this.tenants.has(tenantId);
9556
- }
9557
- /**
9558
- * Get all tenant IDs
9559
- */
9560
- getTenantIds() {
9561
- return Array.from(this.tenants.keys());
9562
- }
9563
- /**
9564
- * Check if tenant can create new session
9565
- */
9566
- canCreateSession(tenantId) {
9567
- const quota = this.quotas.get(tenantId);
9568
- const usage = this.usage.get(tenantId);
9569
- if (!quota || !usage) return false;
9570
- return usage.currentSessions < quota.maxSessions;
9571
- }
9572
- /**
9573
- * Check if tenant can make request
9574
- */
9575
- canMakeRequest(tenantId) {
9576
- const quota = this.quotas.get(tenantId);
9577
- const usage = this.usage.get(tenantId);
9578
- if (!quota || !usage) return false;
9579
- this.checkMinuteReset(tenantId);
9580
- return usage.requestsThisMinute < quota.requestsPerMinute;
9581
- }
9582
- /**
9583
- * Check if tenant can use audio
9584
- */
9585
- canUseAudio(tenantId, minutes) {
9586
- const quota = this.quotas.get(tenantId);
9587
- const usage = this.usage.get(tenantId);
9588
- if (!quota || !usage) return false;
9589
- this.checkDailyReset(tenantId);
9590
- return usage.audioMinutesToday + minutes <= quota.maxAudioMinutesPerDay;
9591
- }
9592
- /**
9593
- * Increment session count
9594
- */
9595
- incrementSessions(tenantId) {
9596
- const usage = this.usage.get(tenantId);
9597
- if (usage) {
9598
- usage.currentSessions++;
9599
- }
9600
- }
9601
- /**
9602
- * Decrement session count
9603
- */
9604
- decrementSessions(tenantId) {
9605
- const usage = this.usage.get(tenantId);
9606
- if (usage && usage.currentSessions > 0) {
9607
- usage.currentSessions--;
9608
- }
9609
- }
9610
- /**
9611
- * Record a request
9612
- */
9613
- recordRequest(tenantId) {
9614
- const usage = this.usage.get(tenantId);
9615
- if (usage) {
9616
- this.checkMinuteReset(tenantId);
9617
- usage.requestsThisMinute++;
9618
- }
9619
- }
9620
- /**
9621
- * Record token usage
9622
- */
9623
- recordTokens(tenantId, tokens) {
9624
- const usage = this.usage.get(tenantId);
9625
- if (usage) {
9626
- usage.tokensUsed += tokens;
9627
- }
9628
- }
9629
- /**
9630
- * Record audio usage
9631
- */
9632
- recordAudioMinutes(tenantId, minutes) {
9633
- const usage = this.usage.get(tenantId);
9634
- if (usage) {
9635
- this.checkDailyReset(tenantId);
9636
- usage.audioMinutesToday += minutes;
9637
- }
9638
- }
9639
- /**
9640
- * Get fresh auth token for tenant
9641
- */
9642
- async getAuthToken(tenantId) {
9643
- const tenant = this.tenants.get(tenantId);
9644
- if (!tenant) {
9645
- throw new Error(`Tenant not found: ${tenantId}`);
9646
- }
9647
- const callback = this.tokenRefreshCallbacks.get(tenantId);
9648
- if (callback) {
9649
- const token = await callback();
9650
- tenant.credentials.authToken = token;
9651
- return token;
9652
- }
9653
- if (tenant.credentials.authToken) {
9654
- return tenant.credentials.authToken;
9655
- }
9656
- throw new Error(`No auth token available for tenant: ${tenantId}`);
9657
- }
9658
- /**
9659
- * Update tenant credentials
9660
- */
9661
- updateCredentials(tenantId, credentials) {
9662
- const tenant = this.tenants.get(tenantId);
9663
- if (tenant) {
9664
- tenant.credentials = { ...tenant.credentials, ...credentials };
9665
- }
9666
- }
9667
- /**
9668
- * Get usage stats for tenant
9669
- */
9670
- getUsage(tenantId) {
9671
- return this.usage.get(tenantId);
9672
- }
9673
- /**
9674
- * Get quota for tenant
9675
- */
9676
- getQuota(tenantId) {
9677
- return this.quotas.get(tenantId);
9678
- }
9679
- /**
9680
- * Update quota for tenant
9681
- */
9682
- updateQuota(tenantId, quota) {
9683
- const existing = this.quotas.get(tenantId);
9684
- if (existing) {
9685
- this.quotas.set(tenantId, { ...existing, ...quota });
9686
- }
9687
- }
9688
- /**
9689
- * Reset all usage stats for a tenant
9690
- */
9691
- resetUsage(tenantId) {
9692
- const usage = this.usage.get(tenantId);
9693
- if (usage) {
9694
- usage.requestsThisMinute = 0;
9695
- usage.tokensUsed = 0;
9696
- usage.audioMinutesToday = 0;
9697
- usage.lastMinuteReset = Date.now();
9698
- usage.lastDailyReset = Date.now();
9699
- }
9700
- }
9701
- // ==================== Private Methods ====================
9702
- checkMinuteReset(tenantId) {
9703
- const usage = this.usage.get(tenantId);
9704
- if (!usage) return;
9705
- const now = Date.now();
9706
- if (now - usage.lastMinuteReset >= 6e4) {
9707
- usage.requestsThisMinute = 0;
9708
- usage.lastMinuteReset = now;
9709
- }
9710
- }
9711
- checkDailyReset(tenantId) {
9712
- const usage = this.usage.get(tenantId);
9713
- if (!usage) return;
9714
- const now = Date.now();
9715
- const MS_PER_DAY = 24 * 60 * 60 * 1e3;
9716
- if (now - usage.lastDailyReset >= MS_PER_DAY) {
9717
- usage.audioMinutesToday = 0;
9718
- usage.lastDailyReset = now;
9719
- }
9720
- }
9721
- };
9722
- /**
9723
- * Default quota for new tenants
9724
- */
9725
- _TenantManager.DEFAULT_QUOTA = {
9726
- maxSessions: 10,
9727
- requestsPerMinute: 60,
9728
- maxTokensPerConversation: 1e5,
9729
- maxAudioMinutesPerDay: 60
9730
- };
9731
- var TenantManager = _TenantManager;
9732
-
9733
- // src/ai/utils/AudioSyncManager.ts
9734
- var AudioSyncManager = class extends EventEmitter {
9735
- constructor(config = {}) {
9736
- super();
9737
- this.bufferPosition = 0;
9738
- this.playbackQueue = [];
9739
- this.isPlaying = false;
9740
- this.audioContext = null;
9741
- this.playbackStartTime = 0;
9742
- this.samplesPlayed = 0;
9743
- this.config = {
9744
- sampleRate: 16e3,
9745
- bufferSize: 16640,
9746
- overlapSize: 4160,
9747
- maxDriftMs: 100,
9748
- ...config
9749
- };
9750
- this.audioBuffer = new Float32Array(this.config.bufferSize);
9751
- }
9752
- /**
9753
- * Initialize audio context
9754
- */
9755
- async initialize() {
9756
- if (!this.audioContext) {
9757
- this.audioContext = new AudioContext({ sampleRate: this.config.sampleRate });
9758
- }
9759
- if (this.audioContext.state === "suspended") {
9760
- await this.audioContext.resume();
9761
- }
9762
- }
9763
- /**
9764
- * Push audio chunk for processing and playback
9765
- */
9766
- pushAudio(audio) {
9767
- this.playbackQueue.push(audio);
9768
- this.bufferForInference(audio);
9769
- if (!this.isPlaying && this.playbackQueue.length > 0) {
9770
- this.startPlayback();
9771
- }
9772
- }
9773
- /**
9774
- * Buffer audio for inference
9775
- */
9776
- bufferForInference(audio) {
9777
- let offset = 0;
9778
- while (offset < audio.length) {
9779
- const remaining = this.config.bufferSize - this.bufferPosition;
9780
- const toCopy = Math.min(remaining, audio.length - offset);
9781
- this.audioBuffer.set(audio.subarray(offset, offset + toCopy), this.bufferPosition);
9782
- this.bufferPosition += toCopy;
9783
- offset += toCopy;
9784
- if (this.bufferPosition >= this.config.bufferSize) {
9785
- this.emit("buffer.ready", { audio: new Float32Array(this.audioBuffer) });
9786
- const overlapStart = this.config.bufferSize - this.config.overlapSize;
9787
- this.audioBuffer.copyWithin(0, overlapStart);
9788
- this.bufferPosition = this.config.overlapSize;
9789
- }
9790
- }
9791
- }
9792
- /**
9793
- * Start audio playback
9794
- */
9795
- async startPlayback() {
9796
- if (!this.audioContext || this.isPlaying) return;
9797
- this.isPlaying = true;
9798
- this.playbackStartTime = this.audioContext.currentTime;
9799
- this.samplesPlayed = 0;
9800
- this.emit("playback.start", {});
9801
- await this.processPlaybackQueue();
9802
- }
9803
- /**
9804
- * Process playback queue
9805
- */
9806
- async processPlaybackQueue() {
9807
- if (!this.audioContext) return;
9808
- while (this.playbackQueue.length > 0) {
9809
- const audio = this.playbackQueue.shift();
9810
- const buffer = this.audioContext.createBuffer(1, audio.length, this.config.sampleRate);
9811
- buffer.copyToChannel(audio, 0);
9812
- const source = this.audioContext.createBufferSource();
9813
- source.buffer = buffer;
9814
- source.connect(this.audioContext.destination);
9815
- const playTime = this.playbackStartTime + this.samplesPlayed / this.config.sampleRate;
9816
- source.start(playTime);
9817
- this.samplesPlayed += audio.length;
9818
- this.checkDrift();
9819
- await new Promise((resolve) => {
9820
- source.onended = resolve;
9821
- });
9822
- }
9823
- this.isPlaying = false;
9824
- this.emit("playback.end", {});
9825
- }
9826
- /**
9827
- * Check for audio/animation drift
9828
- */
9829
- checkDrift() {
9830
- if (!this.audioContext) return;
9831
- const expectedTime = this.playbackStartTime + this.samplesPlayed / this.config.sampleRate;
9832
- const actualTime = this.audioContext.currentTime;
9833
- const driftMs = (actualTime - expectedTime) * 1e3;
9834
- if (Math.abs(driftMs) > this.config.maxDriftMs) {
9835
- this.emit("sync.drift", { driftMs });
9836
- }
9837
- }
9838
- /**
9839
- * Clear playback queue
9840
- */
9841
- clearQueue() {
9842
- this.playbackQueue = [];
9843
- this.bufferPosition = 0;
9844
- this.audioBuffer.fill(0);
9845
- }
9846
- /**
9847
- * Stop playback
9848
- */
9849
- stop() {
9850
- this.clearQueue();
9851
- this.isPlaying = false;
9852
- }
9853
- /**
9854
- * Get current playback position in seconds
9855
- */
9856
- getPlaybackPosition() {
9857
- if (!this.audioContext) return 0;
9858
- return this.audioContext.currentTime - this.playbackStartTime;
9859
- }
9860
- /**
9861
- * Check if currently playing
9862
- */
9863
- getIsPlaying() {
9864
- return this.isPlaying;
9865
- }
9866
- /**
9867
- * Dispose resources
9868
- */
9869
- dispose() {
9870
- this.stop();
9871
- this.audioContext?.close();
9872
- this.audioContext = null;
9873
- }
9874
- };
9875
-
9876
- // src/ai/utils/InterruptionHandler.ts
9877
- var InterruptionHandler = class extends EventEmitter {
9878
- constructor(config = {}) {
9879
- super();
9880
- this.isSpeaking = false;
9881
- this.speechStartTime = 0;
9882
- this.lastSpeechTime = 0;
9883
- this.silenceTimer = null;
9884
- this.aiIsSpeaking = false;
9885
- // Debouncing: only emit one interruption per speech session
9886
- this.interruptionTriggeredThisSession = false;
9887
- this.config = {
9888
- vadThreshold: 0.5,
9889
- // Silero VAD default
9890
- minSpeechDurationMs: 200,
9891
- // Google/Amazon barge-in standard
9892
- silenceTimeoutMs: 500,
9893
- // OpenAI Realtime API standard
9894
- enabled: true,
9895
- ...config
9896
- };
9897
- }
9898
- /**
9899
- * Process VAD result for interruption detection
9900
- * @param vadProbability - Speech probability from VAD (0-1)
9901
- * @param audioEnergy - Optional RMS energy for logging (default: 0)
9902
- */
9903
- processVADResult(vadProbability, audioEnergy = 0) {
9904
- if (!this.config.enabled) return;
9905
- if (vadProbability > this.config.vadThreshold) {
9906
- this.onSpeechDetected(audioEnergy || vadProbability);
9907
- } else {
9908
- this.onSilenceDetected();
9909
- }
9910
- }
9911
- /**
9912
- * Notify that AI started speaking
9913
- */
9914
- setAISpeaking(speaking) {
9915
- this.aiIsSpeaking = speaking;
9916
- }
9917
- /**
9918
- * Enable/disable interruption detection
9919
- */
9920
- setEnabled(enabled) {
9921
- this.config.enabled = enabled;
9922
- if (!enabled) {
9923
- this.reset();
9924
- }
9925
- }
9926
- /**
9927
- * Update configuration
9928
- */
9929
- updateConfig(config) {
9930
- this.config = { ...this.config, ...config };
9931
- }
9932
- /**
9933
- * Reset state
9934
- */
9935
- reset() {
9936
- this.isSpeaking = false;
9937
- this.speechStartTime = 0;
9938
- this.lastSpeechTime = 0;
9939
- this.interruptionTriggeredThisSession = false;
9940
- if (this.silenceTimer) {
9941
- clearTimeout(this.silenceTimer);
9942
- this.silenceTimer = null;
9943
- }
9944
- }
9945
- /**
9946
- * Get current state
9947
- */
9948
- getState() {
9949
- return {
9950
- isSpeaking: this.isSpeaking,
9951
- speechDurationMs: this.isSpeaking ? Date.now() - this.speechStartTime : 0
9952
- };
9953
- }
9954
- // ==================== Private Methods ====================
9955
- onSpeechDetected(rms) {
9956
- const now = Date.now();
9957
- this.lastSpeechTime = now;
9958
- if (this.silenceTimer) {
9959
- clearTimeout(this.silenceTimer);
9960
- this.silenceTimer = null;
9961
- }
9962
- if (!this.isSpeaking) {
9963
- this.isSpeaking = true;
9964
- this.speechStartTime = now;
9965
- this.emit("speech.detected", { rms });
9966
- }
9967
- if (this.aiIsSpeaking && !this.interruptionTriggeredThisSession) {
9968
- const speechDuration = now - this.speechStartTime;
9969
- if (speechDuration >= this.config.minSpeechDurationMs) {
9970
- this.interruptionTriggeredThisSession = true;
9971
- this.emit("interruption.triggered", { rms, durationMs: speechDuration });
9972
- }
9973
- }
9974
- }
9975
- onSilenceDetected() {
9976
- if (!this.isSpeaking) return;
9977
- if (!this.silenceTimer) {
9978
- this.silenceTimer = setTimeout(() => {
9979
- const durationMs = this.lastSpeechTime - this.speechStartTime;
9980
- this.isSpeaking = false;
9981
- this.silenceTimer = null;
9982
- this.interruptionTriggeredThisSession = false;
9983
- this.emit("speech.ended", { durationMs });
9984
- }, this.config.silenceTimeoutMs);
9985
- }
9986
- }
9987
- };
9988
-
9989
8876
  // src/animation/types.ts
9990
8877
  var DEFAULT_ANIMATION_CONFIG = {
9991
8878
  initialState: "idle",
@@ -11028,17 +9915,14 @@ export {
11028
9915
  A2EOrchestrator,
11029
9916
  A2EProcessor,
11030
9917
  ARKIT_BLENDSHAPES,
11031
- AgentCoreAdapter,
11032
9918
  AnimationGraph,
11033
9919
  AudioChunkCoalescer,
11034
9920
  AudioEnergyAnalyzer,
11035
9921
  AudioScheduler,
11036
- AudioSyncManager,
11037
9922
  BLENDSHAPE_TO_GROUP,
11038
9923
  BlendshapeSmoother,
11039
9924
  CTC_VOCAB,
11040
9925
  ConsoleExporter,
11041
- ConversationOrchestrator,
11042
9926
  DEFAULT_ANIMATION_CONFIG,
11043
9927
  DEFAULT_LOGGING_CONFIG,
11044
9928
  EMOTION_NAMES,
@@ -11068,7 +9952,6 @@ export {
11068
9952
  SileroVADInference,
11069
9953
  SileroVADUnifiedAdapter,
11070
9954
  SileroVADWorker,
11071
- TenantManager,
11072
9955
  UnifiedInferenceWorker,
11073
9956
  Wav2ArkitCpuInference,
11074
9957
  Wav2ArkitCpuUnifiedAdapter,