@micdrop/server 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -21,6 +21,28 @@ declare enum MicdropServerCommands {
21
21
  EndCall = "EndCall",
22
22
  ToolCall = "ToolCall"
23
23
  }
24
+ /**
25
+ * Hears whether a sentence has landed, where voice activity detection only
26
+ * hears whether someone is speaking.
27
+ *
28
+ * `SmartTurn` from `@micdrop/smart-turn` implements it, and so can anything
29
+ * else, a call to a service included. Both sides of a call can hold one, the
30
+ * client to decide when its turn ends and the server to decide when to answer.
31
+ */
32
+ interface TurnDetector {
33
+ /**
34
+ * Feeds the audio received since the last call
35
+ * @param samples - Mono samples, in the -1..1 range
36
+ * @param sampleRate - Sample rate of `samples`, in Hz
37
+ */
38
+ push(samples: Float32Array, sampleRate?: number): void;
39
+ /** Answers whether the turn pushed so far sounds finished */
40
+ predict(): Promise<{
41
+ complete: boolean;
42
+ }>;
43
+ /** Starts a new turn, forgetting the previous one */
44
+ reset(): void;
45
+ }
24
46
  interface MicdropCallSummary {
25
47
  conversation: MicdropConversation;
26
48
  duration: number;
@@ -363,6 +385,17 @@ interface MicdropConfig {
363
385
  agent: Agent;
364
386
  stt: STT;
365
387
  tts: TTS;
388
+ /**
389
+ * Waits for the rest of the sentence when the speaker paused in the middle
390
+ * of one, instead of answering an unfinished question.
391
+ *
392
+ * Prefer running the detector in the client, which reaches the same decision
393
+ * without the round trip and can then close its turns sooner. This is the
394
+ * option for the browsers where the model has nowhere to run.
395
+ */
396
+ turnDetector?: TurnDetector;
397
+ /** How long to wait for the rest of a sentence, 4000 ms by default */
398
+ turnMaxWait?: number;
366
399
  }
367
400
  declare class MicdropServer extends EventEmitter<MicdropServerEvents> {
368
401
  socket: WebSocket$1 | null;
@@ -374,6 +407,8 @@ declare class MicdropServer extends EventEmitter<MicdropServerEvents> {
374
407
  private isProcessingQueue;
375
408
  private currentUserStream?;
376
409
  private userSpeechChunks;
410
+ private turnComplete?;
411
+ private heldTurnTimer?;
377
412
  constructor(socket: WebSocket$1, config: MicdropConfig);
378
413
  private log;
379
414
  private processQueue;
@@ -385,6 +420,17 @@ declare class MicdropServer extends EventEmitter<MicdropServerEvents> {
385
420
  private onMute;
386
421
  private onStartSpeaking;
387
422
  private onStopSpeaking;
423
+ private predictTurnComplete;
424
+ /** Answers, unless the sentence sounds like it has more coming */
425
+ private answerUserTurn;
426
+ /**
427
+ * Answers anyway if the rest of the sentence never comes.
428
+ *
429
+ * Without it, a detector that hears an unfinished sentence where there is
430
+ * none leaves the call silent for good.
431
+ */
432
+ private holdTurn;
433
+ private releaseHeldTurn;
388
434
  private onTranscriptSTT;
389
435
  private onAudioTTS;
390
436
  private sendFirstMessage;
@@ -427,4 +473,4 @@ declare class MicdropRecorder extends EventEmitter<MicdropRecorderEvents> {
427
473
 
428
474
  declare function waitForParams<CallParams>(socket: WebSocket$1, validate: (params: any) => CallParams): Promise<CallParams>;
429
475
 
430
- export { AUTO_END_CALL_PROMPT, AUTO_END_CALL_TOOL_NAME, AUTO_IGNORE_USER_NOISE_PROMPT, AUTO_IGNORE_USER_NOISE_TOOL_NAME, AUTO_SEMANTIC_TURN_PROMPT, AUTO_SEMANTIC_TURN_TOOL_NAME, Agent, type AgentEvents, type AgentOptions, type AudioMessage, type DeepPartial, type ExtractJsonOptions, type ExtractOptions, type ExtractTagOptions, FallbackAgent, type FallbackAgentOptions, FallbackSTT, type FallbackSTTOptions, FallbackTTS, type FallbackTTSOptions, Logger, type MicdropAnswerMetadata, type MicdropCallSummary, MicdropClientCommands, type MicdropConfig, type MicdropConversation, type MicdropConversationItem, type MicdropConversationMessage, type MicdropConversationToolCall, type MicdropConversationToolResult, MicdropError, MicdropErrorCode, MicdropRecorder, type MicdropRecorderEvents, MicdropServer, MicdropServerCommands, type MicdropServerEvents, type MicdropToolCall, MockAgent, MockSTT, MockTTS, Pcm16Resampler, STT, type STTEvents, SentenceSplitter, SentenceTTS, TTS, type TTSEvents, type Tool, float32ToPcm16, handleError, pcm16ToFloat32, waitForParams };
476
+ export { AUTO_END_CALL_PROMPT, AUTO_END_CALL_TOOL_NAME, AUTO_IGNORE_USER_NOISE_PROMPT, AUTO_IGNORE_USER_NOISE_TOOL_NAME, AUTO_SEMANTIC_TURN_PROMPT, AUTO_SEMANTIC_TURN_TOOL_NAME, Agent, type AgentEvents, type AgentOptions, type AudioMessage, type DeepPartial, type ExtractJsonOptions, type ExtractOptions, type ExtractTagOptions, FallbackAgent, type FallbackAgentOptions, FallbackSTT, type FallbackSTTOptions, FallbackTTS, type FallbackTTSOptions, Logger, type MicdropAnswerMetadata, type MicdropCallSummary, MicdropClientCommands, type MicdropConfig, type MicdropConversation, type MicdropConversationItem, type MicdropConversationMessage, type MicdropConversationToolCall, type MicdropConversationToolResult, MicdropError, MicdropErrorCode, MicdropRecorder, type MicdropRecorderEvents, MicdropServer, MicdropServerCommands, type MicdropServerEvents, type MicdropToolCall, MockAgent, MockSTT, MockTTS, Pcm16Resampler, STT, type STTEvents, SentenceSplitter, SentenceTTS, TTS, type TTSEvents, type Tool, type TurnDetector, float32ToPcm16, handleError, pcm16ToFloat32, waitForParams };
package/dist/index.d.ts CHANGED
@@ -21,6 +21,28 @@ declare enum MicdropServerCommands {
21
21
  EndCall = "EndCall",
22
22
  ToolCall = "ToolCall"
23
23
  }
24
+ /**
25
+ * Hears whether a sentence has landed, where voice activity detection only
26
+ * hears whether someone is speaking.
27
+ *
28
+ * `SmartTurn` from `@micdrop/smart-turn` implements it, and so can anything
29
+ * else, a call to a service included. Both sides of a call can hold one, the
30
+ * client to decide when its turn ends and the server to decide when to answer.
31
+ */
32
+ interface TurnDetector {
33
+ /**
34
+ * Feeds the audio received since the last call
35
+ * @param samples - Mono samples, in the -1..1 range
36
+ * @param sampleRate - Sample rate of `samples`, in Hz
37
+ */
38
+ push(samples: Float32Array, sampleRate?: number): void;
39
+ /** Answers whether the turn pushed so far sounds finished */
40
+ predict(): Promise<{
41
+ complete: boolean;
42
+ }>;
43
+ /** Starts a new turn, forgetting the previous one */
44
+ reset(): void;
45
+ }
24
46
  interface MicdropCallSummary {
25
47
  conversation: MicdropConversation;
26
48
  duration: number;
@@ -363,6 +385,17 @@ interface MicdropConfig {
363
385
  agent: Agent;
364
386
  stt: STT;
365
387
  tts: TTS;
388
+ /**
389
+ * Waits for the rest of the sentence when the speaker paused in the middle
390
+ * of one, instead of answering an unfinished question.
391
+ *
392
+ * Prefer running the detector in the client, which reaches the same decision
393
+ * without the round trip and can then close its turns sooner. This is the
394
+ * option for the browsers where the model has nowhere to run.
395
+ */
396
+ turnDetector?: TurnDetector;
397
+ /** How long to wait for the rest of a sentence, 4000 ms by default */
398
+ turnMaxWait?: number;
366
399
  }
367
400
  declare class MicdropServer extends EventEmitter<MicdropServerEvents> {
368
401
  socket: WebSocket$1 | null;
@@ -374,6 +407,8 @@ declare class MicdropServer extends EventEmitter<MicdropServerEvents> {
374
407
  private isProcessingQueue;
375
408
  private currentUserStream?;
376
409
  private userSpeechChunks;
410
+ private turnComplete?;
411
+ private heldTurnTimer?;
377
412
  constructor(socket: WebSocket$1, config: MicdropConfig);
378
413
  private log;
379
414
  private processQueue;
@@ -385,6 +420,17 @@ declare class MicdropServer extends EventEmitter<MicdropServerEvents> {
385
420
  private onMute;
386
421
  private onStartSpeaking;
387
422
  private onStopSpeaking;
423
+ private predictTurnComplete;
424
+ /** Answers, unless the sentence sounds like it has more coming */
425
+ private answerUserTurn;
426
+ /**
427
+ * Answers anyway if the rest of the sentence never comes.
428
+ *
429
+ * Without it, a detector that hears an unfinished sentence where there is
430
+ * none leaves the call silent for good.
431
+ */
432
+ private holdTurn;
433
+ private releaseHeldTurn;
388
434
  private onTranscriptSTT;
389
435
  private onAudioTTS;
390
436
  private sendFirstMessage;
@@ -427,4 +473,4 @@ declare class MicdropRecorder extends EventEmitter<MicdropRecorderEvents> {
427
473
 
428
474
  declare function waitForParams<CallParams>(socket: WebSocket$1, validate: (params: any) => CallParams): Promise<CallParams>;
429
475
 
430
- export { AUTO_END_CALL_PROMPT, AUTO_END_CALL_TOOL_NAME, AUTO_IGNORE_USER_NOISE_PROMPT, AUTO_IGNORE_USER_NOISE_TOOL_NAME, AUTO_SEMANTIC_TURN_PROMPT, AUTO_SEMANTIC_TURN_TOOL_NAME, Agent, type AgentEvents, type AgentOptions, type AudioMessage, type DeepPartial, type ExtractJsonOptions, type ExtractOptions, type ExtractTagOptions, FallbackAgent, type FallbackAgentOptions, FallbackSTT, type FallbackSTTOptions, FallbackTTS, type FallbackTTSOptions, Logger, type MicdropAnswerMetadata, type MicdropCallSummary, MicdropClientCommands, type MicdropConfig, type MicdropConversation, type MicdropConversationItem, type MicdropConversationMessage, type MicdropConversationToolCall, type MicdropConversationToolResult, MicdropError, MicdropErrorCode, MicdropRecorder, type MicdropRecorderEvents, MicdropServer, MicdropServerCommands, type MicdropServerEvents, type MicdropToolCall, MockAgent, MockSTT, MockTTS, Pcm16Resampler, STT, type STTEvents, SentenceSplitter, SentenceTTS, TTS, type TTSEvents, type Tool, float32ToPcm16, handleError, pcm16ToFloat32, waitForParams };
476
+ export { AUTO_END_CALL_PROMPT, AUTO_END_CALL_TOOL_NAME, AUTO_IGNORE_USER_NOISE_PROMPT, AUTO_IGNORE_USER_NOISE_TOOL_NAME, AUTO_SEMANTIC_TURN_PROMPT, AUTO_SEMANTIC_TURN_TOOL_NAME, Agent, type AgentEvents, type AgentOptions, type AudioMessage, type DeepPartial, type ExtractJsonOptions, type ExtractOptions, type ExtractTagOptions, FallbackAgent, type FallbackAgentOptions, FallbackSTT, type FallbackSTTOptions, FallbackTTS, type FallbackTTSOptions, Logger, type MicdropAnswerMetadata, type MicdropCallSummary, MicdropClientCommands, type MicdropConfig, type MicdropConversation, type MicdropConversationItem, type MicdropConversationMessage, type MicdropConversationToolCall, type MicdropConversationToolResult, MicdropError, MicdropErrorCode, MicdropRecorder, type MicdropRecorderEvents, MicdropServer, MicdropServerCommands, type MicdropServerEvents, type MicdropToolCall, MockAgent, MockSTT, MockTTS, Pcm16Resampler, STT, type STTEvents, SentenceSplitter, SentenceTTS, TTS, type TTSEvents, type Tool, type TurnDetector, float32ToPcm16, handleError, pcm16ToFloat32, waitForParams };
package/dist/index.js CHANGED
@@ -511,6 +511,8 @@ var MicdropServerCommands = /* @__PURE__ */ ((MicdropServerCommands2) => {
511
511
  })(MicdropServerCommands || {});
512
512
 
513
513
  // src/MicdropServer.ts
514
+ var USER_SAMPLE_RATE = 16e3;
515
+ var DEFAULT_TURN_MAX_WAIT = 4e3;
514
516
  var MicdropServer = class extends import_eventemitter32.EventEmitter {
515
517
  constructor(socket, config) {
516
518
  super();
@@ -522,6 +524,7 @@ var MicdropServer = class extends import_eventemitter32.EventEmitter {
522
524
  this.isProcessingQueue = false;
523
525
  this.userSpeechChunks = 0;
524
526
  this.onClose = () => {
527
+ this.releaseHeldTurn();
525
528
  if (!this.config) return;
526
529
  this.log("Connection closed");
527
530
  const duration = Math.round((Date.now() - this.startTime) / 1e3);
@@ -565,8 +568,7 @@ var MicdropServer = class extends import_eventemitter32.EventEmitter {
565
568
  this.config.agent.addUserMessage(transcript);
566
569
  if (!this.currentUserStream) {
567
570
  this.log("User stopped speaking, answering");
568
- this.cancel();
569
- this.answer();
571
+ this.answerUserTurn();
570
572
  }
571
573
  };
572
574
  this.onAudioTTS = (audio) => {
@@ -639,6 +641,7 @@ var MicdropServer = class extends import_eventemitter32.EventEmitter {
639
641
  this.log(`Received chunk (${chunk.byteLength} bytes)`);
640
642
  this.currentUserStream?.write(chunk);
641
643
  this.userSpeechChunks++;
644
+ this.config?.turnDetector?.push(pcm16ToFloat32(chunk), USER_SAMPLE_RATE);
642
645
  this.emit("UserAudio", chunk);
643
646
  }
644
647
  onMute() {
@@ -652,6 +655,9 @@ var MicdropServer = class extends import_eventemitter32.EventEmitter {
652
655
  this.userSpeechChunks = 0;
653
656
  this.currentUserStream?.end();
654
657
  this.currentUserStream = new import_stream2.PassThrough();
658
+ this.config.turnDetector?.reset();
659
+ this.turnComplete = void 0;
660
+ this.releaseHeldTurn();
655
661
  this.config.stt.transcribe(this.currentUserStream);
656
662
  this.cancel();
657
663
  }
@@ -664,15 +670,60 @@ var MicdropServer = class extends import_eventemitter32.EventEmitter {
664
670
  this.socket?.send("SkipAnswer" /* SkipAnswer */);
665
671
  return;
666
672
  }
673
+ this.turnComplete = this.predictTurnComplete();
667
674
  const conversation = this.config?.agent.conversation;
668
675
  const lastMessage = conversation?.[conversation.length - 1];
669
676
  if (lastMessage?.role === "user" && this.lastMessageSpeeched !== lastMessage) {
670
677
  this.log(
671
678
  "User stopped speaking and a transcript already exists, answering"
672
679
  );
680
+ this.answerUserTurn();
681
+ }
682
+ }
683
+ async predictTurnComplete() {
684
+ const detector = this.config?.turnDetector;
685
+ if (!detector) return true;
686
+ try {
687
+ const { complete } = await detector.predict();
688
+ this.log(`Turn sounds ${complete ? "finished" : "unfinished"}`);
689
+ return complete;
690
+ } catch (error) {
691
+ this.log(`Turn detection failed: ${error}`);
692
+ return true;
693
+ }
694
+ }
695
+ /** Answers, unless the sentence sounds like it has more coming */
696
+ async answerUserTurn() {
697
+ const complete = await (this.turnComplete ?? Promise.resolve(true));
698
+ if (!complete) {
699
+ this.log("Waiting for the rest of the sentence");
700
+ this.socket?.send("SkipAnswer" /* SkipAnswer */);
701
+ this.holdTurn();
702
+ return;
703
+ }
704
+ this.releaseHeldTurn();
705
+ this.cancel();
706
+ this.answer();
707
+ }
708
+ /**
709
+ * Answers anyway if the rest of the sentence never comes.
710
+ *
711
+ * Without it, a detector that hears an unfinished sentence where there is
712
+ * none leaves the call silent for good.
713
+ */
714
+ holdTurn() {
715
+ this.releaseHeldTurn();
716
+ this.heldTurnTimer = setTimeout(() => {
717
+ this.heldTurnTimer = void 0;
718
+ this.log("Nothing more came, answering");
673
719
  this.cancel();
674
720
  this.answer();
675
- }
721
+ }, this.config?.turnMaxWait ?? DEFAULT_TURN_MAX_WAIT);
722
+ }
723
+ releaseHeldTurn() {
724
+ if (!this.heldTurnTimer) return;
725
+ clearTimeout(this.heldTurnTimer);
726
+ this.heldTurnTimer = void 0;
676
727
  }
677
728
  sendFirstMessage() {
678
729
  if (!this.config) return;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/agent/Agent.ts","../src/agent/tools.ts","../src/Logger.ts","../src/agent/FallbackAgent.ts","../src/agent/MockAgent.ts","../src/audio/Pcm16Resampler.ts","../src/audio/pcm16.ts","../src/errors.ts","../src/MicdropServer.ts","../src/types.ts","../src/recorder/MicdropRecorder.ts","../src/stt/STT.ts","../src/stt/MockSTT.ts","../src/stt/FallbackSTT.ts","../src/tts/FallbackTTS.ts","../src/tts/TTS.ts","../src/tts/MockTTS.ts","../src/tts/SentenceSplitter.ts","../src/tts/SentenceTTS.ts","../src/waitForParams.ts"],"sourcesContent":["export * from './agent'\nexport * from './audio'\nexport * from './errors'\nexport * from './Logger'\nexport * from './MicdropServer'\nexport * from './recorder'\nexport * from './stt'\nexport * from './tts'\nexport * from './types'\nexport * from './waitForParams'\n","import { EventEmitter } from 'eventemitter3'\nimport { PassThrough, Readable, Writable } from 'stream'\nimport type { z } from 'zod'\nimport { Logger } from '../Logger'\nimport {\n MicdropAnswerMetadata,\n MicdropConversation,\n MicdropConversationItem,\n MicdropConversationMessage,\n MicdropConversationToolCall,\n MicdropConversationToolResult,\n MicdropToolCall,\n} from '../types'\nimport {\n AUTO_END_CALL_PROMPT,\n AUTO_END_CALL_TOOL_NAME,\n AUTO_IGNORE_USER_NOISE_PROMPT,\n AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n AUTO_SEMANTIC_TURN_PROMPT,\n AUTO_SEMANTIC_TURN_TOOL_NAME,\n Tool,\n} from './tools'\n\nexport interface AgentOptions {\n systemPrompt: string\n\n // Enable auto ending of the call when user asks to end the call\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoEndCall?: boolean | string\n\n // Enable detection of an incomplete sentence, and skip the answer (assistant waits)\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoSemanticTurn?: boolean | string\n\n // Ignore of the last user message when it's meaningless\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoIgnoreUserNoise?: boolean | string\n\n // Extract a value from the answer\n // Value must be at the end of the answer, in JSON or between tags\n extract?: ExtractJsonOptions | ExtractTagOptions\n\n // Function called before any answer is generated\n // Return true to skip generation\n onBeforeAnswer?: (\n this: Agent,\n stream: Writable\n ) => void | boolean | Promise<boolean>\n}\n\nexport interface AgentEvents {\n Message: [MicdropConversationItem]\n CancelLastUserMessage: []\n SkipAnswer: []\n EndCall: []\n ToolCall: [MicdropToolCall]\n // Emitted when the agent gives up generating an answer (e.g. after exhausting\n // its retries). Used by FallbackAgent to switch to the next agent.\n Failed: []\n}\n\nexport interface ExtractOptions {\n callback?: (value: string) => void\n saveInMetadata?: boolean\n}\n\nexport interface ExtractJsonOptions extends ExtractOptions {\n json: true\n callback?: (value: any) => void\n}\n\nexport interface ExtractTagOptions extends ExtractOptions {\n startTag: string\n endTag: string\n}\n\nexport abstract class Agent<\n Options extends AgentOptions = AgentOptions,\n> extends EventEmitter<AgentEvents> {\n public logger?: Logger\n public conversation: MicdropConversation\n public tools: Tool[]\n\n protected answerCount = 0\n protected answering = false\n\n constructor(protected options: Options) {\n super()\n this.conversation = [{ role: 'system', content: options.systemPrompt }]\n this.tools = this.getDefaultTools()\n }\n\n protected abstract generateAnswer(stream: PassThrough): Promise<void>\n abstract cancel(): void\n\n answer(): Readable {\n this.log('Start answering')\n const answerCount = ++this.answerCount\n const stream = new PassThrough()\n this.answering = true\n\n Promise.resolve()\n // Call hook onBeforeAnswer\n .then(() => this.options.onBeforeAnswer?.bind(this)(stream))\n // Generate answer (if not skipped)\n .then((skip) => {\n if (skip) return\n return this.generateAnswer(stream)\n })\n // End stream\n .finally(() => {\n if (stream.writable) {\n stream.end()\n }\n if (answerCount === this.answerCount) {\n this.answering = false\n }\n })\n\n return stream\n }\n\n addUserMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('user', text, metadata)\n }\n\n addAssistantMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('assistant', text, metadata)\n }\n\n addTool<Schema extends z.ZodObject>(tool: Tool<Schema>) {\n this.tools.push(tool)\n }\n\n removeTool(name: string) {\n const index = this.tools.findIndex((tool) => tool.name === name)\n if (index !== -1) {\n this.tools.splice(index, 1)\n }\n }\n\n getTool(name: string): Tool | undefined {\n return this.tools.find((tool) => tool.name === name)\n }\n\n addMessage(\n role: 'user' | 'assistant' | 'system',\n text: string,\n metadata?: MicdropAnswerMetadata\n ) {\n // A turn can carry no text at all, typically when the LLM answered with a\n // tool call only. Keeping it would send an empty message back to the LLM on\n // the next turn, and emit a Message event that consumers store as an empty\n // exchange in their transcripts.\n if (text.trim() === '') {\n this.log(`Skipping empty ${role} message`)\n return\n }\n\n this.log(`Adding ${role} message to conversation: ${text}`)\n const message: MicdropConversationMessage = {\n role,\n content: text,\n metadata,\n }\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n addToolMessage(\n message: MicdropConversationToolCall | MicdropConversationToolResult\n ) {\n this.log('Adding tool message:', message)\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n protected endCall() {\n this.log('Ending call')\n this.emit('EndCall')\n }\n\n protected cancelLastUserMessage() {\n this.log('Cancelling last user message')\n const lastMessageIndex = this.conversation.findLastIndex(\n (message) => message.role === 'user'\n )\n if (lastMessageIndex !== -1) {\n this.conversation.splice(lastMessageIndex, 1)\n }\n this.emit('CancelLastUserMessage')\n }\n\n protected skipAnswer() {\n this.log('Skipping answer')\n this.emit('SkipAnswer')\n }\n\n protected getDefaultTools() {\n const tools: Tool[] = []\n if (this.options.autoEndCall) {\n tools.push({\n name: AUTO_END_CALL_TOOL_NAME,\n description:\n typeof this.options.autoEndCall === 'string'\n ? this.options.autoEndCall\n : AUTO_END_CALL_PROMPT,\n execute: (_input, agent) => agent.endCall(),\n })\n }\n if (this.options.autoSemanticTurn) {\n tools.push({\n name: AUTO_SEMANTIC_TURN_TOOL_NAME,\n description:\n typeof this.options.autoSemanticTurn === 'string'\n ? this.options.autoSemanticTurn\n : AUTO_SEMANTIC_TURN_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.skipAnswer(),\n })\n }\n if (this.options.autoIgnoreUserNoise) {\n tools.push({\n name: AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n description:\n typeof this.options.autoIgnoreUserNoise === 'string'\n ? this.options.autoIgnoreUserNoise\n : AUTO_IGNORE_USER_NOISE_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.cancelLastUserMessage(),\n })\n }\n return tools\n }\n\n protected async executeTool(toolCall: MicdropConversationToolCall) {\n try {\n const tool = this.getTool(toolCall.toolName)\n if (!tool) {\n throw new Error(`Tool not found \"${toolCall.toolName}\"`)\n }\n\n this.log('Executing tool:', toolCall.toolName, toolCall.parameters)\n\n // Save tool call in conversation\n this.addToolMessage(toolCall)\n\n const parameters = JSON.parse(toolCall.parameters)\n const output = tool.execute ? await tool.execute(parameters, this) : {}\n\n // Save tool result in conversation\n this.addToolMessage({\n role: 'tool_result',\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n output: JSON.stringify(output ?? null),\n })\n\n // Emit output\n if (tool.emitOutput) {\n this.emit('ToolCall', {\n name: toolCall.toolName,\n parameters,\n output,\n })\n }\n\n return {\n output,\n skipAnswer: tool.skipAnswer,\n }\n } catch (error: any) {\n console.error('[OpenaiAgent] Error executing tool:', error)\n return {\n output: {\n error: error.message,\n },\n }\n }\n }\n\n protected getExtractOptions(): ExtractTagOptions | undefined {\n const extract = this.options.extract\n if (!extract) return undefined\n if ('json' in extract && extract.json) {\n return { ...extract, startTag: '{', endTag: '}' }\n }\n if ('startTag' in extract && 'endTag' in extract) {\n return extract\n }\n return undefined\n }\n\n public extract(message: string) {\n const extractOptions = this.getExtractOptions()\n let metadata: MicdropAnswerMetadata | undefined = undefined\n\n // Extract value?\n if (extractOptions) {\n const startTagIndex = message.indexOf(extractOptions.startTag)\n if (startTagIndex !== -1) {\n // Find end tag\n let endTagIndex = message.lastIndexOf(extractOptions.endTag)\n if (endTagIndex === -1) endTagIndex = message.length + 1\n else endTagIndex += extractOptions.endTag.length\n const extractedText = message.slice(startTagIndex, endTagIndex).trim()\n\n // Parse extracted value\n try {\n const extractedValue =\n 'json' in extractOptions && extractOptions.json\n ? JSON.parse(extractedText)\n : extractedText\n\n // Call callback\n if (extractOptions.callback) {\n extractOptions.callback(extractedValue)\n }\n\n // Save in metadata\n if (extractOptions.saveInMetadata) {\n metadata = { extracted: extractedValue }\n }\n } catch (error) {\n console.error(\n `[OpenaiAgent] Error parsing extracted value (${extractedText}):`,\n error\n )\n }\n\n // Remove extracted value from message\n message = message.slice(0, startTagIndex).trimEnd()\n }\n }\n return { message, metadata }\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n this.cancel()\n }\n}\n","import type { z } from 'zod'\nimport type { Agent } from './Agent'\n\nexport interface Tool<Schema extends z.ZodObject = z.ZodObject> {\n name: string\n description: string\n inputSchema?: Schema\n // The executing agent is passed as context so tools stay portable (no binding\n // to a specific agent instance), which lets them be shared between agents.\n execute?: (input: z.infer<Schema>, agent: Agent) => any | Promise<any>\n skipAnswer?: boolean\n emitOutput?: boolean\n}\n\nexport const AUTO_END_CALL_TOOL_NAME = 'end_call'\nexport const AUTO_END_CALL_PROMPT =\n 'Call this tool only if user asks to end the call'\n\nexport const AUTO_SEMANTIC_TURN_TOOL_NAME = 'semantic_turn'\nexport const AUTO_SEMANTIC_TURN_PROMPT =\n 'Call this tool only if last user message is obviously an incomplete sentence that you need to wait for the end before answering'\n\nexport const AUTO_IGNORE_USER_NOISE_TOOL_NAME = 'ignore_user_noise'\nexport const AUTO_IGNORE_USER_NOISE_PROMPT =\n 'Call this tool only if last user message is just an interjection or a sound that expresses emotion, hesitation, or reaction (ex: \"Uh\", \"Ahem\", \"Hmm\", \"Ah\") but doesn\\'t carry any clear meaning like agreeing, refusing, or commanding'\n","export class Logger {\n constructor(public name: string) {}\n\n log(...message: any[]) {\n const time = process.uptime().toFixed(3)\n console.log(`[${this.name} ${time}]`, ...message)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Logger } from '../Logger'\nimport { MicdropConversationItem, MicdropToolCall } from '../types'\nimport { Agent } from './Agent'\n\nexport interface FallbackAgentOptions {\n factories: Array<() => Agent>\n}\n\nexport class FallbackAgent extends Agent {\n private agent: Agent | null = null\n private agentIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly fallbackOptions: FallbackAgentOptions) {\n super({ systemPrompt: '' })\n if (this.fallbackOptions.factories.length === 0) {\n throw new Error('FallbackAgent: No factories provided')\n }\n this.startNextAgent()\n }\n\n // Delegate extraction to the active agent (extract config lives on children)\n extract(message: string) {\n return this.agent ? this.agent.extract(message) : super.extract(message)\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n // Try each agent once (one full rotation) until one answers successfully.\n // The conversation is shared between agents, so the next agent picks up\n // exactly where the failed one stopped.\n for (\n let attempt = 0;\n attempt < this.fallbackOptions.factories.length;\n attempt++\n ) {\n const agent = this.agent\n if (!agent) return\n\n let failed = false\n const onFailed = () => {\n failed = true\n }\n agent.once('Failed', onFailed)\n\n try {\n await this.pipeAnswer(agent, stream)\n } finally {\n agent.off('Failed', onFailed)\n }\n\n if (!failed) return\n\n this.log('Agent failed, trying next agent')\n this.startNextAgent()\n }\n\n // Every agent failed within this rotation: report it so an outer consumer\n // (e.g. a wrapping FallbackAgent) can react.\n this.log('All agents failed')\n this.emit('Failed')\n }\n\n cancel() {\n this.agent?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.agent?.destroy()\n this.agent = null\n this.agentIndex = -1\n }\n\n // Run the child agent and forward its answer chunks to our own stream\n private pipeAnswer(agent: Agent, stream: PassThrough): Promise<void> {\n return new Promise((resolve) => {\n const answerStream = agent.answer()\n answerStream.on('data', (chunk) => {\n if (stream.writable) {\n stream.write(chunk)\n }\n })\n answerStream.on('end', resolve)\n answerStream.on('error', resolve)\n })\n }\n\n private startNextAgent() {\n this.agentIndex++\n if (this.agentIndex >= this.fallbackOptions.factories.length) {\n this.agentIndex = 0\n }\n\n const previousAgent = this.agent\n const isFirstAgent = previousAgent === null\n const agent = this.fallbackOptions.factories[this.agentIndex]()\n this.agent = agent\n\n // Share the conversation and tools between the fallback and the child agent.\n // Both are now portable (tools no longer bind to a specific instance), so a\n // single reference is shared, exactly like the conversation.\n if (isFirstAgent) {\n // Adopt the first agent's conversation and tools (keeps its system prompt)\n this.conversation = agent.conversation\n this.tools = agent.tools\n } else {\n // Keep the accumulated history, but use the new agent's system prompt\n if (agent.conversation[0]?.role === 'system') {\n this.conversation[0] = agent.conversation[0]\n }\n agent.conversation = this.conversation\n agent.tools = this.tools\n }\n\n // Forward events from the child agent\n agent.on('Message', this.onMessage)\n agent.on('CancelLastUserMessage', this.onCancelLastUserMessage)\n agent.on('SkipAnswer', this.onSkipAnswer)\n agent.on('EndCall', this.onEndCall)\n agent.on('ToolCall', this.onToolCall)\n\n // Destroy the previous agent (after moving the conversation over)\n previousAgent?.destroy()\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.agent && this.logger) {\n this.agent.logger = new Logger(this.agent.constructor.name)\n }\n }, 0)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n this.emit('Message', message)\n }\n\n private onCancelLastUserMessage = () => {\n this.emit('CancelLastUserMessage')\n }\n\n private onSkipAnswer = () => {\n this.emit('SkipAnswer')\n }\n\n private onEndCall = () => {\n this.emit('EndCall')\n }\n\n private onToolCall = (toolCall: MicdropToolCall) => {\n this.emit('ToolCall', toolCall)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Agent } from './Agent'\n\nexport class MockAgent extends Agent {\n private i = 0\n\n constructor() {\n super({ systemPrompt: '' })\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n const message = `Assistant Message ${this.i++}`\n this.addAssistantMessage(message)\n stream.write(message)\n }\n\n cancel() {}\n}\n","/**\n * Streaming linear-interpolation resampler for PCM16 mono audio.\n *\n * Works in both directions (up or downsampling). It is stateful: it handles\n * arbitrary byte boundaries (a network chunk can split a 16-bit sample) and\n * keeps the fractional sample position continuous across chunks, so feeding a\n * stream chunk by chunk yields the same result as resampling it in one go.\n *\n * Providers use it to bridge their own rate with the 16kHz PCM16 the Micdrop\n * client records and plays: OpenaiSTT (16kHz -> 24kHz, the GA Realtime API\n * requires >= 24kHz), OpenaiTTS and KokoroTTS (24kHz output -> 16kHz).\n */\nexport class Pcm16Resampler {\n private readonly step: number\n private leftover: Buffer<ArrayBufferLike> = Buffer.alloc(0)\n private pos = 0 // Fractional position into the first sample of the buffer\n\n constructor(inRate: number, outRate: number) {\n this.step = inRate / outRate\n }\n\n // Reset to the initial state, to resample a new independent stream\n // (e.g. resending buffered audio after a reconnection).\n reset() {\n this.leftover = Buffer.alloc(0)\n this.pos = 0\n }\n\n process(chunk: Buffer): Buffer {\n const buf = this.leftover.length\n ? Buffer.concat([this.leftover, chunk])\n : chunk\n const samples = Math.floor(buf.length / 2)\n\n // Need at least 2 samples to interpolate\n if (samples < 2) {\n this.leftover = buf\n return Buffer.alloc(0)\n }\n\n const out: number[] = []\n let p = this.pos\n while (Math.floor(p) + 1 < samples) {\n const i = Math.floor(p)\n const frac = p - i\n const s0 = buf.readInt16LE(i * 2)\n const s1 = buf.readInt16LE((i + 1) * 2)\n out.push(Math.round(s0 + (s1 - s0) * frac))\n p += this.step\n }\n\n // Keep the last still-needed sample (and any trailing odd byte) for the\n // next chunk, and carry the fractional position relative to it.\n const consumed = Math.floor(p)\n this.pos = p - consumed\n this.leftover = buf.subarray(consumed * 2)\n\n const result = Buffer.alloc(out.length * 2)\n for (let k = 0; k < out.length; k++) {\n result.writeInt16LE(out[k], k * 2)\n }\n return result\n }\n}\n","/**\n * Conversions between the PCM16 buffers exchanged with the Micdrop client and\n * the float samples that local speech models read and write.\n *\n * Both formats are mono. PCM16 is signed 16-bit little-endian, floats are in\n * the [-1, 1] range. Only the scale changes, the sample rate is left alone.\n */\n\nconst PCM16_MAX = 32767\nconst PCM16_MIN = -32768\n\n/** Turns float samples into a PCM16 buffer, clamping anything out of range. */\nexport function float32ToPcm16(samples: Float32Array): Buffer {\n const buffer = Buffer.alloc(samples.length * 2)\n for (let i = 0; i < samples.length; i++) {\n const scaled = Math.round(samples[i] * PCM16_MAX)\n const clamped = Math.max(PCM16_MIN, Math.min(PCM16_MAX, scaled))\n buffer.writeInt16LE(clamped, i * 2)\n }\n return buffer\n}\n\n/**\n * Turns a PCM16 buffer into float samples.\n *\n * A trailing odd byte is dropped: it is half of a sample whose other half has\n * not arrived, and a caller feeding whole utterances never produces one.\n */\nexport function pcm16ToFloat32(buffer: Buffer): Float32Array {\n const length = Math.floor(buffer.length / 2)\n const samples = new Float32Array(length)\n for (let i = 0; i < length; i++) {\n samples[i] = buffer.readInt16LE(i * 2) / PCM16_MAX\n }\n return samples\n}\n","import WebSocket from 'ws'\n\nexport enum MicdropErrorCode {\n BadRequest = 4400,\n Unauthorized = 4401,\n NotFound = 4404,\n}\n\nexport class MicdropError extends Error {\n code: number\n\n constructor(code: number, message: string) {\n super(message)\n this.code = code\n }\n}\n\nexport function handleError(socket: WebSocket, error: unknown) {\n if (error instanceof MicdropError) {\n socket.close(error.code, error.message)\n } else {\n console.error(error)\n socket.close(1011)\n }\n socket.terminate()\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Duplex, PassThrough, Readable } from 'stream'\nimport { WebSocket } from 'ws'\nimport type { Agent } from './agent'\nimport { Logger } from './Logger'\nimport type { STT } from './stt'\nimport type { TTS } from './tts'\nimport {\n MicdropCallSummary,\n MicdropClientCommands,\n MicdropConversationItem,\n MicdropServerCommands,\n} from './types'\n\nexport interface MicdropServerEvents {\n End: [MicdropCallSummary]\n UserAudio: [Buffer]\n AssistantAudio: [Buffer]\n}\n\nexport interface MicdropConfig {\n firstMessage?: string\n generateFirstMessage?: boolean\n agent: Agent\n stt: STT\n tts: TTS\n}\n\nexport class MicdropServer extends EventEmitter<MicdropServerEvents> {\n public socket: WebSocket | null = null\n public config: MicdropConfig | null = null\n public logger?: Logger\n\n private startTime = Date.now()\n private lastMessageSpeeched?: MicdropConversationItem\n\n // Queue system for operations\n private operationQueue: Array<() => Promise<void>> = []\n private isProcessingQueue = false\n\n // When user is speaking, we're streaming chunks for STT\n private currentUserStream?: Duplex\n private userSpeechChunks = 0\n\n constructor(socket: WebSocket, config: MicdropConfig) {\n super()\n this.socket = socket\n this.config = config\n this.log(`Call started`)\n\n // Setup STT\n this.config.stt.on('Transcript', this.onTranscriptSTT)\n\n // Setup TTS\n this.config.tts.on('Audio', this.onAudioTTS)\n\n // Setup agent\n this.config.agent.on('Message', (message) =>\n this.socket?.send(\n `${MicdropServerCommands.Message} ${JSON.stringify(message)}`\n )\n )\n this.config.agent.on('CancelLastUserMessage', () =>\n this.socket?.send(MicdropServerCommands.CancelLastUserMessage)\n )\n this.config.agent.on('SkipAnswer', () =>\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n )\n this.config.agent.on('EndCall', () =>\n this.socket?.send(MicdropServerCommands.EndCall)\n )\n this.config.agent.on('ToolCall', (toolCall) =>\n this.socket?.send(\n `${MicdropServerCommands.ToolCall} ${JSON.stringify(toolCall)}`\n )\n )\n\n // Assistant speaks first\n // Deferred so consumers (e.g. MicdropRecorder) can subscribe to agent\n // events before the first message is added to the conversation.\n queueMicrotask(() => this.sendFirstMessage())\n\n // Listen to events\n socket.on('close', this.onClose)\n socket.on('message', this.onMessage)\n }\n\n private log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n private async processQueue() {\n if (this.isProcessingQueue || this.operationQueue.length === 0) return\n\n this.isProcessingQueue = true\n\n while (this.operationQueue.length > 0) {\n const operation = this.operationQueue.shift()\n if (operation) {\n try {\n await operation()\n } catch (error) {\n this.log('Error processing queued operation:', error)\n }\n }\n }\n\n this.isProcessingQueue = false\n }\n\n private queueOperation(operation: () => Promise<void>) {\n this.operationQueue.push(operation)\n this.processQueue()\n }\n\n public cancel() {\n this.config?.tts.cancel()\n this.config?.agent.cancel()\n // Clear the queue\n this.operationQueue = []\n }\n\n private onClose = () => {\n if (!this.config) return\n this.log('Connection closed')\n const duration = Math.round((Date.now() - this.startTime) / 1000)\n\n // Destroy instances\n this.config.agent.destroy()\n this.config.stt.destroy()\n this.config.tts.destroy()\n\n // Emit End event\n this.emit('End', {\n conversation: this.config.agent.conversation,\n duration,\n })\n\n // Unset params\n this.socket = null\n this.config = null\n }\n\n private onMessage = async (message: Buffer) => {\n if (message.byteLength === 0) return\n if (!Buffer.isBuffer(message)) {\n this.log('Message is not a buffer')\n return\n }\n\n // Commands\n if (message.byteLength < 15) {\n const cmd = message.toString()\n this.log(`Command: ${cmd}`)\n\n if (cmd === MicdropClientCommands.StartSpeaking) {\n // User started speaking\n this.onStartSpeaking()\n } else if (cmd === MicdropClientCommands.Mute) {\n // User muted the call\n this.onMute()\n } else if (cmd === MicdropClientCommands.StopSpeaking) {\n // User stopped speaking\n this.onStopSpeaking()\n }\n }\n\n // Audio chunk\n else if (this.currentUserStream) {\n this.onUserAudio(message)\n }\n }\n\n private onUserAudio(chunk: Buffer) {\n this.log(`Received chunk (${chunk.byteLength} bytes)`)\n this.currentUserStream?.write(chunk)\n this.userSpeechChunks++\n this.emit('UserAudio', chunk)\n }\n\n private onMute() {\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.cancel()\n }\n\n private onStartSpeaking() {\n if (!this.config) return\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = new PassThrough()\n this.config.stt.transcribe(this.currentUserStream)\n this.cancel()\n }\n\n private onStopSpeaking() {\n const hasNoUserSpeech =\n !this.currentUserStream || this.userSpeechChunks === 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.userSpeechChunks = 0\n\n // If user is not speaking or no chunks were received, skip\n if (hasNoUserSpeech) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n const conversation = this.config?.agent.conversation\n const lastMessage = conversation?.[conversation.length - 1]\n if (\n lastMessage?.role === 'user' &&\n this.lastMessageSpeeched !== lastMessage\n ) {\n this.log(\n 'User stopped speaking and a transcript already exists, answering'\n )\n this.cancel()\n this.answer()\n }\n }\n\n private onTranscriptSTT = async (transcript: string) => {\n if (!this.config) return\n\n // Skip answer if transcript is empty\n if (transcript === '') {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n this.log(`User transcript: \"${transcript}\"`)\n this.config.agent.addUserMessage(transcript)\n\n // Answer if user stopped speaking\n if (!this.currentUserStream) {\n this.log('User stopped speaking, answering')\n this.cancel()\n this.answer()\n }\n }\n\n private onAudioTTS = (audio: Buffer) => {\n if (!this.socket) return\n this.log(`Send audio chunk (${audio.byteLength} bytes)`)\n this.socket.send(audio)\n this.emit('AssistantAudio', audio)\n }\n\n private sendFirstMessage() {\n if (!this.config) return\n if (this.config.firstMessage) {\n // Send first message\n this.config.agent.addAssistantMessage(this.config.firstMessage)\n this.speak(this.config.firstMessage)\n } else if (this.config.generateFirstMessage) {\n // Generate first message\n this.answer()\n } else {\n // Skip answer if no first message is provided\n // to avoid keeping the client in a processing state\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n }\n }\n\n public answer() {\n this.queueOperation(async () => {\n await this._answer()\n })\n }\n\n private async _answer() {\n if (!this.config) return\n\n // Prevent answering twice\n const lastMessage =\n this.config.agent.conversation[this.config.agent.conversation.length - 1]\n if (this.lastMessageSpeeched === lastMessage) {\n this.log('Already answered, skipping')\n return\n }\n this.lastMessageSpeeched = lastMessage\n\n try {\n // LLM: Generate answer\n const stream = this.config.agent.answer()\n\n // TTS: Generate answer audio, unless there is nothing to say.\n //\n // An answer can be skipped after the fact: a tool with skipAnswer, or an\n // onBeforeAnswer hook returning true, ends the stream without a word in\n // it. Handing that empty stream to the TTS opens a synthesis request for\n // nothing, and a provider that stamps each request (Gradium multiplexes\n // this way) then drops the audio of the sentence still playing, so a\n // skipped answer cuts the assistant off mid-word.\n if (await hasContent(stream)) {\n await this._speak(stream)\n }\n } catch (error) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n throw error\n }\n }\n\n // Run text-to-speech and send to client\n public speak(message: string | Readable) {\n this.queueOperation(async () => {\n await this._speak(message)\n })\n }\n\n private async _speak(message: string | Readable) {\n if (!this.socket || !this.config) return\n\n // Convert message to stream if needed\n let textStream: Readable\n if (typeof message === 'string') {\n const stream = new PassThrough()\n stream.write(message)\n stream.end()\n textStream = stream\n } else {\n textStream = message\n }\n\n // Run TTS\n this.config.tts.speak(textStream)\n }\n}\n\n/**\n * Resolves true as soon as the stream holds something to read, false if it ends\n * without ever carrying anything.\n *\n * The chunk read to find out is put back, so the consumer that follows sees the\n * whole stream from its first byte.\n */\nfunction hasContent(stream: Readable): Promise<boolean> {\n return new Promise((resolve) => {\n const onReadable = () => {\n const chunk = stream.read()\n if (chunk === null) return\n stream.unshift(chunk)\n done(true)\n }\n const onEnd = () => done(false)\n const done = (result: boolean) => {\n stream.off('readable', onReadable)\n stream.off('end', onEnd)\n resolve(result)\n }\n stream.on('readable', onReadable)\n stream.on('end', onEnd)\n })\n}\n","export enum MicdropClientCommands {\n StartSpeaking = 'StartSpeaking',\n StopSpeaking = 'StopSpeaking',\n Mute = 'Mute',\n}\n\nexport enum MicdropServerCommands {\n Message = 'Message',\n CancelLastUserMessage = 'CancelLastUserMessage',\n SkipAnswer = 'SkipAnswer',\n EndCall = 'EndCall',\n ToolCall = 'ToolCall',\n}\n\nexport interface MicdropCallSummary {\n conversation: MicdropConversation\n duration: number\n}\n\nexport type MicdropConversationItem =\n | MicdropConversationMessage\n | MicdropConversationToolCall\n | MicdropConversationToolResult\n\nexport type MicdropConversation = Array<MicdropConversationItem>\n\nexport type MicdropAnswerMetadata = {\n [key: string]: any\n}\n\nexport interface MicdropConversationMessage<\n Data extends MicdropAnswerMetadata = MicdropAnswerMetadata,\n> {\n role: 'system' | 'user' | 'assistant'\n content: string\n metadata?: Data\n}\n\nexport interface MicdropConversationToolCall {\n role: 'tool_call'\n toolCallId: string\n toolName: string\n parameters: string\n}\n\nexport interface MicdropConversationToolResult {\n role: 'tool_result'\n toolCallId: string\n toolName: string\n output: string\n}\n\nexport interface MicdropToolCall {\n name: string\n parameters: any\n output: any\n}\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n","import { EventEmitter } from 'eventemitter3'\nimport type { MicdropServer } from '../MicdropServer'\nimport type { MicdropConversationItem } from '../types'\nimport { Logger } from '../Logger'\n\nexport interface AudioMessage {\n buffer: Buffer\n messageIndex: number\n message: string\n role: 'user' | 'assistant'\n}\n\nexport interface MicdropRecorderEvents {\n AudioMessage: [AudioMessage]\n Complete: [AudioMessage[]]\n}\n\nexport class MicdropRecorder extends EventEmitter<MicdropRecorderEvents> {\n public logger?: Logger\n\n private audioMessages: AudioMessage[] = []\n private currentUserChunks: Buffer[] = []\n private currentAssistantChunks: Buffer[] = []\n private lastUserMessageIndex: number = -1\n private lastAssistantMessageIndex: number = -1\n\n constructor(private server: MicdropServer) {\n super()\n this.setupListeners()\n }\n\n private setupListeners() {\n // Listen to audio events from server\n this.server.on('UserAudio', this.onUserAudio)\n this.server.on('AssistantAudio', this.onAssistantAudio)\n this.server.on('End', this.onEnd)\n\n // Listen to message events from agent\n const agent = this.server.config?.agent\n if (agent) {\n agent.on('Message', this.onMessage)\n }\n }\n\n private onUserAudio = (chunk: Buffer) => {\n // Finalize or discard assistant audio when user starts speaking\n if (this.currentAssistantChunks.length > 0) {\n if (this.lastAssistantMessageIndex >= 0) {\n this.finalizeAssistantAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned assistant audio chunks')\n this.currentAssistantChunks = []\n }\n }\n\n this.log('Recording user audio chunk')\n this.currentUserChunks.push(chunk)\n }\n\n private onAssistantAudio = (chunk: Buffer) => {\n // Finalize or discard user audio when assistant starts speaking\n if (this.currentUserChunks.length > 0) {\n if (this.lastUserMessageIndex >= 0) {\n this.finalizeUserAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned user audio chunks')\n this.currentUserChunks = []\n }\n }\n\n this.log('Recording assistant audio chunk')\n this.currentAssistantChunks.push(chunk)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const messageIndex = conversation.length - 1\n\n if (message.role === 'user') {\n this.lastUserMessageIndex = messageIndex\n // User audio might already be complete, finalize if we have chunks\n // Audio chunks arrive BEFORE message, so we finalize when we know the message\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n } else if (message.role === 'assistant') {\n this.lastAssistantMessageIndex = messageIndex\n // Don't finalize assistant audio here - chunks can still arrive after message\n }\n }\n\n private finalizeUserAudio() {\n if (this.currentUserChunks.length === 0) return\n if (this.lastUserMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastUserMessageIndex]\n const buffer = Buffer.concat(this.currentUserChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastUserMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'user',\n }\n\n this.log(\n `Finalized user audio: ${buffer.length} bytes, message index ${this.lastUserMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentUserChunks = []\n this.lastUserMessageIndex = -1\n }\n\n private finalizeAssistantAudio() {\n if (this.currentAssistantChunks.length === 0) return\n if (this.lastAssistantMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastAssistantMessageIndex]\n const buffer = Buffer.concat(this.currentAssistantChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastAssistantMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'assistant',\n }\n\n this.log(\n `Finalized assistant audio: ${buffer.length} bytes, message index ${this.lastAssistantMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentAssistantChunks = []\n this.lastAssistantMessageIndex = -1\n }\n\n private onEnd = () => {\n // Finalize any remaining audio\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n if (this.currentAssistantChunks.length > 0) {\n this.finalizeAssistantAudio()\n }\n\n this.log(`Recording complete: ${this.audioMessages.length} audio messages`)\n this.emit('Complete', this.audioMessages)\n }\n\n public getAudioMessages(): AudioMessage[] {\n return [...this.audioMessages]\n }\n\n public destroy() {\n this.log('Destroyed')\n this.server.off('UserAudio', this.onUserAudio)\n this.server.off('AssistantAudio', this.onAssistantAudio)\n this.server.off('End', this.onEnd)\n\n const agent = this.server.config?.agent\n if (agent) {\n agent.off('Message', this.onMessage)\n }\n\n this.removeAllListeners()\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface STTEvents {\n Transcript: [string]\n Failed: [Buffer[]]\n}\n\nexport abstract class STT extends EventEmitter<STTEvents> {\n public logger?: Logger\n\n // Set stream of audio to transcribe\n abstract transcribe(audioStream: Readable): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n }\n}\n","import { STT } from './STT'\n\nexport class MockSTT extends STT {\n private i = 0\n\n async transcribe() {\n setTimeout(() => {\n this.emit('Transcript', `User Message ${this.i++}`)\n }, 300)\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { STT } from './STT'\nimport { Logger } from '..'\n\nexport interface FallbackSTTOptions {\n factories: Array<() => STT>\n}\n\nexport class FallbackSTT extends STT {\n private stt: STT | null = null\n private sttIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackSTTOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackSTT: No factories provided')\n }\n this.startNextSTT()\n }\n\n transcribe(audioStream: Readable) {\n this.stt?.transcribe(audioStream)\n }\n\n destroy() {\n super.destroy()\n this.stt?.destroy()\n this.stt = null\n this.sttIndex = -1\n }\n\n private startNextSTT() {\n this.sttIndex++\n if (this.sttIndex >= this.options.factories.length) {\n this.sttIndex = 0\n }\n this.stt?.destroy()\n this.stt = this.options.factories[this.sttIndex]()\n this.stt.on('Transcript', this.onTranscript)\n this.stt.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.stt && this.logger) {\n this.stt.logger = new Logger(this.stt.constructor.name)\n }\n }, 0)\n }\n\n private onTranscript = (transcript: string) => {\n this.emit('Transcript', transcript)\n }\n\n private onFailed = (chunks: Buffer[]) => {\n this.log('STT failed, trying next STT')\n this.startNextSTT()\n\n if (chunks.length > 0) {\n this.log('Sending audio chunks again')\n const stream = new PassThrough()\n this.stt?.transcribe(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\nimport { Logger } from '..'\n\nexport interface FallbackTTSOptions {\n factories: Array<() => TTS>\n}\n\nexport class FallbackTTS extends TTS {\n private tts: TTS | null = null\n private ttsIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackTTSOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackTTS: No factories provided')\n }\n this.startNextTTS()\n }\n\n speak(textStream: Readable) {\n this.tts?.speak(textStream)\n }\n\n cancel() {\n this.tts?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.tts?.destroy()\n this.tts = null\n this.ttsIndex = -1\n }\n\n private startNextTTS() {\n this.ttsIndex++\n if (this.ttsIndex >= this.options.factories.length) {\n this.ttsIndex = 0\n }\n this.tts?.destroy()\n this.tts = this.options.factories[this.ttsIndex]()\n this.tts.on('Audio', this.onAudio)\n this.tts.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.tts && this.logger) {\n this.tts.logger = new Logger(this.tts.constructor.name)\n }\n }, 0)\n }\n\n private onAudio = (audio: Buffer) => {\n this.emit('Audio', audio)\n }\n\n private onFailed = (chunks: string[]) => {\n this.log('TTS failed, trying next TTS')\n this.startNextTTS()\n\n if (chunks.length > 0) {\n this.log('Sending text chunks again')\n const stream = new PassThrough()\n this.tts?.speak(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface TTSEvents {\n Audio: [Buffer]\n Failed: [string[]]\n}\n\nexport abstract class TTS extends EventEmitter<TTSEvents> {\n public logger?: Logger\n\n abstract speak(textStream: Readable): void\n abstract cancel(): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.cancel()\n }\n}\n","import * as fs from 'fs'\nimport { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\n\nexport class MockTTS extends TTS {\n constructor(private audioFilePaths: string[]) {\n super()\n }\n\n speak(textStream: Readable) {\n const audioStream = new PassThrough()\n textStream.once('data', async () => {\n for (const filePath of this.audioFilePaths) {\n await new Promise((resolve) => setTimeout(resolve, 200))\n const audioBuffer = fs.readFileSync(filePath)\n this.log(`Loaded chunk (${audioBuffer.length} bytes)`)\n audioStream.write(audioBuffer)\n }\n audioStream.end()\n })\n return audioStream\n }\n\n cancel() {}\n}\n","/**\n * Cuts a stream of text into sentences as it arrives.\n *\n * Providers that synthesize a whole input at once need complete sentences, and\n * an agent writes its answer token by token. Feeding every fragment as it comes\n * would either cut words in half or wait for the end of the answer, so the text\n * is buffered until a sentence closes and released the moment it does.\n *\n * The splitter is stateful: `push` returns the sentences that are complete,\n * `flush` returns whatever is left when the stream ends.\n */\nexport class SentenceSplitter {\n private buffer = ''\n\n /** Adds text and returns the sentences it completes. */\n push(text: string): string[] {\n this.buffer += text\n return this.extract(false)\n }\n\n /** Returns the sentences left in the buffer and empties it. */\n flush(): string[] {\n const sentences = this.extract(true)\n const rest = this.buffer.trim()\n this.buffer = ''\n if (rest) sentences.push(rest)\n return sentences\n }\n\n /** Drops the buffered text, used when an utterance is cancelled. */\n reset() {\n this.buffer = ''\n }\n\n private extract(end: boolean): string[] {\n const sentences: string[] = []\n const regex = /[\\s\\S]*?[.!?…\\n]+(?=\\s|$)/g\n let match: RegExpExecArray | null\n let lastIndex = 0\n\n while ((match = regex.exec(this.buffer)) !== null) {\n // A sentence ending at the very end of an unfinished stream may still\n // grow, so keep it buffered until more text arrives or the stream ends.\n if (!end && regex.lastIndex === this.buffer.length) break\n const sentence = match[0].trim()\n if (sentence) sentences.push(sentence)\n lastIndex = regex.lastIndex\n }\n\n this.buffer = this.buffer.slice(lastIndex)\n return sentences\n }\n}\n","import { Readable } from 'stream'\nimport { SentenceSplitter } from './SentenceSplitter'\nimport { TTS } from './TTS'\n\n/**\n * Base class for text to speech engines that read a whole input at once.\n *\n * A local model, and a remote endpoint without a streaming interface, cannot\n * be fed the agent's answer token by token. This class buffers the answer into\n * sentences, hands them over one at a time, and emits the audio in the order\n * they were written. Subclasses only have to turn one sentence into PCM16 at\n * the rate the Micdrop client expects.\n *\n * Sentences are synthesized one after the other rather than at once: a local\n * model is single threaded, so racing two sentences through it slows both down\n * without bringing the first word any closer.\n */\nexport abstract class SentenceTTS extends TTS {\n private splitter = new SentenceSplitter()\n private queue: string[] = []\n private draining = false\n private controller?: AbortController\n // Bumped by every speak() and every cancel(), so a call claimed late can tell\n // whether it is still the one that should be heard.\n private generation = 0\n private counter = 0 // Identifies the current speak() call\n private synthesizing = 0 // Stamp of the sentence being synthesized\n\n /**\n * Turns one sentence into PCM16 audio at the client's sample rate.\n *\n * The signal is aborted when the utterance is cancelled, which is the moment\n * to stop a subprocess or an inference that is no longer needed. Returning\n * nothing emits nothing, which is how a cancelled synthesis reports back.\n */\n protected abstract synthesize(\n text: string,\n signal: AbortSignal\n ): Promise<Buffer | undefined>\n\n /**\n * Emits a piece of the sentence being synthesized.\n *\n * A model that generates progressively can hand its chunks over as they\n * come rather than waiting for the sentence to be finished, which brings\n * the first word forward by the duration of that sentence. The false it\n * returns says the utterance was cancelled or replaced, so the generation\n * it comes from can be stopped there.\n */\n protected emitAudio(audio: Buffer): boolean {\n if (this.synthesizing !== this.counter) return false\n if (audio.length) this.emit('Audio', audio)\n return true\n }\n\n speak(textStream: Readable) {\n const generation = ++this.generation\n let counter = 0\n\n // Claiming the call is deferred until there is something to say.\n //\n // Taking the next number right away would drop the utterance still being\n // spoken, since the queue skips anything stamped with an older one. A\n // stream that never carries a word, which is what an answer skipped by a\n // tool or by onBeforeAnswer hands over, would then cut the assistant off\n // and throw away the sentences still queued.\n const claimCall = () => {\n if (counter) return true\n // Cancelled, or superseded by another speak(), before the first word\n if (this.generation !== generation) return false\n this.counter++\n counter = this.counter\n this.splitter.reset()\n return true\n }\n\n textStream.on('data', (chunk: Buffer) => {\n if (!claimCall()) return\n if (counter !== this.counter) return\n this.enqueue(counter, this.splitter.push(chunk.toString('utf-8')))\n })\n\n textStream.on('error', (error) => {\n this.log('Error in text stream', error)\n })\n\n textStream.on('end', () => {\n // Nothing was ever said, so there is nothing left to flush\n if (!counter || counter !== this.counter) return\n this.enqueue(counter, this.splitter.flush())\n })\n }\n\n cancel() {\n this.log('Cancel')\n this.generation++\n // Increment counter to ignore queued work and the sentence in flight\n this.counter++\n this.splitter.reset()\n this.queue = []\n this.controller?.abort()\n this.controller = undefined\n }\n\n private enqueue(counter: number, sentences: string[]) {\n if (sentences.length === 0) return\n if (counter !== this.counter) return\n this.queue.push(...sentences)\n this.drain()\n }\n\n private async drain() {\n if (this.draining) return\n this.draining = true\n\n while (this.queue.length > 0) {\n const counter = this.counter\n this.synthesizing = counter\n const text = this.queue.shift()!\n const controller = new AbortController()\n this.controller = controller\n\n try {\n this.log(`Synthesizing: \"${text}\"`)\n const audio = await this.synthesize(text, controller.signal)\n // The utterance may have been cancelled while it was being synthesized\n if (counter !== this.counter) continue\n if (audio?.length) this.emit('Audio', audio)\n } catch (error) {\n // A cancelled utterance is not a failure, it left its queue on purpose\n if (counter !== this.counter) continue\n this.log('Error synthesizing speech', error)\n this.emit('Failed', [text, ...this.queue])\n this.queue = []\n } finally {\n if (this.controller === controller) this.controller = undefined\n }\n }\n\n this.draining = false\n // Sentences may have arrived right as we exited the loop\n if (this.queue.length > 0) this.drain()\n }\n}\n","import { WebSocket } from 'ws'\nimport { MicdropError, MicdropErrorCode } from './errors'\n\nexport async function waitForParams<CallParams>(\n socket: WebSocket,\n validate: (params: any) => CallParams\n): Promise<CallParams> {\n return new Promise<CallParams>((resolve, reject) => {\n // Handle timeout\n const timeout = setTimeout(() => {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Missing params'))\n }, 3000)\n\n const onParams = (payload: string) => {\n // Clear timeout and listener\n clearTimeout(timeout)\n socket.off('message', onParams)\n\n try {\n // Parse JSON payload\n const params = validate(JSON.parse(payload))\n resolve(params)\n } catch (error) {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Invalid params'))\n }\n }\n\n // Listen for params\n socket.on('message', onParams)\n })\n}\n"],"mappings":"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","names":["MicdropErrorCode","import_eventemitter3","import_stream","MicdropClientCommands","MicdropServerCommands","import_eventemitter3","import_eventemitter3","import_stream","import_stream","import_eventemitter3","import_stream"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/agent/Agent.ts","../src/agent/tools.ts","../src/Logger.ts","../src/agent/FallbackAgent.ts","../src/agent/MockAgent.ts","../src/audio/Pcm16Resampler.ts","../src/audio/pcm16.ts","../src/errors.ts","../src/MicdropServer.ts","../src/types.ts","../src/recorder/MicdropRecorder.ts","../src/stt/STT.ts","../src/stt/MockSTT.ts","../src/stt/FallbackSTT.ts","../src/tts/FallbackTTS.ts","../src/tts/TTS.ts","../src/tts/MockTTS.ts","../src/tts/SentenceSplitter.ts","../src/tts/SentenceTTS.ts","../src/waitForParams.ts"],"sourcesContent":["export * from './agent'\nexport * from './audio'\nexport * from './errors'\nexport * from './Logger'\nexport * from './MicdropServer'\nexport * from './recorder'\nexport * from './stt'\nexport * from './tts'\nexport * from './types'\nexport * from './waitForParams'\n","import { EventEmitter } from 'eventemitter3'\nimport { PassThrough, Readable, Writable } from 'stream'\nimport type { z } from 'zod'\nimport { Logger } from '../Logger'\nimport {\n MicdropAnswerMetadata,\n MicdropConversation,\n MicdropConversationItem,\n MicdropConversationMessage,\n MicdropConversationToolCall,\n MicdropConversationToolResult,\n MicdropToolCall,\n} from '../types'\nimport {\n AUTO_END_CALL_PROMPT,\n AUTO_END_CALL_TOOL_NAME,\n AUTO_IGNORE_USER_NOISE_PROMPT,\n AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n AUTO_SEMANTIC_TURN_PROMPT,\n AUTO_SEMANTIC_TURN_TOOL_NAME,\n Tool,\n} from './tools'\n\nexport interface AgentOptions {\n systemPrompt: string\n\n // Enable auto ending of the call when user asks to end the call\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoEndCall?: boolean | string\n\n // Enable detection of an incomplete sentence, and skip the answer (assistant waits)\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoSemanticTurn?: boolean | string\n\n // Ignore of the last user message when it's meaningless\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoIgnoreUserNoise?: boolean | string\n\n // Extract a value from the answer\n // Value must be at the end of the answer, in JSON or between tags\n extract?: ExtractJsonOptions | ExtractTagOptions\n\n // Function called before any answer is generated\n // Return true to skip generation\n onBeforeAnswer?: (\n this: Agent,\n stream: Writable\n ) => void | boolean | Promise<boolean>\n}\n\nexport interface AgentEvents {\n Message: [MicdropConversationItem]\n CancelLastUserMessage: []\n SkipAnswer: []\n EndCall: []\n ToolCall: [MicdropToolCall]\n // Emitted when the agent gives up generating an answer (e.g. after exhausting\n // its retries). Used by FallbackAgent to switch to the next agent.\n Failed: []\n}\n\nexport interface ExtractOptions {\n callback?: (value: string) => void\n saveInMetadata?: boolean\n}\n\nexport interface ExtractJsonOptions extends ExtractOptions {\n json: true\n callback?: (value: any) => void\n}\n\nexport interface ExtractTagOptions extends ExtractOptions {\n startTag: string\n endTag: string\n}\n\nexport abstract class Agent<\n Options extends AgentOptions = AgentOptions,\n> extends EventEmitter<AgentEvents> {\n public logger?: Logger\n public conversation: MicdropConversation\n public tools: Tool[]\n\n protected answerCount = 0\n protected answering = false\n\n constructor(protected options: Options) {\n super()\n this.conversation = [{ role: 'system', content: options.systemPrompt }]\n this.tools = this.getDefaultTools()\n }\n\n protected abstract generateAnswer(stream: PassThrough): Promise<void>\n abstract cancel(): void\n\n answer(): Readable {\n this.log('Start answering')\n const answerCount = ++this.answerCount\n const stream = new PassThrough()\n this.answering = true\n\n Promise.resolve()\n // Call hook onBeforeAnswer\n .then(() => this.options.onBeforeAnswer?.bind(this)(stream))\n // Generate answer (if not skipped)\n .then((skip) => {\n if (skip) return\n return this.generateAnswer(stream)\n })\n // End stream\n .finally(() => {\n if (stream.writable) {\n stream.end()\n }\n if (answerCount === this.answerCount) {\n this.answering = false\n }\n })\n\n return stream\n }\n\n addUserMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('user', text, metadata)\n }\n\n addAssistantMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('assistant', text, metadata)\n }\n\n addTool<Schema extends z.ZodObject>(tool: Tool<Schema>) {\n this.tools.push(tool)\n }\n\n removeTool(name: string) {\n const index = this.tools.findIndex((tool) => tool.name === name)\n if (index !== -1) {\n this.tools.splice(index, 1)\n }\n }\n\n getTool(name: string): Tool | undefined {\n return this.tools.find((tool) => tool.name === name)\n }\n\n addMessage(\n role: 'user' | 'assistant' | 'system',\n text: string,\n metadata?: MicdropAnswerMetadata\n ) {\n // A turn can carry no text at all, typically when the LLM answered with a\n // tool call only. Keeping it would send an empty message back to the LLM on\n // the next turn, and emit a Message event that consumers store as an empty\n // exchange in their transcripts.\n if (text.trim() === '') {\n this.log(`Skipping empty ${role} message`)\n return\n }\n\n this.log(`Adding ${role} message to conversation: ${text}`)\n const message: MicdropConversationMessage = {\n role,\n content: text,\n metadata,\n }\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n addToolMessage(\n message: MicdropConversationToolCall | MicdropConversationToolResult\n ) {\n this.log('Adding tool message:', message)\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n protected endCall() {\n this.log('Ending call')\n this.emit('EndCall')\n }\n\n protected cancelLastUserMessage() {\n this.log('Cancelling last user message')\n const lastMessageIndex = this.conversation.findLastIndex(\n (message) => message.role === 'user'\n )\n if (lastMessageIndex !== -1) {\n this.conversation.splice(lastMessageIndex, 1)\n }\n this.emit('CancelLastUserMessage')\n }\n\n protected skipAnswer() {\n this.log('Skipping answer')\n this.emit('SkipAnswer')\n }\n\n protected getDefaultTools() {\n const tools: Tool[] = []\n if (this.options.autoEndCall) {\n tools.push({\n name: AUTO_END_CALL_TOOL_NAME,\n description:\n typeof this.options.autoEndCall === 'string'\n ? this.options.autoEndCall\n : AUTO_END_CALL_PROMPT,\n execute: (_input, agent) => agent.endCall(),\n })\n }\n if (this.options.autoSemanticTurn) {\n tools.push({\n name: AUTO_SEMANTIC_TURN_TOOL_NAME,\n description:\n typeof this.options.autoSemanticTurn === 'string'\n ? this.options.autoSemanticTurn\n : AUTO_SEMANTIC_TURN_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.skipAnswer(),\n })\n }\n if (this.options.autoIgnoreUserNoise) {\n tools.push({\n name: AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n description:\n typeof this.options.autoIgnoreUserNoise === 'string'\n ? this.options.autoIgnoreUserNoise\n : AUTO_IGNORE_USER_NOISE_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.cancelLastUserMessage(),\n })\n }\n return tools\n }\n\n protected async executeTool(toolCall: MicdropConversationToolCall) {\n try {\n const tool = this.getTool(toolCall.toolName)\n if (!tool) {\n throw new Error(`Tool not found \"${toolCall.toolName}\"`)\n }\n\n this.log('Executing tool:', toolCall.toolName, toolCall.parameters)\n\n // Save tool call in conversation\n this.addToolMessage(toolCall)\n\n const parameters = JSON.parse(toolCall.parameters)\n const output = tool.execute ? await tool.execute(parameters, this) : {}\n\n // Save tool result in conversation\n this.addToolMessage({\n role: 'tool_result',\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n output: JSON.stringify(output ?? null),\n })\n\n // Emit output\n if (tool.emitOutput) {\n this.emit('ToolCall', {\n name: toolCall.toolName,\n parameters,\n output,\n })\n }\n\n return {\n output,\n skipAnswer: tool.skipAnswer,\n }\n } catch (error: any) {\n console.error('[OpenaiAgent] Error executing tool:', error)\n return {\n output: {\n error: error.message,\n },\n }\n }\n }\n\n protected getExtractOptions(): ExtractTagOptions | undefined {\n const extract = this.options.extract\n if (!extract) return undefined\n if ('json' in extract && extract.json) {\n return { ...extract, startTag: '{', endTag: '}' }\n }\n if ('startTag' in extract && 'endTag' in extract) {\n return extract\n }\n return undefined\n }\n\n public extract(message: string) {\n const extractOptions = this.getExtractOptions()\n let metadata: MicdropAnswerMetadata | undefined = undefined\n\n // Extract value?\n if (extractOptions) {\n const startTagIndex = message.indexOf(extractOptions.startTag)\n if (startTagIndex !== -1) {\n // Find end tag\n let endTagIndex = message.lastIndexOf(extractOptions.endTag)\n if (endTagIndex === -1) endTagIndex = message.length + 1\n else endTagIndex += extractOptions.endTag.length\n const extractedText = message.slice(startTagIndex, endTagIndex).trim()\n\n // Parse extracted value\n try {\n const extractedValue =\n 'json' in extractOptions && extractOptions.json\n ? JSON.parse(extractedText)\n : extractedText\n\n // Call callback\n if (extractOptions.callback) {\n extractOptions.callback(extractedValue)\n }\n\n // Save in metadata\n if (extractOptions.saveInMetadata) {\n metadata = { extracted: extractedValue }\n }\n } catch (error) {\n console.error(\n `[OpenaiAgent] Error parsing extracted value (${extractedText}):`,\n error\n )\n }\n\n // Remove extracted value from message\n message = message.slice(0, startTagIndex).trimEnd()\n }\n }\n return { message, metadata }\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n this.cancel()\n }\n}\n","import type { z } from 'zod'\nimport type { Agent } from './Agent'\n\nexport interface Tool<Schema extends z.ZodObject = z.ZodObject> {\n name: string\n description: string\n inputSchema?: Schema\n // The executing agent is passed as context so tools stay portable (no binding\n // to a specific agent instance), which lets them be shared between agents.\n execute?: (input: z.infer<Schema>, agent: Agent) => any | Promise<any>\n skipAnswer?: boolean\n emitOutput?: boolean\n}\n\nexport const AUTO_END_CALL_TOOL_NAME = 'end_call'\nexport const AUTO_END_CALL_PROMPT =\n 'Call this tool only if user asks to end the call'\n\nexport const AUTO_SEMANTIC_TURN_TOOL_NAME = 'semantic_turn'\nexport const AUTO_SEMANTIC_TURN_PROMPT =\n 'Call this tool only if last user message is obviously an incomplete sentence that you need to wait for the end before answering'\n\nexport const AUTO_IGNORE_USER_NOISE_TOOL_NAME = 'ignore_user_noise'\nexport const AUTO_IGNORE_USER_NOISE_PROMPT =\n 'Call this tool only if last user message is just an interjection or a sound that expresses emotion, hesitation, or reaction (ex: \"Uh\", \"Ahem\", \"Hmm\", \"Ah\") but doesn\\'t carry any clear meaning like agreeing, refusing, or commanding'\n","export class Logger {\n constructor(public name: string) {}\n\n log(...message: any[]) {\n const time = process.uptime().toFixed(3)\n console.log(`[${this.name} ${time}]`, ...message)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Logger } from '../Logger'\nimport { MicdropConversationItem, MicdropToolCall } from '../types'\nimport { Agent } from './Agent'\n\nexport interface FallbackAgentOptions {\n factories: Array<() => Agent>\n}\n\nexport class FallbackAgent extends Agent {\n private agent: Agent | null = null\n private agentIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly fallbackOptions: FallbackAgentOptions) {\n super({ systemPrompt: '' })\n if (this.fallbackOptions.factories.length === 0) {\n throw new Error('FallbackAgent: No factories provided')\n }\n this.startNextAgent()\n }\n\n // Delegate extraction to the active agent (extract config lives on children)\n extract(message: string) {\n return this.agent ? this.agent.extract(message) : super.extract(message)\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n // Try each agent once (one full rotation) until one answers successfully.\n // The conversation is shared between agents, so the next agent picks up\n // exactly where the failed one stopped.\n for (\n let attempt = 0;\n attempt < this.fallbackOptions.factories.length;\n attempt++\n ) {\n const agent = this.agent\n if (!agent) return\n\n let failed = false\n const onFailed = () => {\n failed = true\n }\n agent.once('Failed', onFailed)\n\n try {\n await this.pipeAnswer(agent, stream)\n } finally {\n agent.off('Failed', onFailed)\n }\n\n if (!failed) return\n\n this.log('Agent failed, trying next agent')\n this.startNextAgent()\n }\n\n // Every agent failed within this rotation: report it so an outer consumer\n // (e.g. a wrapping FallbackAgent) can react.\n this.log('All agents failed')\n this.emit('Failed')\n }\n\n cancel() {\n this.agent?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.agent?.destroy()\n this.agent = null\n this.agentIndex = -1\n }\n\n // Run the child agent and forward its answer chunks to our own stream\n private pipeAnswer(agent: Agent, stream: PassThrough): Promise<void> {\n return new Promise((resolve) => {\n const answerStream = agent.answer()\n answerStream.on('data', (chunk) => {\n if (stream.writable) {\n stream.write(chunk)\n }\n })\n answerStream.on('end', resolve)\n answerStream.on('error', resolve)\n })\n }\n\n private startNextAgent() {\n this.agentIndex++\n if (this.agentIndex >= this.fallbackOptions.factories.length) {\n this.agentIndex = 0\n }\n\n const previousAgent = this.agent\n const isFirstAgent = previousAgent === null\n const agent = this.fallbackOptions.factories[this.agentIndex]()\n this.agent = agent\n\n // Share the conversation and tools between the fallback and the child agent.\n // Both are now portable (tools no longer bind to a specific instance), so a\n // single reference is shared, exactly like the conversation.\n if (isFirstAgent) {\n // Adopt the first agent's conversation and tools (keeps its system prompt)\n this.conversation = agent.conversation\n this.tools = agent.tools\n } else {\n // Keep the accumulated history, but use the new agent's system prompt\n if (agent.conversation[0]?.role === 'system') {\n this.conversation[0] = agent.conversation[0]\n }\n agent.conversation = this.conversation\n agent.tools = this.tools\n }\n\n // Forward events from the child agent\n agent.on('Message', this.onMessage)\n agent.on('CancelLastUserMessage', this.onCancelLastUserMessage)\n agent.on('SkipAnswer', this.onSkipAnswer)\n agent.on('EndCall', this.onEndCall)\n agent.on('ToolCall', this.onToolCall)\n\n // Destroy the previous agent (after moving the conversation over)\n previousAgent?.destroy()\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.agent && this.logger) {\n this.agent.logger = new Logger(this.agent.constructor.name)\n }\n }, 0)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n this.emit('Message', message)\n }\n\n private onCancelLastUserMessage = () => {\n this.emit('CancelLastUserMessage')\n }\n\n private onSkipAnswer = () => {\n this.emit('SkipAnswer')\n }\n\n private onEndCall = () => {\n this.emit('EndCall')\n }\n\n private onToolCall = (toolCall: MicdropToolCall) => {\n this.emit('ToolCall', toolCall)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Agent } from './Agent'\n\nexport class MockAgent extends Agent {\n private i = 0\n\n constructor() {\n super({ systemPrompt: '' })\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n const message = `Assistant Message ${this.i++}`\n this.addAssistantMessage(message)\n stream.write(message)\n }\n\n cancel() {}\n}\n","/**\n * Streaming linear-interpolation resampler for PCM16 mono audio.\n *\n * Works in both directions (up or downsampling). It is stateful: it handles\n * arbitrary byte boundaries (a network chunk can split a 16-bit sample) and\n * keeps the fractional sample position continuous across chunks, so feeding a\n * stream chunk by chunk yields the same result as resampling it in one go.\n *\n * Providers use it to bridge their own rate with the 16kHz PCM16 the Micdrop\n * client records and plays: OpenaiSTT (16kHz -> 24kHz, the GA Realtime API\n * requires >= 24kHz), OpenaiTTS and KokoroTTS (24kHz output -> 16kHz).\n */\nexport class Pcm16Resampler {\n private readonly step: number\n private leftover: Buffer<ArrayBufferLike> = Buffer.alloc(0)\n private pos = 0 // Fractional position into the first sample of the buffer\n\n constructor(inRate: number, outRate: number) {\n this.step = inRate / outRate\n }\n\n // Reset to the initial state, to resample a new independent stream\n // (e.g. resending buffered audio after a reconnection).\n reset() {\n this.leftover = Buffer.alloc(0)\n this.pos = 0\n }\n\n process(chunk: Buffer): Buffer {\n const buf = this.leftover.length\n ? Buffer.concat([this.leftover, chunk])\n : chunk\n const samples = Math.floor(buf.length / 2)\n\n // Need at least 2 samples to interpolate\n if (samples < 2) {\n this.leftover = buf\n return Buffer.alloc(0)\n }\n\n const out: number[] = []\n let p = this.pos\n while (Math.floor(p) + 1 < samples) {\n const i = Math.floor(p)\n const frac = p - i\n const s0 = buf.readInt16LE(i * 2)\n const s1 = buf.readInt16LE((i + 1) * 2)\n out.push(Math.round(s0 + (s1 - s0) * frac))\n p += this.step\n }\n\n // Keep the last still-needed sample (and any trailing odd byte) for the\n // next chunk, and carry the fractional position relative to it.\n const consumed = Math.floor(p)\n this.pos = p - consumed\n this.leftover = buf.subarray(consumed * 2)\n\n const result = Buffer.alloc(out.length * 2)\n for (let k = 0; k < out.length; k++) {\n result.writeInt16LE(out[k], k * 2)\n }\n return result\n }\n}\n","/**\n * Conversions between the PCM16 buffers exchanged with the Micdrop client and\n * the float samples that local speech models read and write.\n *\n * Both formats are mono. PCM16 is signed 16-bit little-endian, floats are in\n * the [-1, 1] range. Only the scale changes, the sample rate is left alone.\n */\n\nconst PCM16_MAX = 32767\nconst PCM16_MIN = -32768\n\n/** Turns float samples into a PCM16 buffer, clamping anything out of range. */\nexport function float32ToPcm16(samples: Float32Array): Buffer {\n const buffer = Buffer.alloc(samples.length * 2)\n for (let i = 0; i < samples.length; i++) {\n const scaled = Math.round(samples[i] * PCM16_MAX)\n const clamped = Math.max(PCM16_MIN, Math.min(PCM16_MAX, scaled))\n buffer.writeInt16LE(clamped, i * 2)\n }\n return buffer\n}\n\n/**\n * Turns a PCM16 buffer into float samples.\n *\n * A trailing odd byte is dropped: it is half of a sample whose other half has\n * not arrived, and a caller feeding whole utterances never produces one.\n */\nexport function pcm16ToFloat32(buffer: Buffer): Float32Array {\n const length = Math.floor(buffer.length / 2)\n const samples = new Float32Array(length)\n for (let i = 0; i < length; i++) {\n samples[i] = buffer.readInt16LE(i * 2) / PCM16_MAX\n }\n return samples\n}\n","import WebSocket from 'ws'\n\nexport enum MicdropErrorCode {\n BadRequest = 4400,\n Unauthorized = 4401,\n NotFound = 4404,\n}\n\nexport class MicdropError extends Error {\n code: number\n\n constructor(code: number, message: string) {\n super(message)\n this.code = code\n }\n}\n\nexport function handleError(socket: WebSocket, error: unknown) {\n if (error instanceof MicdropError) {\n socket.close(error.code, error.message)\n } else {\n console.error(error)\n socket.close(1011)\n }\n socket.terminate()\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Duplex, PassThrough, Readable } from 'stream'\nimport { WebSocket } from 'ws'\nimport type { Agent } from './agent'\nimport { pcm16ToFloat32 } from './audio'\nimport { Logger } from './Logger'\nimport type { STT } from './stt'\nimport type { TTS } from './tts'\nimport {\n MicdropCallSummary,\n MicdropClientCommands,\n MicdropConversationItem,\n MicdropServerCommands,\n TurnDetector,\n} from './types'\n\n/** Rate the client records at, and sends its chunks in */\nconst USER_SAMPLE_RATE = 16000\n\n/**\n * How long an answer waits once the detector asked for the rest of a sentence.\n *\n * The way out of a wrong verdict: a speaker who never comes back still gets an\n * answer instead of a call that goes quiet.\n */\nconst DEFAULT_TURN_MAX_WAIT = 4000 // ms\n\nexport interface MicdropServerEvents {\n End: [MicdropCallSummary]\n UserAudio: [Buffer]\n AssistantAudio: [Buffer]\n}\n\nexport interface MicdropConfig {\n firstMessage?: string\n generateFirstMessage?: boolean\n agent: Agent\n stt: STT\n tts: TTS\n\n /**\n * Waits for the rest of the sentence when the speaker paused in the middle\n * of one, instead of answering an unfinished question.\n *\n * Prefer running the detector in the client, which reaches the same decision\n * without the round trip and can then close its turns sooner. This is the\n * option for the browsers where the model has nowhere to run.\n */\n turnDetector?: TurnDetector\n\n /** How long to wait for the rest of a sentence, 4000 ms by default */\n turnMaxWait?: number\n}\n\nexport class MicdropServer extends EventEmitter<MicdropServerEvents> {\n public socket: WebSocket | null = null\n public config: MicdropConfig | null = null\n public logger?: Logger\n\n private startTime = Date.now()\n private lastMessageSpeeched?: MicdropConversationItem\n\n // Queue system for operations\n private operationQueue: Array<() => Promise<void>> = []\n private isProcessingQueue = false\n\n // When user is speaking, we're streaming chunks for STT\n private currentUserStream?: Duplex\n private userSpeechChunks = 0\n // Asked as soon as the speaker pauses, so it weighs that stretch of audio\n // and not the next one\n private turnComplete?: Promise<boolean>\n private heldTurnTimer?: ReturnType<typeof setTimeout>\n\n constructor(socket: WebSocket, config: MicdropConfig) {\n super()\n this.socket = socket\n this.config = config\n this.log(`Call started`)\n\n // Setup STT\n this.config.stt.on('Transcript', this.onTranscriptSTT)\n\n // Setup TTS\n this.config.tts.on('Audio', this.onAudioTTS)\n\n // Setup agent\n this.config.agent.on('Message', (message) =>\n this.socket?.send(\n `${MicdropServerCommands.Message} ${JSON.stringify(message)}`\n )\n )\n this.config.agent.on('CancelLastUserMessage', () =>\n this.socket?.send(MicdropServerCommands.CancelLastUserMessage)\n )\n this.config.agent.on('SkipAnswer', () =>\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n )\n this.config.agent.on('EndCall', () =>\n this.socket?.send(MicdropServerCommands.EndCall)\n )\n this.config.agent.on('ToolCall', (toolCall) =>\n this.socket?.send(\n `${MicdropServerCommands.ToolCall} ${JSON.stringify(toolCall)}`\n )\n )\n\n // Assistant speaks first\n // Deferred so consumers (e.g. MicdropRecorder) can subscribe to agent\n // events before the first message is added to the conversation.\n queueMicrotask(() => this.sendFirstMessage())\n\n // Listen to events\n socket.on('close', this.onClose)\n socket.on('message', this.onMessage)\n }\n\n private log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n private async processQueue() {\n if (this.isProcessingQueue || this.operationQueue.length === 0) return\n\n this.isProcessingQueue = true\n\n while (this.operationQueue.length > 0) {\n const operation = this.operationQueue.shift()\n if (operation) {\n try {\n await operation()\n } catch (error) {\n this.log('Error processing queued operation:', error)\n }\n }\n }\n\n this.isProcessingQueue = false\n }\n\n private queueOperation(operation: () => Promise<void>) {\n this.operationQueue.push(operation)\n this.processQueue()\n }\n\n public cancel() {\n this.config?.tts.cancel()\n this.config?.agent.cancel()\n // Clear the queue\n this.operationQueue = []\n }\n\n private onClose = () => {\n this.releaseHeldTurn()\n if (!this.config) return\n this.log('Connection closed')\n const duration = Math.round((Date.now() - this.startTime) / 1000)\n\n // Destroy instances\n this.config.agent.destroy()\n this.config.stt.destroy()\n this.config.tts.destroy()\n\n // Emit End event\n this.emit('End', {\n conversation: this.config.agent.conversation,\n duration,\n })\n\n // Unset params\n this.socket = null\n this.config = null\n }\n\n private onMessage = async (message: Buffer) => {\n if (message.byteLength === 0) return\n if (!Buffer.isBuffer(message)) {\n this.log('Message is not a buffer')\n return\n }\n\n // Commands\n if (message.byteLength < 15) {\n const cmd = message.toString()\n this.log(`Command: ${cmd}`)\n\n if (cmd === MicdropClientCommands.StartSpeaking) {\n // User started speaking\n this.onStartSpeaking()\n } else if (cmd === MicdropClientCommands.Mute) {\n // User muted the call\n this.onMute()\n } else if (cmd === MicdropClientCommands.StopSpeaking) {\n // User stopped speaking\n this.onStopSpeaking()\n }\n }\n\n // Audio chunk\n else if (this.currentUserStream) {\n this.onUserAudio(message)\n }\n }\n\n private onUserAudio(chunk: Buffer) {\n this.log(`Received chunk (${chunk.byteLength} bytes)`)\n this.currentUserStream?.write(chunk)\n this.userSpeechChunks++\n this.config?.turnDetector?.push(pcm16ToFloat32(chunk), USER_SAMPLE_RATE)\n this.emit('UserAudio', chunk)\n }\n\n private onMute() {\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.cancel()\n }\n\n private onStartSpeaking() {\n if (!this.config) return\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = new PassThrough()\n this.config.turnDetector?.reset()\n this.turnComplete = undefined\n // The rest of the sentence is arriving, so the deadline can go\n this.releaseHeldTurn()\n this.config.stt.transcribe(this.currentUserStream)\n this.cancel()\n }\n\n private onStopSpeaking() {\n const hasNoUserSpeech =\n !this.currentUserStream || this.userSpeechChunks === 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.userSpeechChunks = 0\n\n // If user is not speaking or no chunks were received, skip\n if (hasNoUserSpeech) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n // Weigh the stretch of audio that just ended, before the next one starts\n this.turnComplete = this.predictTurnComplete()\n\n const conversation = this.config?.agent.conversation\n const lastMessage = conversation?.[conversation.length - 1]\n if (\n lastMessage?.role === 'user' &&\n this.lastMessageSpeeched !== lastMessage\n ) {\n this.log(\n 'User stopped speaking and a transcript already exists, answering'\n )\n this.answerUserTurn()\n }\n }\n\n private async predictTurnComplete(): Promise<boolean> {\n const detector = this.config?.turnDetector\n if (!detector) return true\n try {\n const { complete } = await detector.predict()\n this.log(`Turn sounds ${complete ? 'finished' : 'unfinished'}`)\n return complete\n } catch (error) {\n this.log(`Turn detection failed: ${error}`)\n return true\n }\n }\n\n /** Answers, unless the sentence sounds like it has more coming */\n private async answerUserTurn() {\n const complete = await (this.turnComplete ?? Promise.resolve(true))\n if (!complete) {\n this.log('Waiting for the rest of the sentence')\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n this.holdTurn()\n return\n }\n this.releaseHeldTurn()\n this.cancel()\n this.answer()\n }\n\n /**\n * Answers anyway if the rest of the sentence never comes.\n *\n * Without it, a detector that hears an unfinished sentence where there is\n * none leaves the call silent for good.\n */\n private holdTurn() {\n this.releaseHeldTurn()\n this.heldTurnTimer = setTimeout(() => {\n this.heldTurnTimer = undefined\n this.log('Nothing more came, answering')\n this.cancel()\n this.answer()\n }, this.config?.turnMaxWait ?? DEFAULT_TURN_MAX_WAIT)\n }\n\n private releaseHeldTurn() {\n if (!this.heldTurnTimer) return\n clearTimeout(this.heldTurnTimer)\n this.heldTurnTimer = undefined\n }\n\n private onTranscriptSTT = async (transcript: string) => {\n if (!this.config) return\n\n // Skip answer if transcript is empty\n if (transcript === '') {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n this.log(`User transcript: \"${transcript}\"`)\n this.config.agent.addUserMessage(transcript)\n\n // Answer if user stopped speaking\n if (!this.currentUserStream) {\n this.log('User stopped speaking, answering')\n this.answerUserTurn()\n }\n }\n\n private onAudioTTS = (audio: Buffer) => {\n if (!this.socket) return\n this.log(`Send audio chunk (${audio.byteLength} bytes)`)\n this.socket.send(audio)\n this.emit('AssistantAudio', audio)\n }\n\n private sendFirstMessage() {\n if (!this.config) return\n if (this.config.firstMessage) {\n // Send first message\n this.config.agent.addAssistantMessage(this.config.firstMessage)\n this.speak(this.config.firstMessage)\n } else if (this.config.generateFirstMessage) {\n // Generate first message\n this.answer()\n } else {\n // Skip answer if no first message is provided\n // to avoid keeping the client in a processing state\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n }\n }\n\n public answer() {\n this.queueOperation(async () => {\n await this._answer()\n })\n }\n\n private async _answer() {\n if (!this.config) return\n\n // Prevent answering twice\n const lastMessage =\n this.config.agent.conversation[this.config.agent.conversation.length - 1]\n if (this.lastMessageSpeeched === lastMessage) {\n this.log('Already answered, skipping')\n return\n }\n this.lastMessageSpeeched = lastMessage\n\n try {\n // LLM: Generate answer\n const stream = this.config.agent.answer()\n\n // TTS: Generate answer audio, unless there is nothing to say.\n //\n // An answer can be skipped after the fact: a tool with skipAnswer, or an\n // onBeforeAnswer hook returning true, ends the stream without a word in\n // it. Handing that empty stream to the TTS opens a synthesis request for\n // nothing, and a provider that stamps each request (Gradium multiplexes\n // this way) then drops the audio of the sentence still playing, so a\n // skipped answer cuts the assistant off mid-word.\n if (await hasContent(stream)) {\n await this._speak(stream)\n }\n } catch (error) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n throw error\n }\n }\n\n // Run text-to-speech and send to client\n public speak(message: string | Readable) {\n this.queueOperation(async () => {\n await this._speak(message)\n })\n }\n\n private async _speak(message: string | Readable) {\n if (!this.socket || !this.config) return\n\n // Convert message to stream if needed\n let textStream: Readable\n if (typeof message === 'string') {\n const stream = new PassThrough()\n stream.write(message)\n stream.end()\n textStream = stream\n } else {\n textStream = message\n }\n\n // Run TTS\n this.config.tts.speak(textStream)\n }\n}\n\n/**\n * Resolves true as soon as the stream holds something to read, false if it ends\n * without ever carrying anything.\n *\n * The chunk read to find out is put back, so the consumer that follows sees the\n * whole stream from its first byte.\n */\nfunction hasContent(stream: Readable): Promise<boolean> {\n return new Promise((resolve) => {\n const onReadable = () => {\n const chunk = stream.read()\n if (chunk === null) return\n stream.unshift(chunk)\n done(true)\n }\n const onEnd = () => done(false)\n const done = (result: boolean) => {\n stream.off('readable', onReadable)\n stream.off('end', onEnd)\n resolve(result)\n }\n stream.on('readable', onReadable)\n stream.on('end', onEnd)\n })\n}\n","export enum MicdropClientCommands {\n StartSpeaking = 'StartSpeaking',\n StopSpeaking = 'StopSpeaking',\n Mute = 'Mute',\n}\n\nexport enum MicdropServerCommands {\n Message = 'Message',\n CancelLastUserMessage = 'CancelLastUserMessage',\n SkipAnswer = 'SkipAnswer',\n EndCall = 'EndCall',\n ToolCall = 'ToolCall',\n}\n\n/**\n * Hears whether a sentence has landed, where voice activity detection only\n * hears whether someone is speaking.\n *\n * `SmartTurn` from `@micdrop/smart-turn` implements it, and so can anything\n * else, a call to a service included. Both sides of a call can hold one, the\n * client to decide when its turn ends and the server to decide when to answer.\n */\nexport interface TurnDetector {\n /**\n * Feeds the audio received since the last call\n * @param samples - Mono samples, in the -1..1 range\n * @param sampleRate - Sample rate of `samples`, in Hz\n */\n push(samples: Float32Array, sampleRate?: number): void\n\n /** Answers whether the turn pushed so far sounds finished */\n predict(): Promise<{ complete: boolean }>\n\n /** Starts a new turn, forgetting the previous one */\n reset(): void\n}\n\nexport interface MicdropCallSummary {\n conversation: MicdropConversation\n duration: number\n}\n\nexport type MicdropConversationItem =\n | MicdropConversationMessage\n | MicdropConversationToolCall\n | MicdropConversationToolResult\n\nexport type MicdropConversation = Array<MicdropConversationItem>\n\nexport type MicdropAnswerMetadata = {\n [key: string]: any\n}\n\nexport interface MicdropConversationMessage<\n Data extends MicdropAnswerMetadata = MicdropAnswerMetadata,\n> {\n role: 'system' | 'user' | 'assistant'\n content: string\n metadata?: Data\n}\n\nexport interface MicdropConversationToolCall {\n role: 'tool_call'\n toolCallId: string\n toolName: string\n parameters: string\n}\n\nexport interface MicdropConversationToolResult {\n role: 'tool_result'\n toolCallId: string\n toolName: string\n output: string\n}\n\nexport interface MicdropToolCall {\n name: string\n parameters: any\n output: any\n}\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n","import { EventEmitter } from 'eventemitter3'\nimport type { MicdropServer } from '../MicdropServer'\nimport type { MicdropConversationItem } from '../types'\nimport { Logger } from '../Logger'\n\nexport interface AudioMessage {\n buffer: Buffer\n messageIndex: number\n message: string\n role: 'user' | 'assistant'\n}\n\nexport interface MicdropRecorderEvents {\n AudioMessage: [AudioMessage]\n Complete: [AudioMessage[]]\n}\n\nexport class MicdropRecorder extends EventEmitter<MicdropRecorderEvents> {\n public logger?: Logger\n\n private audioMessages: AudioMessage[] = []\n private currentUserChunks: Buffer[] = []\n private currentAssistantChunks: Buffer[] = []\n private lastUserMessageIndex: number = -1\n private lastAssistantMessageIndex: number = -1\n\n constructor(private server: MicdropServer) {\n super()\n this.setupListeners()\n }\n\n private setupListeners() {\n // Listen to audio events from server\n this.server.on('UserAudio', this.onUserAudio)\n this.server.on('AssistantAudio', this.onAssistantAudio)\n this.server.on('End', this.onEnd)\n\n // Listen to message events from agent\n const agent = this.server.config?.agent\n if (agent) {\n agent.on('Message', this.onMessage)\n }\n }\n\n private onUserAudio = (chunk: Buffer) => {\n // Finalize or discard assistant audio when user starts speaking\n if (this.currentAssistantChunks.length > 0) {\n if (this.lastAssistantMessageIndex >= 0) {\n this.finalizeAssistantAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned assistant audio chunks')\n this.currentAssistantChunks = []\n }\n }\n\n this.log('Recording user audio chunk')\n this.currentUserChunks.push(chunk)\n }\n\n private onAssistantAudio = (chunk: Buffer) => {\n // Finalize or discard user audio when assistant starts speaking\n if (this.currentUserChunks.length > 0) {\n if (this.lastUserMessageIndex >= 0) {\n this.finalizeUserAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned user audio chunks')\n this.currentUserChunks = []\n }\n }\n\n this.log('Recording assistant audio chunk')\n this.currentAssistantChunks.push(chunk)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const messageIndex = conversation.length - 1\n\n if (message.role === 'user') {\n this.lastUserMessageIndex = messageIndex\n // User audio might already be complete, finalize if we have chunks\n // Audio chunks arrive BEFORE message, so we finalize when we know the message\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n } else if (message.role === 'assistant') {\n this.lastAssistantMessageIndex = messageIndex\n // Don't finalize assistant audio here - chunks can still arrive after message\n }\n }\n\n private finalizeUserAudio() {\n if (this.currentUserChunks.length === 0) return\n if (this.lastUserMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastUserMessageIndex]\n const buffer = Buffer.concat(this.currentUserChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastUserMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'user',\n }\n\n this.log(\n `Finalized user audio: ${buffer.length} bytes, message index ${this.lastUserMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentUserChunks = []\n this.lastUserMessageIndex = -1\n }\n\n private finalizeAssistantAudio() {\n if (this.currentAssistantChunks.length === 0) return\n if (this.lastAssistantMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastAssistantMessageIndex]\n const buffer = Buffer.concat(this.currentAssistantChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastAssistantMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'assistant',\n }\n\n this.log(\n `Finalized assistant audio: ${buffer.length} bytes, message index ${this.lastAssistantMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentAssistantChunks = []\n this.lastAssistantMessageIndex = -1\n }\n\n private onEnd = () => {\n // Finalize any remaining audio\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n if (this.currentAssistantChunks.length > 0) {\n this.finalizeAssistantAudio()\n }\n\n this.log(`Recording complete: ${this.audioMessages.length} audio messages`)\n this.emit('Complete', this.audioMessages)\n }\n\n public getAudioMessages(): AudioMessage[] {\n return [...this.audioMessages]\n }\n\n public destroy() {\n this.log('Destroyed')\n this.server.off('UserAudio', this.onUserAudio)\n this.server.off('AssistantAudio', this.onAssistantAudio)\n this.server.off('End', this.onEnd)\n\n const agent = this.server.config?.agent\n if (agent) {\n agent.off('Message', this.onMessage)\n }\n\n this.removeAllListeners()\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface STTEvents {\n Transcript: [string]\n Failed: [Buffer[]]\n}\n\nexport abstract class STT extends EventEmitter<STTEvents> {\n public logger?: Logger\n\n // Set stream of audio to transcribe\n abstract transcribe(audioStream: Readable): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n }\n}\n","import { STT } from './STT'\n\nexport class MockSTT extends STT {\n private i = 0\n\n async transcribe() {\n setTimeout(() => {\n this.emit('Transcript', `User Message ${this.i++}`)\n }, 300)\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { STT } from './STT'\nimport { Logger } from '..'\n\nexport interface FallbackSTTOptions {\n factories: Array<() => STT>\n}\n\nexport class FallbackSTT extends STT {\n private stt: STT | null = null\n private sttIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackSTTOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackSTT: No factories provided')\n }\n this.startNextSTT()\n }\n\n transcribe(audioStream: Readable) {\n this.stt?.transcribe(audioStream)\n }\n\n destroy() {\n super.destroy()\n this.stt?.destroy()\n this.stt = null\n this.sttIndex = -1\n }\n\n private startNextSTT() {\n this.sttIndex++\n if (this.sttIndex >= this.options.factories.length) {\n this.sttIndex = 0\n }\n this.stt?.destroy()\n this.stt = this.options.factories[this.sttIndex]()\n this.stt.on('Transcript', this.onTranscript)\n this.stt.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.stt && this.logger) {\n this.stt.logger = new Logger(this.stt.constructor.name)\n }\n }, 0)\n }\n\n private onTranscript = (transcript: string) => {\n this.emit('Transcript', transcript)\n }\n\n private onFailed = (chunks: Buffer[]) => {\n this.log('STT failed, trying next STT')\n this.startNextSTT()\n\n if (chunks.length > 0) {\n this.log('Sending audio chunks again')\n const stream = new PassThrough()\n this.stt?.transcribe(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\nimport { Logger } from '..'\n\nexport interface FallbackTTSOptions {\n factories: Array<() => TTS>\n}\n\nexport class FallbackTTS extends TTS {\n private tts: TTS | null = null\n private ttsIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackTTSOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackTTS: No factories provided')\n }\n this.startNextTTS()\n }\n\n speak(textStream: Readable) {\n this.tts?.speak(textStream)\n }\n\n cancel() {\n this.tts?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.tts?.destroy()\n this.tts = null\n this.ttsIndex = -1\n }\n\n private startNextTTS() {\n this.ttsIndex++\n if (this.ttsIndex >= this.options.factories.length) {\n this.ttsIndex = 0\n }\n this.tts?.destroy()\n this.tts = this.options.factories[this.ttsIndex]()\n this.tts.on('Audio', this.onAudio)\n this.tts.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.tts && this.logger) {\n this.tts.logger = new Logger(this.tts.constructor.name)\n }\n }, 0)\n }\n\n private onAudio = (audio: Buffer) => {\n this.emit('Audio', audio)\n }\n\n private onFailed = (chunks: string[]) => {\n this.log('TTS failed, trying next TTS')\n this.startNextTTS()\n\n if (chunks.length > 0) {\n this.log('Sending text chunks again')\n const stream = new PassThrough()\n this.tts?.speak(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface TTSEvents {\n Audio: [Buffer]\n Failed: [string[]]\n}\n\nexport abstract class TTS extends EventEmitter<TTSEvents> {\n public logger?: Logger\n\n abstract speak(textStream: Readable): void\n abstract cancel(): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.cancel()\n }\n}\n","import * as fs from 'fs'\nimport { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\n\nexport class MockTTS extends TTS {\n constructor(private audioFilePaths: string[]) {\n super()\n }\n\n speak(textStream: Readable) {\n const audioStream = new PassThrough()\n textStream.once('data', async () => {\n for (const filePath of this.audioFilePaths) {\n await new Promise((resolve) => setTimeout(resolve, 200))\n const audioBuffer = fs.readFileSync(filePath)\n this.log(`Loaded chunk (${audioBuffer.length} bytes)`)\n audioStream.write(audioBuffer)\n }\n audioStream.end()\n })\n return audioStream\n }\n\n cancel() {}\n}\n","/**\n * Cuts a stream of text into sentences as it arrives.\n *\n * Providers that synthesize a whole input at once need complete sentences, and\n * an agent writes its answer token by token. Feeding every fragment as it comes\n * would either cut words in half or wait for the end of the answer, so the text\n * is buffered until a sentence closes and released the moment it does.\n *\n * The splitter is stateful: `push` returns the sentences that are complete,\n * `flush` returns whatever is left when the stream ends.\n */\nexport class SentenceSplitter {\n private buffer = ''\n\n /** Adds text and returns the sentences it completes. */\n push(text: string): string[] {\n this.buffer += text\n return this.extract(false)\n }\n\n /** Returns the sentences left in the buffer and empties it. */\n flush(): string[] {\n const sentences = this.extract(true)\n const rest = this.buffer.trim()\n this.buffer = ''\n if (rest) sentences.push(rest)\n return sentences\n }\n\n /** Drops the buffered text, used when an utterance is cancelled. */\n reset() {\n this.buffer = ''\n }\n\n private extract(end: boolean): string[] {\n const sentences: string[] = []\n const regex = /[\\s\\S]*?[.!?…\\n]+(?=\\s|$)/g\n let match: RegExpExecArray | null\n let lastIndex = 0\n\n while ((match = regex.exec(this.buffer)) !== null) {\n // A sentence ending at the very end of an unfinished stream may still\n // grow, so keep it buffered until more text arrives or the stream ends.\n if (!end && regex.lastIndex === this.buffer.length) break\n const sentence = match[0].trim()\n if (sentence) sentences.push(sentence)\n lastIndex = regex.lastIndex\n }\n\n this.buffer = this.buffer.slice(lastIndex)\n return sentences\n }\n}\n","import { Readable } from 'stream'\nimport { SentenceSplitter } from './SentenceSplitter'\nimport { TTS } from './TTS'\n\n/**\n * Base class for text to speech engines that read a whole input at once.\n *\n * A local model, and a remote endpoint without a streaming interface, cannot\n * be fed the agent's answer token by token. This class buffers the answer into\n * sentences, hands them over one at a time, and emits the audio in the order\n * they were written. Subclasses only have to turn one sentence into PCM16 at\n * the rate the Micdrop client expects.\n *\n * Sentences are synthesized one after the other rather than at once: a local\n * model is single threaded, so racing two sentences through it slows both down\n * without bringing the first word any closer.\n */\nexport abstract class SentenceTTS extends TTS {\n private splitter = new SentenceSplitter()\n private queue: string[] = []\n private draining = false\n private controller?: AbortController\n // Bumped by every speak() and every cancel(), so a call claimed late can tell\n // whether it is still the one that should be heard.\n private generation = 0\n private counter = 0 // Identifies the current speak() call\n private synthesizing = 0 // Stamp of the sentence being synthesized\n\n /**\n * Turns one sentence into PCM16 audio at the client's sample rate.\n *\n * The signal is aborted when the utterance is cancelled, which is the moment\n * to stop a subprocess or an inference that is no longer needed. Returning\n * nothing emits nothing, which is how a cancelled synthesis reports back.\n */\n protected abstract synthesize(\n text: string,\n signal: AbortSignal\n ): Promise<Buffer | undefined>\n\n /**\n * Emits a piece of the sentence being synthesized.\n *\n * A model that generates progressively can hand its chunks over as they\n * come rather than waiting for the sentence to be finished, which brings\n * the first word forward by the duration of that sentence. The false it\n * returns says the utterance was cancelled or replaced, so the generation\n * it comes from can be stopped there.\n */\n protected emitAudio(audio: Buffer): boolean {\n if (this.synthesizing !== this.counter) return false\n if (audio.length) this.emit('Audio', audio)\n return true\n }\n\n speak(textStream: Readable) {\n const generation = ++this.generation\n let counter = 0\n\n // Claiming the call is deferred until there is something to say.\n //\n // Taking the next number right away would drop the utterance still being\n // spoken, since the queue skips anything stamped with an older one. A\n // stream that never carries a word, which is what an answer skipped by a\n // tool or by onBeforeAnswer hands over, would then cut the assistant off\n // and throw away the sentences still queued.\n const claimCall = () => {\n if (counter) return true\n // Cancelled, or superseded by another speak(), before the first word\n if (this.generation !== generation) return false\n this.counter++\n counter = this.counter\n this.splitter.reset()\n return true\n }\n\n textStream.on('data', (chunk: Buffer) => {\n if (!claimCall()) return\n if (counter !== this.counter) return\n this.enqueue(counter, this.splitter.push(chunk.toString('utf-8')))\n })\n\n textStream.on('error', (error) => {\n this.log('Error in text stream', error)\n })\n\n textStream.on('end', () => {\n // Nothing was ever said, so there is nothing left to flush\n if (!counter || counter !== this.counter) return\n this.enqueue(counter, this.splitter.flush())\n })\n }\n\n cancel() {\n this.log('Cancel')\n this.generation++\n // Increment counter to ignore queued work and the sentence in flight\n this.counter++\n this.splitter.reset()\n this.queue = []\n this.controller?.abort()\n this.controller = undefined\n }\n\n private enqueue(counter: number, sentences: string[]) {\n if (sentences.length === 0) return\n if (counter !== this.counter) return\n this.queue.push(...sentences)\n this.drain()\n }\n\n private async drain() {\n if (this.draining) return\n this.draining = true\n\n while (this.queue.length > 0) {\n const counter = this.counter\n this.synthesizing = counter\n const text = this.queue.shift()!\n const controller = new AbortController()\n this.controller = controller\n\n try {\n this.log(`Synthesizing: \"${text}\"`)\n const audio = await this.synthesize(text, controller.signal)\n // The utterance may have been cancelled while it was being synthesized\n if (counter !== this.counter) continue\n if (audio?.length) this.emit('Audio', audio)\n } catch (error) {\n // A cancelled utterance is not a failure, it left its queue on purpose\n if (counter !== this.counter) continue\n this.log('Error synthesizing speech', error)\n this.emit('Failed', [text, ...this.queue])\n this.queue = []\n } finally {\n if (this.controller === controller) this.controller = undefined\n }\n }\n\n this.draining = false\n // Sentences may have arrived right as we exited the loop\n if (this.queue.length > 0) this.drain()\n }\n}\n","import { WebSocket } from 'ws'\nimport { MicdropError, MicdropErrorCode } from './errors'\n\nexport async function waitForParams<CallParams>(\n socket: WebSocket,\n validate: (params: any) => CallParams\n): Promise<CallParams> {\n return new Promise<CallParams>((resolve, reject) => {\n // Handle timeout\n const timeout = setTimeout(() => {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Missing params'))\n }, 3000)\n\n const onParams = (payload: string) => {\n // Clear timeout and listener\n clearTimeout(timeout)\n socket.off('message', onParams)\n\n try {\n // Parse JSON payload\n const params = validate(JSON.parse(payload))\n resolve(params)\n } catch (error) {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Invalid params'))\n }\n }\n\n // Listen for params\n socket.on('message', onParams)\n })\n}\n"],"mappings":"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","names":["MicdropErrorCode","import_eventemitter3","import_stream","MicdropClientCommands","MicdropServerCommands","import_eventemitter3","import_eventemitter3","import_stream","import_stream","import_eventemitter3","import_stream"]}
package/dist/index.mjs CHANGED
@@ -447,6 +447,8 @@ var MicdropServerCommands = /* @__PURE__ */ ((MicdropServerCommands2) => {
447
447
  })(MicdropServerCommands || {});
448
448
 
449
449
  // src/MicdropServer.ts
450
+ var USER_SAMPLE_RATE = 16e3;
451
+ var DEFAULT_TURN_MAX_WAIT = 4e3;
450
452
  var MicdropServer = class extends EventEmitter2 {
451
453
  constructor(socket, config) {
452
454
  super();
@@ -458,6 +460,7 @@ var MicdropServer = class extends EventEmitter2 {
458
460
  this.isProcessingQueue = false;
459
461
  this.userSpeechChunks = 0;
460
462
  this.onClose = () => {
463
+ this.releaseHeldTurn();
461
464
  if (!this.config) return;
462
465
  this.log("Connection closed");
463
466
  const duration = Math.round((Date.now() - this.startTime) / 1e3);
@@ -501,8 +504,7 @@ var MicdropServer = class extends EventEmitter2 {
501
504
  this.config.agent.addUserMessage(transcript);
502
505
  if (!this.currentUserStream) {
503
506
  this.log("User stopped speaking, answering");
504
- this.cancel();
505
- this.answer();
507
+ this.answerUserTurn();
506
508
  }
507
509
  };
508
510
  this.onAudioTTS = (audio) => {
@@ -575,6 +577,7 @@ var MicdropServer = class extends EventEmitter2 {
575
577
  this.log(`Received chunk (${chunk.byteLength} bytes)`);
576
578
  this.currentUserStream?.write(chunk);
577
579
  this.userSpeechChunks++;
580
+ this.config?.turnDetector?.push(pcm16ToFloat32(chunk), USER_SAMPLE_RATE);
578
581
  this.emit("UserAudio", chunk);
579
582
  }
580
583
  onMute() {
@@ -588,6 +591,9 @@ var MicdropServer = class extends EventEmitter2 {
588
591
  this.userSpeechChunks = 0;
589
592
  this.currentUserStream?.end();
590
593
  this.currentUserStream = new PassThrough2();
594
+ this.config.turnDetector?.reset();
595
+ this.turnComplete = void 0;
596
+ this.releaseHeldTurn();
591
597
  this.config.stt.transcribe(this.currentUserStream);
592
598
  this.cancel();
593
599
  }
@@ -600,15 +606,60 @@ var MicdropServer = class extends EventEmitter2 {
600
606
  this.socket?.send("SkipAnswer" /* SkipAnswer */);
601
607
  return;
602
608
  }
609
+ this.turnComplete = this.predictTurnComplete();
603
610
  const conversation = this.config?.agent.conversation;
604
611
  const lastMessage = conversation?.[conversation.length - 1];
605
612
  if (lastMessage?.role === "user" && this.lastMessageSpeeched !== lastMessage) {
606
613
  this.log(
607
614
  "User stopped speaking and a transcript already exists, answering"
608
615
  );
616
+ this.answerUserTurn();
617
+ }
618
+ }
619
+ async predictTurnComplete() {
620
+ const detector = this.config?.turnDetector;
621
+ if (!detector) return true;
622
+ try {
623
+ const { complete } = await detector.predict();
624
+ this.log(`Turn sounds ${complete ? "finished" : "unfinished"}`);
625
+ return complete;
626
+ } catch (error) {
627
+ this.log(`Turn detection failed: ${error}`);
628
+ return true;
629
+ }
630
+ }
631
+ /** Answers, unless the sentence sounds like it has more coming */
632
+ async answerUserTurn() {
633
+ const complete = await (this.turnComplete ?? Promise.resolve(true));
634
+ if (!complete) {
635
+ this.log("Waiting for the rest of the sentence");
636
+ this.socket?.send("SkipAnswer" /* SkipAnswer */);
637
+ this.holdTurn();
638
+ return;
639
+ }
640
+ this.releaseHeldTurn();
641
+ this.cancel();
642
+ this.answer();
643
+ }
644
+ /**
645
+ * Answers anyway if the rest of the sentence never comes.
646
+ *
647
+ * Without it, a detector that hears an unfinished sentence where there is
648
+ * none leaves the call silent for good.
649
+ */
650
+ holdTurn() {
651
+ this.releaseHeldTurn();
652
+ this.heldTurnTimer = setTimeout(() => {
653
+ this.heldTurnTimer = void 0;
654
+ this.log("Nothing more came, answering");
609
655
  this.cancel();
610
656
  this.answer();
611
- }
657
+ }, this.config?.turnMaxWait ?? DEFAULT_TURN_MAX_WAIT);
658
+ }
659
+ releaseHeldTurn() {
660
+ if (!this.heldTurnTimer) return;
661
+ clearTimeout(this.heldTurnTimer);
662
+ this.heldTurnTimer = void 0;
612
663
  }
613
664
  sendFirstMessage() {
614
665
  if (!this.config) return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/agent/Agent.ts","../src/agent/tools.ts","../src/Logger.ts","../src/agent/FallbackAgent.ts","../src/agent/MockAgent.ts","../src/audio/Pcm16Resampler.ts","../src/audio/pcm16.ts","../src/errors.ts","../src/MicdropServer.ts","../src/types.ts","../src/recorder/MicdropRecorder.ts","../src/stt/STT.ts","../src/stt/MockSTT.ts","../src/stt/FallbackSTT.ts","../src/tts/FallbackTTS.ts","../src/tts/TTS.ts","../src/tts/MockTTS.ts","../src/tts/SentenceSplitter.ts","../src/tts/SentenceTTS.ts","../src/waitForParams.ts"],"sourcesContent":["import { EventEmitter } from 'eventemitter3'\nimport { PassThrough, Readable, Writable } from 'stream'\nimport type { z } from 'zod'\nimport { Logger } from '../Logger'\nimport {\n MicdropAnswerMetadata,\n MicdropConversation,\n MicdropConversationItem,\n MicdropConversationMessage,\n MicdropConversationToolCall,\n MicdropConversationToolResult,\n MicdropToolCall,\n} from '../types'\nimport {\n AUTO_END_CALL_PROMPT,\n AUTO_END_CALL_TOOL_NAME,\n AUTO_IGNORE_USER_NOISE_PROMPT,\n AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n AUTO_SEMANTIC_TURN_PROMPT,\n AUTO_SEMANTIC_TURN_TOOL_NAME,\n Tool,\n} from './tools'\n\nexport interface AgentOptions {\n systemPrompt: string\n\n // Enable auto ending of the call when user asks to end the call\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoEndCall?: boolean | string\n\n // Enable detection of an incomplete sentence, and skip the answer (assistant waits)\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoSemanticTurn?: boolean | string\n\n // Ignore of the last user message when it's meaningless\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoIgnoreUserNoise?: boolean | string\n\n // Extract a value from the answer\n // Value must be at the end of the answer, in JSON or between tags\n extract?: ExtractJsonOptions | ExtractTagOptions\n\n // Function called before any answer is generated\n // Return true to skip generation\n onBeforeAnswer?: (\n this: Agent,\n stream: Writable\n ) => void | boolean | Promise<boolean>\n}\n\nexport interface AgentEvents {\n Message: [MicdropConversationItem]\n CancelLastUserMessage: []\n SkipAnswer: []\n EndCall: []\n ToolCall: [MicdropToolCall]\n // Emitted when the agent gives up generating an answer (e.g. after exhausting\n // its retries). Used by FallbackAgent to switch to the next agent.\n Failed: []\n}\n\nexport interface ExtractOptions {\n callback?: (value: string) => void\n saveInMetadata?: boolean\n}\n\nexport interface ExtractJsonOptions extends ExtractOptions {\n json: true\n callback?: (value: any) => void\n}\n\nexport interface ExtractTagOptions extends ExtractOptions {\n startTag: string\n endTag: string\n}\n\nexport abstract class Agent<\n Options extends AgentOptions = AgentOptions,\n> extends EventEmitter<AgentEvents> {\n public logger?: Logger\n public conversation: MicdropConversation\n public tools: Tool[]\n\n protected answerCount = 0\n protected answering = false\n\n constructor(protected options: Options) {\n super()\n this.conversation = [{ role: 'system', content: options.systemPrompt }]\n this.tools = this.getDefaultTools()\n }\n\n protected abstract generateAnswer(stream: PassThrough): Promise<void>\n abstract cancel(): void\n\n answer(): Readable {\n this.log('Start answering')\n const answerCount = ++this.answerCount\n const stream = new PassThrough()\n this.answering = true\n\n Promise.resolve()\n // Call hook onBeforeAnswer\n .then(() => this.options.onBeforeAnswer?.bind(this)(stream))\n // Generate answer (if not skipped)\n .then((skip) => {\n if (skip) return\n return this.generateAnswer(stream)\n })\n // End stream\n .finally(() => {\n if (stream.writable) {\n stream.end()\n }\n if (answerCount === this.answerCount) {\n this.answering = false\n }\n })\n\n return stream\n }\n\n addUserMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('user', text, metadata)\n }\n\n addAssistantMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('assistant', text, metadata)\n }\n\n addTool<Schema extends z.ZodObject>(tool: Tool<Schema>) {\n this.tools.push(tool)\n }\n\n removeTool(name: string) {\n const index = this.tools.findIndex((tool) => tool.name === name)\n if (index !== -1) {\n this.tools.splice(index, 1)\n }\n }\n\n getTool(name: string): Tool | undefined {\n return this.tools.find((tool) => tool.name === name)\n }\n\n addMessage(\n role: 'user' | 'assistant' | 'system',\n text: string,\n metadata?: MicdropAnswerMetadata\n ) {\n // A turn can carry no text at all, typically when the LLM answered with a\n // tool call only. Keeping it would send an empty message back to the LLM on\n // the next turn, and emit a Message event that consumers store as an empty\n // exchange in their transcripts.\n if (text.trim() === '') {\n this.log(`Skipping empty ${role} message`)\n return\n }\n\n this.log(`Adding ${role} message to conversation: ${text}`)\n const message: MicdropConversationMessage = {\n role,\n content: text,\n metadata,\n }\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n addToolMessage(\n message: MicdropConversationToolCall | MicdropConversationToolResult\n ) {\n this.log('Adding tool message:', message)\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n protected endCall() {\n this.log('Ending call')\n this.emit('EndCall')\n }\n\n protected cancelLastUserMessage() {\n this.log('Cancelling last user message')\n const lastMessageIndex = this.conversation.findLastIndex(\n (message) => message.role === 'user'\n )\n if (lastMessageIndex !== -1) {\n this.conversation.splice(lastMessageIndex, 1)\n }\n this.emit('CancelLastUserMessage')\n }\n\n protected skipAnswer() {\n this.log('Skipping answer')\n this.emit('SkipAnswer')\n }\n\n protected getDefaultTools() {\n const tools: Tool[] = []\n if (this.options.autoEndCall) {\n tools.push({\n name: AUTO_END_CALL_TOOL_NAME,\n description:\n typeof this.options.autoEndCall === 'string'\n ? this.options.autoEndCall\n : AUTO_END_CALL_PROMPT,\n execute: (_input, agent) => agent.endCall(),\n })\n }\n if (this.options.autoSemanticTurn) {\n tools.push({\n name: AUTO_SEMANTIC_TURN_TOOL_NAME,\n description:\n typeof this.options.autoSemanticTurn === 'string'\n ? this.options.autoSemanticTurn\n : AUTO_SEMANTIC_TURN_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.skipAnswer(),\n })\n }\n if (this.options.autoIgnoreUserNoise) {\n tools.push({\n name: AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n description:\n typeof this.options.autoIgnoreUserNoise === 'string'\n ? this.options.autoIgnoreUserNoise\n : AUTO_IGNORE_USER_NOISE_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.cancelLastUserMessage(),\n })\n }\n return tools\n }\n\n protected async executeTool(toolCall: MicdropConversationToolCall) {\n try {\n const tool = this.getTool(toolCall.toolName)\n if (!tool) {\n throw new Error(`Tool not found \"${toolCall.toolName}\"`)\n }\n\n this.log('Executing tool:', toolCall.toolName, toolCall.parameters)\n\n // Save tool call in conversation\n this.addToolMessage(toolCall)\n\n const parameters = JSON.parse(toolCall.parameters)\n const output = tool.execute ? await tool.execute(parameters, this) : {}\n\n // Save tool result in conversation\n this.addToolMessage({\n role: 'tool_result',\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n output: JSON.stringify(output ?? null),\n })\n\n // Emit output\n if (tool.emitOutput) {\n this.emit('ToolCall', {\n name: toolCall.toolName,\n parameters,\n output,\n })\n }\n\n return {\n output,\n skipAnswer: tool.skipAnswer,\n }\n } catch (error: any) {\n console.error('[OpenaiAgent] Error executing tool:', error)\n return {\n output: {\n error: error.message,\n },\n }\n }\n }\n\n protected getExtractOptions(): ExtractTagOptions | undefined {\n const extract = this.options.extract\n if (!extract) return undefined\n if ('json' in extract && extract.json) {\n return { ...extract, startTag: '{', endTag: '}' }\n }\n if ('startTag' in extract && 'endTag' in extract) {\n return extract\n }\n return undefined\n }\n\n public extract(message: string) {\n const extractOptions = this.getExtractOptions()\n let metadata: MicdropAnswerMetadata | undefined = undefined\n\n // Extract value?\n if (extractOptions) {\n const startTagIndex = message.indexOf(extractOptions.startTag)\n if (startTagIndex !== -1) {\n // Find end tag\n let endTagIndex = message.lastIndexOf(extractOptions.endTag)\n if (endTagIndex === -1) endTagIndex = message.length + 1\n else endTagIndex += extractOptions.endTag.length\n const extractedText = message.slice(startTagIndex, endTagIndex).trim()\n\n // Parse extracted value\n try {\n const extractedValue =\n 'json' in extractOptions && extractOptions.json\n ? JSON.parse(extractedText)\n : extractedText\n\n // Call callback\n if (extractOptions.callback) {\n extractOptions.callback(extractedValue)\n }\n\n // Save in metadata\n if (extractOptions.saveInMetadata) {\n metadata = { extracted: extractedValue }\n }\n } catch (error) {\n console.error(\n `[OpenaiAgent] Error parsing extracted value (${extractedText}):`,\n error\n )\n }\n\n // Remove extracted value from message\n message = message.slice(0, startTagIndex).trimEnd()\n }\n }\n return { message, metadata }\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n this.cancel()\n }\n}\n","import type { z } from 'zod'\nimport type { Agent } from './Agent'\n\nexport interface Tool<Schema extends z.ZodObject = z.ZodObject> {\n name: string\n description: string\n inputSchema?: Schema\n // The executing agent is passed as context so tools stay portable (no binding\n // to a specific agent instance), which lets them be shared between agents.\n execute?: (input: z.infer<Schema>, agent: Agent) => any | Promise<any>\n skipAnswer?: boolean\n emitOutput?: boolean\n}\n\nexport const AUTO_END_CALL_TOOL_NAME = 'end_call'\nexport const AUTO_END_CALL_PROMPT =\n 'Call this tool only if user asks to end the call'\n\nexport const AUTO_SEMANTIC_TURN_TOOL_NAME = 'semantic_turn'\nexport const AUTO_SEMANTIC_TURN_PROMPT =\n 'Call this tool only if last user message is obviously an incomplete sentence that you need to wait for the end before answering'\n\nexport const AUTO_IGNORE_USER_NOISE_TOOL_NAME = 'ignore_user_noise'\nexport const AUTO_IGNORE_USER_NOISE_PROMPT =\n 'Call this tool only if last user message is just an interjection or a sound that expresses emotion, hesitation, or reaction (ex: \"Uh\", \"Ahem\", \"Hmm\", \"Ah\") but doesn\\'t carry any clear meaning like agreeing, refusing, or commanding'\n","export class Logger {\n constructor(public name: string) {}\n\n log(...message: any[]) {\n const time = process.uptime().toFixed(3)\n console.log(`[${this.name} ${time}]`, ...message)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Logger } from '../Logger'\nimport { MicdropConversationItem, MicdropToolCall } from '../types'\nimport { Agent } from './Agent'\n\nexport interface FallbackAgentOptions {\n factories: Array<() => Agent>\n}\n\nexport class FallbackAgent extends Agent {\n private agent: Agent | null = null\n private agentIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly fallbackOptions: FallbackAgentOptions) {\n super({ systemPrompt: '' })\n if (this.fallbackOptions.factories.length === 0) {\n throw new Error('FallbackAgent: No factories provided')\n }\n this.startNextAgent()\n }\n\n // Delegate extraction to the active agent (extract config lives on children)\n extract(message: string) {\n return this.agent ? this.agent.extract(message) : super.extract(message)\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n // Try each agent once (one full rotation) until one answers successfully.\n // The conversation is shared between agents, so the next agent picks up\n // exactly where the failed one stopped.\n for (\n let attempt = 0;\n attempt < this.fallbackOptions.factories.length;\n attempt++\n ) {\n const agent = this.agent\n if (!agent) return\n\n let failed = false\n const onFailed = () => {\n failed = true\n }\n agent.once('Failed', onFailed)\n\n try {\n await this.pipeAnswer(agent, stream)\n } finally {\n agent.off('Failed', onFailed)\n }\n\n if (!failed) return\n\n this.log('Agent failed, trying next agent')\n this.startNextAgent()\n }\n\n // Every agent failed within this rotation: report it so an outer consumer\n // (e.g. a wrapping FallbackAgent) can react.\n this.log('All agents failed')\n this.emit('Failed')\n }\n\n cancel() {\n this.agent?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.agent?.destroy()\n this.agent = null\n this.agentIndex = -1\n }\n\n // Run the child agent and forward its answer chunks to our own stream\n private pipeAnswer(agent: Agent, stream: PassThrough): Promise<void> {\n return new Promise((resolve) => {\n const answerStream = agent.answer()\n answerStream.on('data', (chunk) => {\n if (stream.writable) {\n stream.write(chunk)\n }\n })\n answerStream.on('end', resolve)\n answerStream.on('error', resolve)\n })\n }\n\n private startNextAgent() {\n this.agentIndex++\n if (this.agentIndex >= this.fallbackOptions.factories.length) {\n this.agentIndex = 0\n }\n\n const previousAgent = this.agent\n const isFirstAgent = previousAgent === null\n const agent = this.fallbackOptions.factories[this.agentIndex]()\n this.agent = agent\n\n // Share the conversation and tools between the fallback and the child agent.\n // Both are now portable (tools no longer bind to a specific instance), so a\n // single reference is shared, exactly like the conversation.\n if (isFirstAgent) {\n // Adopt the first agent's conversation and tools (keeps its system prompt)\n this.conversation = agent.conversation\n this.tools = agent.tools\n } else {\n // Keep the accumulated history, but use the new agent's system prompt\n if (agent.conversation[0]?.role === 'system') {\n this.conversation[0] = agent.conversation[0]\n }\n agent.conversation = this.conversation\n agent.tools = this.tools\n }\n\n // Forward events from the child agent\n agent.on('Message', this.onMessage)\n agent.on('CancelLastUserMessage', this.onCancelLastUserMessage)\n agent.on('SkipAnswer', this.onSkipAnswer)\n agent.on('EndCall', this.onEndCall)\n agent.on('ToolCall', this.onToolCall)\n\n // Destroy the previous agent (after moving the conversation over)\n previousAgent?.destroy()\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.agent && this.logger) {\n this.agent.logger = new Logger(this.agent.constructor.name)\n }\n }, 0)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n this.emit('Message', message)\n }\n\n private onCancelLastUserMessage = () => {\n this.emit('CancelLastUserMessage')\n }\n\n private onSkipAnswer = () => {\n this.emit('SkipAnswer')\n }\n\n private onEndCall = () => {\n this.emit('EndCall')\n }\n\n private onToolCall = (toolCall: MicdropToolCall) => {\n this.emit('ToolCall', toolCall)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Agent } from './Agent'\n\nexport class MockAgent extends Agent {\n private i = 0\n\n constructor() {\n super({ systemPrompt: '' })\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n const message = `Assistant Message ${this.i++}`\n this.addAssistantMessage(message)\n stream.write(message)\n }\n\n cancel() {}\n}\n","/**\n * Streaming linear-interpolation resampler for PCM16 mono audio.\n *\n * Works in both directions (up or downsampling). It is stateful: it handles\n * arbitrary byte boundaries (a network chunk can split a 16-bit sample) and\n * keeps the fractional sample position continuous across chunks, so feeding a\n * stream chunk by chunk yields the same result as resampling it in one go.\n *\n * Providers use it to bridge their own rate with the 16kHz PCM16 the Micdrop\n * client records and plays: OpenaiSTT (16kHz -> 24kHz, the GA Realtime API\n * requires >= 24kHz), OpenaiTTS and KokoroTTS (24kHz output -> 16kHz).\n */\nexport class Pcm16Resampler {\n private readonly step: number\n private leftover: Buffer<ArrayBufferLike> = Buffer.alloc(0)\n private pos = 0 // Fractional position into the first sample of the buffer\n\n constructor(inRate: number, outRate: number) {\n this.step = inRate / outRate\n }\n\n // Reset to the initial state, to resample a new independent stream\n // (e.g. resending buffered audio after a reconnection).\n reset() {\n this.leftover = Buffer.alloc(0)\n this.pos = 0\n }\n\n process(chunk: Buffer): Buffer {\n const buf = this.leftover.length\n ? Buffer.concat([this.leftover, chunk])\n : chunk\n const samples = Math.floor(buf.length / 2)\n\n // Need at least 2 samples to interpolate\n if (samples < 2) {\n this.leftover = buf\n return Buffer.alloc(0)\n }\n\n const out: number[] = []\n let p = this.pos\n while (Math.floor(p) + 1 < samples) {\n const i = Math.floor(p)\n const frac = p - i\n const s0 = buf.readInt16LE(i * 2)\n const s1 = buf.readInt16LE((i + 1) * 2)\n out.push(Math.round(s0 + (s1 - s0) * frac))\n p += this.step\n }\n\n // Keep the last still-needed sample (and any trailing odd byte) for the\n // next chunk, and carry the fractional position relative to it.\n const consumed = Math.floor(p)\n this.pos = p - consumed\n this.leftover = buf.subarray(consumed * 2)\n\n const result = Buffer.alloc(out.length * 2)\n for (let k = 0; k < out.length; k++) {\n result.writeInt16LE(out[k], k * 2)\n }\n return result\n }\n}\n","/**\n * Conversions between the PCM16 buffers exchanged with the Micdrop client and\n * the float samples that local speech models read and write.\n *\n * Both formats are mono. PCM16 is signed 16-bit little-endian, floats are in\n * the [-1, 1] range. Only the scale changes, the sample rate is left alone.\n */\n\nconst PCM16_MAX = 32767\nconst PCM16_MIN = -32768\n\n/** Turns float samples into a PCM16 buffer, clamping anything out of range. */\nexport function float32ToPcm16(samples: Float32Array): Buffer {\n const buffer = Buffer.alloc(samples.length * 2)\n for (let i = 0; i < samples.length; i++) {\n const scaled = Math.round(samples[i] * PCM16_MAX)\n const clamped = Math.max(PCM16_MIN, Math.min(PCM16_MAX, scaled))\n buffer.writeInt16LE(clamped, i * 2)\n }\n return buffer\n}\n\n/**\n * Turns a PCM16 buffer into float samples.\n *\n * A trailing odd byte is dropped: it is half of a sample whose other half has\n * not arrived, and a caller feeding whole utterances never produces one.\n */\nexport function pcm16ToFloat32(buffer: Buffer): Float32Array {\n const length = Math.floor(buffer.length / 2)\n const samples = new Float32Array(length)\n for (let i = 0; i < length; i++) {\n samples[i] = buffer.readInt16LE(i * 2) / PCM16_MAX\n }\n return samples\n}\n","import WebSocket from 'ws'\n\nexport enum MicdropErrorCode {\n BadRequest = 4400,\n Unauthorized = 4401,\n NotFound = 4404,\n}\n\nexport class MicdropError extends Error {\n code: number\n\n constructor(code: number, message: string) {\n super(message)\n this.code = code\n }\n}\n\nexport function handleError(socket: WebSocket, error: unknown) {\n if (error instanceof MicdropError) {\n socket.close(error.code, error.message)\n } else {\n console.error(error)\n socket.close(1011)\n }\n socket.terminate()\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Duplex, PassThrough, Readable } from 'stream'\nimport { WebSocket } from 'ws'\nimport type { Agent } from './agent'\nimport { Logger } from './Logger'\nimport type { STT } from './stt'\nimport type { TTS } from './tts'\nimport {\n MicdropCallSummary,\n MicdropClientCommands,\n MicdropConversationItem,\n MicdropServerCommands,\n} from './types'\n\nexport interface MicdropServerEvents {\n End: [MicdropCallSummary]\n UserAudio: [Buffer]\n AssistantAudio: [Buffer]\n}\n\nexport interface MicdropConfig {\n firstMessage?: string\n generateFirstMessage?: boolean\n agent: Agent\n stt: STT\n tts: TTS\n}\n\nexport class MicdropServer extends EventEmitter<MicdropServerEvents> {\n public socket: WebSocket | null = null\n public config: MicdropConfig | null = null\n public logger?: Logger\n\n private startTime = Date.now()\n private lastMessageSpeeched?: MicdropConversationItem\n\n // Queue system for operations\n private operationQueue: Array<() => Promise<void>> = []\n private isProcessingQueue = false\n\n // When user is speaking, we're streaming chunks for STT\n private currentUserStream?: Duplex\n private userSpeechChunks = 0\n\n constructor(socket: WebSocket, config: MicdropConfig) {\n super()\n this.socket = socket\n this.config = config\n this.log(`Call started`)\n\n // Setup STT\n this.config.stt.on('Transcript', this.onTranscriptSTT)\n\n // Setup TTS\n this.config.tts.on('Audio', this.onAudioTTS)\n\n // Setup agent\n this.config.agent.on('Message', (message) =>\n this.socket?.send(\n `${MicdropServerCommands.Message} ${JSON.stringify(message)}`\n )\n )\n this.config.agent.on('CancelLastUserMessage', () =>\n this.socket?.send(MicdropServerCommands.CancelLastUserMessage)\n )\n this.config.agent.on('SkipAnswer', () =>\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n )\n this.config.agent.on('EndCall', () =>\n this.socket?.send(MicdropServerCommands.EndCall)\n )\n this.config.agent.on('ToolCall', (toolCall) =>\n this.socket?.send(\n `${MicdropServerCommands.ToolCall} ${JSON.stringify(toolCall)}`\n )\n )\n\n // Assistant speaks first\n // Deferred so consumers (e.g. MicdropRecorder) can subscribe to agent\n // events before the first message is added to the conversation.\n queueMicrotask(() => this.sendFirstMessage())\n\n // Listen to events\n socket.on('close', this.onClose)\n socket.on('message', this.onMessage)\n }\n\n private log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n private async processQueue() {\n if (this.isProcessingQueue || this.operationQueue.length === 0) return\n\n this.isProcessingQueue = true\n\n while (this.operationQueue.length > 0) {\n const operation = this.operationQueue.shift()\n if (operation) {\n try {\n await operation()\n } catch (error) {\n this.log('Error processing queued operation:', error)\n }\n }\n }\n\n this.isProcessingQueue = false\n }\n\n private queueOperation(operation: () => Promise<void>) {\n this.operationQueue.push(operation)\n this.processQueue()\n }\n\n public cancel() {\n this.config?.tts.cancel()\n this.config?.agent.cancel()\n // Clear the queue\n this.operationQueue = []\n }\n\n private onClose = () => {\n if (!this.config) return\n this.log('Connection closed')\n const duration = Math.round((Date.now() - this.startTime) / 1000)\n\n // Destroy instances\n this.config.agent.destroy()\n this.config.stt.destroy()\n this.config.tts.destroy()\n\n // Emit End event\n this.emit('End', {\n conversation: this.config.agent.conversation,\n duration,\n })\n\n // Unset params\n this.socket = null\n this.config = null\n }\n\n private onMessage = async (message: Buffer) => {\n if (message.byteLength === 0) return\n if (!Buffer.isBuffer(message)) {\n this.log('Message is not a buffer')\n return\n }\n\n // Commands\n if (message.byteLength < 15) {\n const cmd = message.toString()\n this.log(`Command: ${cmd}`)\n\n if (cmd === MicdropClientCommands.StartSpeaking) {\n // User started speaking\n this.onStartSpeaking()\n } else if (cmd === MicdropClientCommands.Mute) {\n // User muted the call\n this.onMute()\n } else if (cmd === MicdropClientCommands.StopSpeaking) {\n // User stopped speaking\n this.onStopSpeaking()\n }\n }\n\n // Audio chunk\n else if (this.currentUserStream) {\n this.onUserAudio(message)\n }\n }\n\n private onUserAudio(chunk: Buffer) {\n this.log(`Received chunk (${chunk.byteLength} bytes)`)\n this.currentUserStream?.write(chunk)\n this.userSpeechChunks++\n this.emit('UserAudio', chunk)\n }\n\n private onMute() {\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.cancel()\n }\n\n private onStartSpeaking() {\n if (!this.config) return\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = new PassThrough()\n this.config.stt.transcribe(this.currentUserStream)\n this.cancel()\n }\n\n private onStopSpeaking() {\n const hasNoUserSpeech =\n !this.currentUserStream || this.userSpeechChunks === 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.userSpeechChunks = 0\n\n // If user is not speaking or no chunks were received, skip\n if (hasNoUserSpeech) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n const conversation = this.config?.agent.conversation\n const lastMessage = conversation?.[conversation.length - 1]\n if (\n lastMessage?.role === 'user' &&\n this.lastMessageSpeeched !== lastMessage\n ) {\n this.log(\n 'User stopped speaking and a transcript already exists, answering'\n )\n this.cancel()\n this.answer()\n }\n }\n\n private onTranscriptSTT = async (transcript: string) => {\n if (!this.config) return\n\n // Skip answer if transcript is empty\n if (transcript === '') {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n this.log(`User transcript: \"${transcript}\"`)\n this.config.agent.addUserMessage(transcript)\n\n // Answer if user stopped speaking\n if (!this.currentUserStream) {\n this.log('User stopped speaking, answering')\n this.cancel()\n this.answer()\n }\n }\n\n private onAudioTTS = (audio: Buffer) => {\n if (!this.socket) return\n this.log(`Send audio chunk (${audio.byteLength} bytes)`)\n this.socket.send(audio)\n this.emit('AssistantAudio', audio)\n }\n\n private sendFirstMessage() {\n if (!this.config) return\n if (this.config.firstMessage) {\n // Send first message\n this.config.agent.addAssistantMessage(this.config.firstMessage)\n this.speak(this.config.firstMessage)\n } else if (this.config.generateFirstMessage) {\n // Generate first message\n this.answer()\n } else {\n // Skip answer if no first message is provided\n // to avoid keeping the client in a processing state\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n }\n }\n\n public answer() {\n this.queueOperation(async () => {\n await this._answer()\n })\n }\n\n private async _answer() {\n if (!this.config) return\n\n // Prevent answering twice\n const lastMessage =\n this.config.agent.conversation[this.config.agent.conversation.length - 1]\n if (this.lastMessageSpeeched === lastMessage) {\n this.log('Already answered, skipping')\n return\n }\n this.lastMessageSpeeched = lastMessage\n\n try {\n // LLM: Generate answer\n const stream = this.config.agent.answer()\n\n // TTS: Generate answer audio, unless there is nothing to say.\n //\n // An answer can be skipped after the fact: a tool with skipAnswer, or an\n // onBeforeAnswer hook returning true, ends the stream without a word in\n // it. Handing that empty stream to the TTS opens a synthesis request for\n // nothing, and a provider that stamps each request (Gradium multiplexes\n // this way) then drops the audio of the sentence still playing, so a\n // skipped answer cuts the assistant off mid-word.\n if (await hasContent(stream)) {\n await this._speak(stream)\n }\n } catch (error) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n throw error\n }\n }\n\n // Run text-to-speech and send to client\n public speak(message: string | Readable) {\n this.queueOperation(async () => {\n await this._speak(message)\n })\n }\n\n private async _speak(message: string | Readable) {\n if (!this.socket || !this.config) return\n\n // Convert message to stream if needed\n let textStream: Readable\n if (typeof message === 'string') {\n const stream = new PassThrough()\n stream.write(message)\n stream.end()\n textStream = stream\n } else {\n textStream = message\n }\n\n // Run TTS\n this.config.tts.speak(textStream)\n }\n}\n\n/**\n * Resolves true as soon as the stream holds something to read, false if it ends\n * without ever carrying anything.\n *\n * The chunk read to find out is put back, so the consumer that follows sees the\n * whole stream from its first byte.\n */\nfunction hasContent(stream: Readable): Promise<boolean> {\n return new Promise((resolve) => {\n const onReadable = () => {\n const chunk = stream.read()\n if (chunk === null) return\n stream.unshift(chunk)\n done(true)\n }\n const onEnd = () => done(false)\n const done = (result: boolean) => {\n stream.off('readable', onReadable)\n stream.off('end', onEnd)\n resolve(result)\n }\n stream.on('readable', onReadable)\n stream.on('end', onEnd)\n })\n}\n","export enum MicdropClientCommands {\n StartSpeaking = 'StartSpeaking',\n StopSpeaking = 'StopSpeaking',\n Mute = 'Mute',\n}\n\nexport enum MicdropServerCommands {\n Message = 'Message',\n CancelLastUserMessage = 'CancelLastUserMessage',\n SkipAnswer = 'SkipAnswer',\n EndCall = 'EndCall',\n ToolCall = 'ToolCall',\n}\n\nexport interface MicdropCallSummary {\n conversation: MicdropConversation\n duration: number\n}\n\nexport type MicdropConversationItem =\n | MicdropConversationMessage\n | MicdropConversationToolCall\n | MicdropConversationToolResult\n\nexport type MicdropConversation = Array<MicdropConversationItem>\n\nexport type MicdropAnswerMetadata = {\n [key: string]: any\n}\n\nexport interface MicdropConversationMessage<\n Data extends MicdropAnswerMetadata = MicdropAnswerMetadata,\n> {\n role: 'system' | 'user' | 'assistant'\n content: string\n metadata?: Data\n}\n\nexport interface MicdropConversationToolCall {\n role: 'tool_call'\n toolCallId: string\n toolName: string\n parameters: string\n}\n\nexport interface MicdropConversationToolResult {\n role: 'tool_result'\n toolCallId: string\n toolName: string\n output: string\n}\n\nexport interface MicdropToolCall {\n name: string\n parameters: any\n output: any\n}\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n","import { EventEmitter } from 'eventemitter3'\nimport type { MicdropServer } from '../MicdropServer'\nimport type { MicdropConversationItem } from '../types'\nimport { Logger } from '../Logger'\n\nexport interface AudioMessage {\n buffer: Buffer\n messageIndex: number\n message: string\n role: 'user' | 'assistant'\n}\n\nexport interface MicdropRecorderEvents {\n AudioMessage: [AudioMessage]\n Complete: [AudioMessage[]]\n}\n\nexport class MicdropRecorder extends EventEmitter<MicdropRecorderEvents> {\n public logger?: Logger\n\n private audioMessages: AudioMessage[] = []\n private currentUserChunks: Buffer[] = []\n private currentAssistantChunks: Buffer[] = []\n private lastUserMessageIndex: number = -1\n private lastAssistantMessageIndex: number = -1\n\n constructor(private server: MicdropServer) {\n super()\n this.setupListeners()\n }\n\n private setupListeners() {\n // Listen to audio events from server\n this.server.on('UserAudio', this.onUserAudio)\n this.server.on('AssistantAudio', this.onAssistantAudio)\n this.server.on('End', this.onEnd)\n\n // Listen to message events from agent\n const agent = this.server.config?.agent\n if (agent) {\n agent.on('Message', this.onMessage)\n }\n }\n\n private onUserAudio = (chunk: Buffer) => {\n // Finalize or discard assistant audio when user starts speaking\n if (this.currentAssistantChunks.length > 0) {\n if (this.lastAssistantMessageIndex >= 0) {\n this.finalizeAssistantAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned assistant audio chunks')\n this.currentAssistantChunks = []\n }\n }\n\n this.log('Recording user audio chunk')\n this.currentUserChunks.push(chunk)\n }\n\n private onAssistantAudio = (chunk: Buffer) => {\n // Finalize or discard user audio when assistant starts speaking\n if (this.currentUserChunks.length > 0) {\n if (this.lastUserMessageIndex >= 0) {\n this.finalizeUserAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned user audio chunks')\n this.currentUserChunks = []\n }\n }\n\n this.log('Recording assistant audio chunk')\n this.currentAssistantChunks.push(chunk)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const messageIndex = conversation.length - 1\n\n if (message.role === 'user') {\n this.lastUserMessageIndex = messageIndex\n // User audio might already be complete, finalize if we have chunks\n // Audio chunks arrive BEFORE message, so we finalize when we know the message\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n } else if (message.role === 'assistant') {\n this.lastAssistantMessageIndex = messageIndex\n // Don't finalize assistant audio here - chunks can still arrive after message\n }\n }\n\n private finalizeUserAudio() {\n if (this.currentUserChunks.length === 0) return\n if (this.lastUserMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastUserMessageIndex]\n const buffer = Buffer.concat(this.currentUserChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastUserMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'user',\n }\n\n this.log(\n `Finalized user audio: ${buffer.length} bytes, message index ${this.lastUserMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentUserChunks = []\n this.lastUserMessageIndex = -1\n }\n\n private finalizeAssistantAudio() {\n if (this.currentAssistantChunks.length === 0) return\n if (this.lastAssistantMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastAssistantMessageIndex]\n const buffer = Buffer.concat(this.currentAssistantChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastAssistantMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'assistant',\n }\n\n this.log(\n `Finalized assistant audio: ${buffer.length} bytes, message index ${this.lastAssistantMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentAssistantChunks = []\n this.lastAssistantMessageIndex = -1\n }\n\n private onEnd = () => {\n // Finalize any remaining audio\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n if (this.currentAssistantChunks.length > 0) {\n this.finalizeAssistantAudio()\n }\n\n this.log(`Recording complete: ${this.audioMessages.length} audio messages`)\n this.emit('Complete', this.audioMessages)\n }\n\n public getAudioMessages(): AudioMessage[] {\n return [...this.audioMessages]\n }\n\n public destroy() {\n this.log('Destroyed')\n this.server.off('UserAudio', this.onUserAudio)\n this.server.off('AssistantAudio', this.onAssistantAudio)\n this.server.off('End', this.onEnd)\n\n const agent = this.server.config?.agent\n if (agent) {\n agent.off('Message', this.onMessage)\n }\n\n this.removeAllListeners()\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface STTEvents {\n Transcript: [string]\n Failed: [Buffer[]]\n}\n\nexport abstract class STT extends EventEmitter<STTEvents> {\n public logger?: Logger\n\n // Set stream of audio to transcribe\n abstract transcribe(audioStream: Readable): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n }\n}\n","import { STT } from './STT'\n\nexport class MockSTT extends STT {\n private i = 0\n\n async transcribe() {\n setTimeout(() => {\n this.emit('Transcript', `User Message ${this.i++}`)\n }, 300)\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { STT } from './STT'\nimport { Logger } from '..'\n\nexport interface FallbackSTTOptions {\n factories: Array<() => STT>\n}\n\nexport class FallbackSTT extends STT {\n private stt: STT | null = null\n private sttIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackSTTOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackSTT: No factories provided')\n }\n this.startNextSTT()\n }\n\n transcribe(audioStream: Readable) {\n this.stt?.transcribe(audioStream)\n }\n\n destroy() {\n super.destroy()\n this.stt?.destroy()\n this.stt = null\n this.sttIndex = -1\n }\n\n private startNextSTT() {\n this.sttIndex++\n if (this.sttIndex >= this.options.factories.length) {\n this.sttIndex = 0\n }\n this.stt?.destroy()\n this.stt = this.options.factories[this.sttIndex]()\n this.stt.on('Transcript', this.onTranscript)\n this.stt.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.stt && this.logger) {\n this.stt.logger = new Logger(this.stt.constructor.name)\n }\n }, 0)\n }\n\n private onTranscript = (transcript: string) => {\n this.emit('Transcript', transcript)\n }\n\n private onFailed = (chunks: Buffer[]) => {\n this.log('STT failed, trying next STT')\n this.startNextSTT()\n\n if (chunks.length > 0) {\n this.log('Sending audio chunks again')\n const stream = new PassThrough()\n this.stt?.transcribe(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\nimport { Logger } from '..'\n\nexport interface FallbackTTSOptions {\n factories: Array<() => TTS>\n}\n\nexport class FallbackTTS extends TTS {\n private tts: TTS | null = null\n private ttsIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackTTSOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackTTS: No factories provided')\n }\n this.startNextTTS()\n }\n\n speak(textStream: Readable) {\n this.tts?.speak(textStream)\n }\n\n cancel() {\n this.tts?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.tts?.destroy()\n this.tts = null\n this.ttsIndex = -1\n }\n\n private startNextTTS() {\n this.ttsIndex++\n if (this.ttsIndex >= this.options.factories.length) {\n this.ttsIndex = 0\n }\n this.tts?.destroy()\n this.tts = this.options.factories[this.ttsIndex]()\n this.tts.on('Audio', this.onAudio)\n this.tts.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.tts && this.logger) {\n this.tts.logger = new Logger(this.tts.constructor.name)\n }\n }, 0)\n }\n\n private onAudio = (audio: Buffer) => {\n this.emit('Audio', audio)\n }\n\n private onFailed = (chunks: string[]) => {\n this.log('TTS failed, trying next TTS')\n this.startNextTTS()\n\n if (chunks.length > 0) {\n this.log('Sending text chunks again')\n const stream = new PassThrough()\n this.tts?.speak(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface TTSEvents {\n Audio: [Buffer]\n Failed: [string[]]\n}\n\nexport abstract class TTS extends EventEmitter<TTSEvents> {\n public logger?: Logger\n\n abstract speak(textStream: Readable): void\n abstract cancel(): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.cancel()\n }\n}\n","import * as fs from 'fs'\nimport { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\n\nexport class MockTTS extends TTS {\n constructor(private audioFilePaths: string[]) {\n super()\n }\n\n speak(textStream: Readable) {\n const audioStream = new PassThrough()\n textStream.once('data', async () => {\n for (const filePath of this.audioFilePaths) {\n await new Promise((resolve) => setTimeout(resolve, 200))\n const audioBuffer = fs.readFileSync(filePath)\n this.log(`Loaded chunk (${audioBuffer.length} bytes)`)\n audioStream.write(audioBuffer)\n }\n audioStream.end()\n })\n return audioStream\n }\n\n cancel() {}\n}\n","/**\n * Cuts a stream of text into sentences as it arrives.\n *\n * Providers that synthesize a whole input at once need complete sentences, and\n * an agent writes its answer token by token. Feeding every fragment as it comes\n * would either cut words in half or wait for the end of the answer, so the text\n * is buffered until a sentence closes and released the moment it does.\n *\n * The splitter is stateful: `push` returns the sentences that are complete,\n * `flush` returns whatever is left when the stream ends.\n */\nexport class SentenceSplitter {\n private buffer = ''\n\n /** Adds text and returns the sentences it completes. */\n push(text: string): string[] {\n this.buffer += text\n return this.extract(false)\n }\n\n /** Returns the sentences left in the buffer and empties it. */\n flush(): string[] {\n const sentences = this.extract(true)\n const rest = this.buffer.trim()\n this.buffer = ''\n if (rest) sentences.push(rest)\n return sentences\n }\n\n /** Drops the buffered text, used when an utterance is cancelled. */\n reset() {\n this.buffer = ''\n }\n\n private extract(end: boolean): string[] {\n const sentences: string[] = []\n const regex = /[\\s\\S]*?[.!?…\\n]+(?=\\s|$)/g\n let match: RegExpExecArray | null\n let lastIndex = 0\n\n while ((match = regex.exec(this.buffer)) !== null) {\n // A sentence ending at the very end of an unfinished stream may still\n // grow, so keep it buffered until more text arrives or the stream ends.\n if (!end && regex.lastIndex === this.buffer.length) break\n const sentence = match[0].trim()\n if (sentence) sentences.push(sentence)\n lastIndex = regex.lastIndex\n }\n\n this.buffer = this.buffer.slice(lastIndex)\n return sentences\n }\n}\n","import { Readable } from 'stream'\nimport { SentenceSplitter } from './SentenceSplitter'\nimport { TTS } from './TTS'\n\n/**\n * Base class for text to speech engines that read a whole input at once.\n *\n * A local model, and a remote endpoint without a streaming interface, cannot\n * be fed the agent's answer token by token. This class buffers the answer into\n * sentences, hands them over one at a time, and emits the audio in the order\n * they were written. Subclasses only have to turn one sentence into PCM16 at\n * the rate the Micdrop client expects.\n *\n * Sentences are synthesized one after the other rather than at once: a local\n * model is single threaded, so racing two sentences through it slows both down\n * without bringing the first word any closer.\n */\nexport abstract class SentenceTTS extends TTS {\n private splitter = new SentenceSplitter()\n private queue: string[] = []\n private draining = false\n private controller?: AbortController\n // Bumped by every speak() and every cancel(), so a call claimed late can tell\n // whether it is still the one that should be heard.\n private generation = 0\n private counter = 0 // Identifies the current speak() call\n private synthesizing = 0 // Stamp of the sentence being synthesized\n\n /**\n * Turns one sentence into PCM16 audio at the client's sample rate.\n *\n * The signal is aborted when the utterance is cancelled, which is the moment\n * to stop a subprocess or an inference that is no longer needed. Returning\n * nothing emits nothing, which is how a cancelled synthesis reports back.\n */\n protected abstract synthesize(\n text: string,\n signal: AbortSignal\n ): Promise<Buffer | undefined>\n\n /**\n * Emits a piece of the sentence being synthesized.\n *\n * A model that generates progressively can hand its chunks over as they\n * come rather than waiting for the sentence to be finished, which brings\n * the first word forward by the duration of that sentence. The false it\n * returns says the utterance was cancelled or replaced, so the generation\n * it comes from can be stopped there.\n */\n protected emitAudio(audio: Buffer): boolean {\n if (this.synthesizing !== this.counter) return false\n if (audio.length) this.emit('Audio', audio)\n return true\n }\n\n speak(textStream: Readable) {\n const generation = ++this.generation\n let counter = 0\n\n // Claiming the call is deferred until there is something to say.\n //\n // Taking the next number right away would drop the utterance still being\n // spoken, since the queue skips anything stamped with an older one. A\n // stream that never carries a word, which is what an answer skipped by a\n // tool or by onBeforeAnswer hands over, would then cut the assistant off\n // and throw away the sentences still queued.\n const claimCall = () => {\n if (counter) return true\n // Cancelled, or superseded by another speak(), before the first word\n if (this.generation !== generation) return false\n this.counter++\n counter = this.counter\n this.splitter.reset()\n return true\n }\n\n textStream.on('data', (chunk: Buffer) => {\n if (!claimCall()) return\n if (counter !== this.counter) return\n this.enqueue(counter, this.splitter.push(chunk.toString('utf-8')))\n })\n\n textStream.on('error', (error) => {\n this.log('Error in text stream', error)\n })\n\n textStream.on('end', () => {\n // Nothing was ever said, so there is nothing left to flush\n if (!counter || counter !== this.counter) return\n this.enqueue(counter, this.splitter.flush())\n })\n }\n\n cancel() {\n this.log('Cancel')\n this.generation++\n // Increment counter to ignore queued work and the sentence in flight\n this.counter++\n this.splitter.reset()\n this.queue = []\n this.controller?.abort()\n this.controller = undefined\n }\n\n private enqueue(counter: number, sentences: string[]) {\n if (sentences.length === 0) return\n if (counter !== this.counter) return\n this.queue.push(...sentences)\n this.drain()\n }\n\n private async drain() {\n if (this.draining) return\n this.draining = true\n\n while (this.queue.length > 0) {\n const counter = this.counter\n this.synthesizing = counter\n const text = this.queue.shift()!\n const controller = new AbortController()\n this.controller = controller\n\n try {\n this.log(`Synthesizing: \"${text}\"`)\n const audio = await this.synthesize(text, controller.signal)\n // The utterance may have been cancelled while it was being synthesized\n if (counter !== this.counter) continue\n if (audio?.length) this.emit('Audio', audio)\n } catch (error) {\n // A cancelled utterance is not a failure, it left its queue on purpose\n if (counter !== this.counter) continue\n this.log('Error synthesizing speech', error)\n this.emit('Failed', [text, ...this.queue])\n this.queue = []\n } finally {\n if (this.controller === controller) this.controller = undefined\n }\n }\n\n this.draining = false\n // Sentences may have arrived right as we exited the loop\n if (this.queue.length > 0) this.drain()\n }\n}\n","import { WebSocket } from 'ws'\nimport { MicdropError, MicdropErrorCode } from './errors'\n\nexport async function waitForParams<CallParams>(\n socket: WebSocket,\n validate: (params: any) => CallParams\n): Promise<CallParams> {\n return new Promise<CallParams>((resolve, reject) => {\n // Handle timeout\n const timeout = setTimeout(() => {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Missing params'))\n }, 3000)\n\n const onParams = (payload: string) => {\n // Clear timeout and listener\n clearTimeout(timeout)\n socket.off('message', onParams)\n\n try {\n // Parse JSON payload\n const params = validate(JSON.parse(payload))\n resolve(params)\n } catch (error) {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Invalid params'))\n }\n }\n\n // Listen for params\n socket.on('message', onParams)\n })\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,mBAAuC;;;ACazC,IAAM,0BAA0B;AAChC,IAAM,uBACX;AAEK,IAAM,+BAA+B;AACrC,IAAM,4BACX;AAEK,IAAM,mCAAmC;AACzC,IAAM,gCACX;;;ADoDK,IAAe,QAAf,cAEG,aAA0B;AAAA,EAQlC,YAAsB,SAAkB;AACtC,UAAM;AADc;AAHtB,SAAU,cAAc;AACxB,SAAU,YAAY;AAIpB,SAAK,eAAe,CAAC,EAAE,MAAM,UAAU,SAAS,QAAQ,aAAa,CAAC;AACtE,SAAK,QAAQ,KAAK,gBAAgB;AAAA,EACpC;AAAA,EAKA,SAAmB;AACjB,SAAK,IAAI,iBAAiB;AAC1B,UAAM,cAAc,EAAE,KAAK;AAC3B,UAAM,SAAS,IAAI,YAAY;AAC/B,SAAK,YAAY;AAEjB,YAAQ,QAAQ,EAEb,KAAK,MAAM,KAAK,QAAQ,gBAAgB,KAAK,IAAI,EAAE,MAAM,CAAC,EAE1D,KAAK,CAAC,SAAS;AACd,UAAI,KAAM;AACV,aAAO,KAAK,eAAe,MAAM;AAAA,IACnC,CAAC,EAEA,QAAQ,MAAM;AACb,UAAI,OAAO,UAAU;AACnB,eAAO,IAAI;AAAA,MACb;AACA,UAAI,gBAAgB,KAAK,aAAa;AACpC,aAAK,YAAY;AAAA,MACnB;AAAA,IACF,CAAC;AAEH,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,MAAc,UAAkC;AAC7D,SAAK,WAAW,QAAQ,MAAM,QAAQ;AAAA,EACxC;AAAA,EAEA,oBAAoB,MAAc,UAAkC;AAClE,SAAK,WAAW,aAAa,MAAM,QAAQ;AAAA,EAC7C;AAAA,EAEA,QAAoC,MAAoB;AACtD,SAAK,MAAM,KAAK,IAAI;AAAA,EACtB;AAAA,EAEA,WAAW,MAAc;AACvB,UAAM,QAAQ,KAAK,MAAM,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI;AAC/D,QAAI,UAAU,IAAI;AAChB,WAAK,MAAM,OAAO,OAAO,CAAC;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,QAAQ,MAAgC;AACtC,WAAO,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,IAAI;AAAA,EACrD;AAAA,EAEA,WACE,MACA,MACA,UACA;AAKA,QAAI,KAAK,KAAK,MAAM,IAAI;AACtB,WAAK,IAAI,kBAAkB,IAAI,UAAU;AACzC;AAAA,IACF;AAEA,SAAK,IAAI,UAAU,IAAI,6BAA6B,IAAI,EAAE;AAC1D,UAAM,UAAsC;AAAA,MAC1C;AAAA,MACA,SAAS;AAAA,MACT;AAAA,IACF;AACA,SAAK,aAAa,KAAK,OAAO;AAC9B,SAAK,KAAK,WAAW,OAAO;AAAA,EAC9B;AAAA,EAEA,eACE,SACA;AACA,SAAK,IAAI,wBAAwB,OAAO;AACxC,SAAK,aAAa,KAAK,OAAO;AAC9B,SAAK,KAAK,WAAW,OAAO;AAAA,EAC9B;AAAA,EAEU,UAAU;AAClB,SAAK,IAAI,aAAa;AACtB,SAAK,KAAK,SAAS;AAAA,EACrB;AAAA,EAEU,wBAAwB;AAChC,SAAK,IAAI,8BAA8B;AACvC,UAAM,mBAAmB,KAAK,aAAa;AAAA,MACzC,CAAC,YAAY,QAAQ,SAAS;AAAA,IAChC;AACA,QAAI,qBAAqB,IAAI;AAC3B,WAAK,aAAa,OAAO,kBAAkB,CAAC;AAAA,IAC9C;AACA,SAAK,KAAK,uBAAuB;AAAA,EACnC;AAAA,EAEU,aAAa;AACrB,SAAK,IAAI,iBAAiB;AAC1B,SAAK,KAAK,YAAY;AAAA,EACxB;AAAA,EAEU,kBAAkB;AAC1B,UAAM,QAAgB,CAAC;AACvB,QAAI,KAAK,QAAQ,aAAa;AAC5B,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,aACE,OAAO,KAAK,QAAQ,gBAAgB,WAChC,KAAK,QAAQ,cACb;AAAA,QACN,SAAS,CAAC,QAAQ,UAAU,MAAM,QAAQ;AAAA,MAC5C,CAAC;AAAA,IACH;AACA,QAAI,KAAK,QAAQ,kBAAkB;AACjC,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,aACE,OAAO,KAAK,QAAQ,qBAAqB,WACrC,KAAK,QAAQ,mBACb;AAAA,QACN,YAAY;AAAA,QACZ,SAAS,CAAC,QAAQ,UAAU,MAAM,WAAW;AAAA,MAC/C,CAAC;AAAA,IACH;AACA,QAAI,KAAK,QAAQ,qBAAqB;AACpC,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,aACE,OAAO,KAAK,QAAQ,wBAAwB,WACxC,KAAK,QAAQ,sBACb;AAAA,QACN,YAAY;AAAA,QACZ,SAAS,CAAC,QAAQ,UAAU,MAAM,sBAAsB;AAAA,MAC1D,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAgB,YAAY,UAAuC;AACjE,QAAI;AACF,YAAM,OAAO,KAAK,QAAQ,SAAS,QAAQ;AAC3C,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,MAAM,mBAAmB,SAAS,QAAQ,GAAG;AAAA,MACzD;AAEA,WAAK,IAAI,mBAAmB,SAAS,UAAU,SAAS,UAAU;AAGlE,WAAK,eAAe,QAAQ;AAE5B,YAAM,aAAa,KAAK,MAAM,SAAS,UAAU;AACjD,YAAM,SAAS,KAAK,UAAU,MAAM,KAAK,QAAQ,YAAY,IAAI,IAAI,CAAC;AAGtE,WAAK,eAAe;AAAA,QAClB,MAAM;AAAA,QACN,YAAY,SAAS;AAAA,QACrB,UAAU,SAAS;AAAA,QACnB,QAAQ,KAAK,UAAU,UAAU,IAAI;AAAA,MACvC,CAAC;AAGD,UAAI,KAAK,YAAY;AACnB,aAAK,KAAK,YAAY;AAAA,UACpB,MAAM,SAAS;AAAA,UACf;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,QACL;AAAA,QACA,YAAY,KAAK;AAAA,MACnB;AAAA,IACF,SAAS,OAAY;AACnB,cAAQ,MAAM,uCAAuC,KAAK;AAC1D,aAAO;AAAA,QACL,QAAQ;AAAA,UACN,OAAO,MAAM;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEU,oBAAmD;AAC3D,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI,CAAC,QAAS,QAAO;AACrB,QAAI,UAAU,WAAW,QAAQ,MAAM;AACrC,aAAO,EAAE,GAAG,SAAS,UAAU,KAAK,QAAQ,IAAI;AAAA,IAClD;AACA,QAAI,cAAc,WAAW,YAAY,SAAS;AAChD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEO,QAAQ,SAAiB;AAC9B,UAAM,iBAAiB,KAAK,kBAAkB;AAC9C,QAAI,WAA8C;AAGlD,QAAI,gBAAgB;AAClB,YAAM,gBAAgB,QAAQ,QAAQ,eAAe,QAAQ;AAC7D,UAAI,kBAAkB,IAAI;AAExB,YAAI,cAAc,QAAQ,YAAY,eAAe,MAAM;AAC3D,YAAI,gBAAgB,GAAI,eAAc,QAAQ,SAAS;AAAA,YAClD,gBAAe,eAAe,OAAO;AAC1C,cAAM,gBAAgB,QAAQ,MAAM,eAAe,WAAW,EAAE,KAAK;AAGrE,YAAI;AACF,gBAAM,iBACJ,UAAU,kBAAkB,eAAe,OACvC,KAAK,MAAM,aAAa,IACxB;AAGN,cAAI,eAAe,UAAU;AAC3B,2BAAe,SAAS,cAAc;AAAA,UACxC;AAGA,cAAI,eAAe,gBAAgB;AACjC,uBAAW,EAAE,WAAW,eAAe;AAAA,UACzC;AAAA,QACF,SAAS,OAAO;AACd,kBAAQ;AAAA,YACN,gDAAgD,aAAa;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAGA,kBAAU,QAAQ,MAAM,GAAG,aAAa,EAAE,QAAQ;AAAA,MACpD;AAAA,IACF;AACA,WAAO,EAAE,SAAS,SAAS;AAAA,EAC7B;AAAA,EAEU,OAAO,SAAgB;AAC/B,SAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7B;AAAA,EAEA,UAAU;AACR,SAAK,IAAI,WAAW;AACpB,SAAK,mBAAmB;AACxB,SAAK,OAAO;AAAA,EACd;AACF;;;AE1VO,IAAM,SAAN,MAAa;AAAA,EAClB,YAAmB,MAAc;AAAd;AAAA,EAAe;AAAA,EAElC,OAAO,SAAgB;AACrB,UAAM,OAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC;AACvC,YAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,OAAO;AAAA,EAClD;AACF;;;ACEO,IAAM,gBAAN,cAA4B,MAAM;AAAA;AAAA,EAIvC,YAA6B,iBAAuC;AAClE,UAAM,EAAE,cAAc,GAAG,CAAC;AADC;AAH7B,SAAQ,QAAsB;AAC9B,SAAQ,aAAa;AAyHrB,SAAQ,YAAY,CAAC,YAAqC;AACxD,WAAK,KAAK,WAAW,OAAO;AAAA,IAC9B;AAEA,SAAQ,0BAA0B,MAAM;AACtC,WAAK,KAAK,uBAAuB;AAAA,IACnC;AAEA,SAAQ,eAAe,MAAM;AAC3B,WAAK,KAAK,YAAY;AAAA,IACxB;AAEA,SAAQ,YAAY,MAAM;AACxB,WAAK,KAAK,SAAS;AAAA,IACrB;AAEA,SAAQ,aAAa,CAAC,aAA8B;AAClD,WAAK,KAAK,YAAY,QAAQ;AAAA,IAChC;AAvIE,QAAI,KAAK,gBAAgB,UAAU,WAAW,GAAG;AAC/C,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,QAAQ,SAAiB;AACvB,WAAO,KAAK,QAAQ,KAAK,MAAM,QAAQ,OAAO,IAAI,MAAM,QAAQ,OAAO;AAAA,EACzE;AAAA,EAEA,MAAgB,eAAe,QAAoC;AAIjE,aACM,UAAU,GACd,UAAU,KAAK,gBAAgB,UAAU,QACzC,WACA;AACA,YAAM,QAAQ,KAAK;AACnB,UAAI,CAAC,MAAO;AAEZ,UAAI,SAAS;AACb,YAAM,WAAW,MAAM;AACrB,iBAAS;AAAA,MACX;AACA,YAAM,KAAK,UAAU,QAAQ;AAE7B,UAAI;AACF,cAAM,KAAK,WAAW,OAAO,MAAM;AAAA,MACrC,UAAE;AACA,cAAM,IAAI,UAAU,QAAQ;AAAA,MAC9B;AAEA,UAAI,CAAC,OAAQ;AAEb,WAAK,IAAI,iCAAiC;AAC1C,WAAK,eAAe;AAAA,IACtB;AAIA,SAAK,IAAI,mBAAmB;AAC5B,SAAK,KAAK,QAAQ;AAAA,EACpB;AAAA,EAEA,SAAS;AACP,SAAK,OAAO,OAAO;AAAA,EACrB;AAAA,EAEA,UAAU;AACR,UAAM,QAAQ;AACd,SAAK,OAAO,QAAQ;AACpB,SAAK,QAAQ;AACb,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAGQ,WAAW,OAAc,QAAoC;AACnE,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,eAAe,MAAM,OAAO;AAClC,mBAAa,GAAG,QAAQ,CAAC,UAAU;AACjC,YAAI,OAAO,UAAU;AACnB,iBAAO,MAAM,KAAK;AAAA,QACpB;AAAA,MACF,CAAC;AACD,mBAAa,GAAG,OAAO,OAAO;AAC9B,mBAAa,GAAG,SAAS,OAAO;AAAA,IAClC,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB;AACvB,SAAK;AACL,QAAI,KAAK,cAAc,KAAK,gBAAgB,UAAU,QAAQ;AAC5D,WAAK,aAAa;AAAA,IACpB;AAEA,UAAM,gBAAgB,KAAK;AAC3B,UAAM,eAAe,kBAAkB;AACvC,UAAM,QAAQ,KAAK,gBAAgB,UAAU,KAAK,UAAU,EAAE;AAC9D,SAAK,QAAQ;AAKb,QAAI,cAAc;AAEhB,WAAK,eAAe,MAAM;AAC1B,WAAK,QAAQ,MAAM;AAAA,IACrB,OAAO;AAEL,UAAI,MAAM,aAAa,CAAC,GAAG,SAAS,UAAU;AAC5C,aAAK,aAAa,CAAC,IAAI,MAAM,aAAa,CAAC;AAAA,MAC7C;AACA,YAAM,eAAe,KAAK;AAC1B,YAAM,QAAQ,KAAK;AAAA,IACrB;AAGA,UAAM,GAAG,WAAW,KAAK,SAAS;AAClC,UAAM,GAAG,yBAAyB,KAAK,uBAAuB;AAC9D,UAAM,GAAG,cAAc,KAAK,YAAY;AACxC,UAAM,GAAG,WAAW,KAAK,SAAS;AAClC,UAAM,GAAG,YAAY,KAAK,UAAU;AAGpC,mBAAe,QAAQ;AAGvB,eAAW,MAAM;AACf,UAAI,KAAK,SAAS,KAAK,QAAQ;AAC7B,aAAK,MAAM,SAAS,IAAI,OAAO,KAAK,MAAM,YAAY,IAAI;AAAA,MAC5D;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AAqBF;;;ACpJO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAGnC,cAAc;AACZ,UAAM,EAAE,cAAc,GAAG,CAAC;AAH5B,SAAQ,IAAI;AAAA,EAIZ;AAAA,EAEA,MAAgB,eAAe,QAAoC;AACjE,UAAM,UAAU,qBAAqB,KAAK,GAAG;AAC7C,SAAK,oBAAoB,OAAO;AAChC,WAAO,MAAM,OAAO;AAAA,EACtB;AAAA,EAEA,SAAS;AAAA,EAAC;AACZ;;;ACLO,IAAM,iBAAN,MAAqB;AAAA;AAAA,EAK1B,YAAY,QAAgB,SAAiB;AAH7C,SAAQ,WAAoC,OAAO,MAAM,CAAC;AAC1D,SAAQ,MAAM;AAGZ,SAAK,OAAO,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA,EAIA,QAAQ;AACN,SAAK,WAAW,OAAO,MAAM,CAAC;AAC9B,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,QAAQ,OAAuB;AAC7B,UAAM,MAAM,KAAK,SAAS,SACtB,OAAO,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC,IACpC;AACJ,UAAM,UAAU,KAAK,MAAM,IAAI,SAAS,CAAC;AAGzC,QAAI,UAAU,GAAG;AACf,WAAK,WAAW;AAChB,aAAO,OAAO,MAAM,CAAC;AAAA,IACvB;AAEA,UAAM,MAAgB,CAAC;AACvB,QAAI,IAAI,KAAK;AACb,WAAO,KAAK,MAAM,CAAC,IAAI,IAAI,SAAS;AAClC,YAAM,IAAI,KAAK,MAAM,CAAC;AACtB,YAAM,OAAO,IAAI;AACjB,YAAM,KAAK,IAAI,YAAY,IAAI,CAAC;AAChC,YAAM,KAAK,IAAI,aAAa,IAAI,KAAK,CAAC;AACtC,UAAI,KAAK,KAAK,MAAM,MAAM,KAAK,MAAM,IAAI,CAAC;AAC1C,WAAK,KAAK;AAAA,IACZ;AAIA,UAAM,WAAW,KAAK,MAAM,CAAC;AAC7B,SAAK,MAAM,IAAI;AACf,SAAK,WAAW,IAAI,SAAS,WAAW,CAAC;AAEzC,UAAM,SAAS,OAAO,MAAM,IAAI,SAAS,CAAC;AAC1C,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,aAAO,aAAa,IAAI,CAAC,GAAG,IAAI,CAAC;AAAA,IACnC;AACA,WAAO;AAAA,EACT;AACF;;;ACvDA,IAAM,YAAY;AAClB,IAAM,YAAY;AAGX,SAAS,eAAe,SAA+B;AAC5D,QAAM,SAAS,OAAO,MAAM,QAAQ,SAAS,CAAC;AAC9C,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,SAAS,KAAK,MAAM,QAAQ,CAAC,IAAI,SAAS;AAChD,UAAM,UAAU,KAAK,IAAI,WAAW,KAAK,IAAI,WAAW,MAAM,CAAC;AAC/D,WAAO,aAAa,SAAS,IAAI,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAQO,SAAS,eAAe,QAA8B;AAC3D,QAAM,SAAS,KAAK,MAAM,OAAO,SAAS,CAAC;AAC3C,QAAM,UAAU,IAAI,aAAa,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,YAAQ,CAAC,IAAI,OAAO,YAAY,IAAI,CAAC,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;;;ACjCO,IAAK,mBAAL,kBAAKA,sBAAL;AACL,EAAAA,oCAAA,gBAAa,QAAb;AACA,EAAAA,oCAAA,kBAAe,QAAf;AACA,EAAAA,oCAAA,cAAW,QAAX;AAHU,SAAAA;AAAA,GAAA;AAML,IAAM,eAAN,cAA2B,MAAM;AAAA,EAGtC,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,YAAY,QAAmB,OAAgB;AAC7D,MAAI,iBAAiB,cAAc;AACjC,WAAO,MAAM,MAAM,MAAM,MAAM,OAAO;AAAA,EACxC,OAAO;AACL,YAAQ,MAAM,KAAK;AACnB,WAAO,MAAM,IAAI;AAAA,EACnB;AACA,SAAO,UAAU;AACnB;;;ACzBA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAiB,eAAAC,oBAA6B;;;ACDvC,IAAK,wBAAL,kBAAKC,2BAAL;AACL,EAAAA,uBAAA,mBAAgB;AAChB,EAAAA,uBAAA,kBAAe;AACf,EAAAA,uBAAA,UAAO;AAHG,SAAAA;AAAA,GAAA;AAML,IAAK,wBAAL,kBAAKC,2BAAL;AACL,EAAAA,uBAAA,aAAU;AACV,EAAAA,uBAAA,2BAAwB;AACxB,EAAAA,uBAAA,gBAAa;AACb,EAAAA,uBAAA,aAAU;AACV,EAAAA,uBAAA,cAAW;AALD,SAAAA;AAAA,GAAA;;;ADsBL,IAAM,gBAAN,cAA4BC,cAAkC;AAAA,EAgBnE,YAAY,QAAmB,QAAuB;AACpD,UAAM;AAhBR,SAAO,SAA2B;AAClC,SAAO,SAA+B;AAGtC,SAAQ,YAAY,KAAK,IAAI;AAI7B;AAAA,SAAQ,iBAA6C,CAAC;AACtD,SAAQ,oBAAoB;AAI5B,SAAQ,mBAAmB;AAgF3B,SAAQ,UAAU,MAAM;AACtB,UAAI,CAAC,KAAK,OAAQ;AAClB,WAAK,IAAI,mBAAmB;AAC5B,YAAM,WAAW,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,aAAa,GAAI;AAGhE,WAAK,OAAO,MAAM,QAAQ;AAC1B,WAAK,OAAO,IAAI,QAAQ;AACxB,WAAK,OAAO,IAAI,QAAQ;AAGxB,WAAK,KAAK,OAAO;AAAA,QACf,cAAc,KAAK,OAAO,MAAM;AAAA,QAChC;AAAA,MACF,CAAC;AAGD,WAAK,SAAS;AACd,WAAK,SAAS;AAAA,IAChB;AAEA,SAAQ,YAAY,OAAO,YAAoB;AAC7C,UAAI,QAAQ,eAAe,EAAG;AAC9B,UAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC7B,aAAK,IAAI,yBAAyB;AAClC;AAAA,MACF;AAGA,UAAI,QAAQ,aAAa,IAAI;AAC3B,cAAM,MAAM,QAAQ,SAAS;AAC7B,aAAK,IAAI,YAAY,GAAG,EAAE;AAE1B,YAAI,6CAA6C;AAE/C,eAAK,gBAAgB;AAAA,QACvB,WAAW,2BAAoC;AAE7C,eAAK,OAAO;AAAA,QACd,WAAW,2CAA4C;AAErD,eAAK,eAAe;AAAA,QACtB;AAAA,MACF,WAGS,KAAK,mBAAmB;AAC/B,aAAK,YAAY,OAAO;AAAA,MAC1B;AAAA,IACF;AAoDA,SAAQ,kBAAkB,OAAO,eAAuB;AACtD,UAAI,CAAC,KAAK,OAAQ;AAGlB,UAAI,eAAe,IAAI;AACrB,aAAK,QAAQ,kCAAqC;AAClD;AAAA,MACF;AAEA,WAAK,IAAI,qBAAqB,UAAU,GAAG;AAC3C,WAAK,OAAO,MAAM,eAAe,UAAU;AAG3C,UAAI,CAAC,KAAK,mBAAmB;AAC3B,aAAK,IAAI,kCAAkC;AAC3C,aAAK,OAAO;AACZ,aAAK,OAAO;AAAA,MACd;AAAA,IACF;AAEA,SAAQ,aAAa,CAAC,UAAkB;AACtC,UAAI,CAAC,KAAK,OAAQ;AAClB,WAAK,IAAI,qBAAqB,MAAM,UAAU,SAAS;AACvD,WAAK,OAAO,KAAK,KAAK;AACtB,WAAK,KAAK,kBAAkB,KAAK;AAAA,IACnC;AA1ME,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,IAAI,cAAc;AAGvB,SAAK,OAAO,IAAI,GAAG,cAAc,KAAK,eAAe;AAGrD,SAAK,OAAO,IAAI,GAAG,SAAS,KAAK,UAAU;AAG3C,SAAK,OAAO,MAAM;AAAA,MAAG;AAAA,MAAW,CAAC,YAC/B,KAAK,QAAQ;AAAA,QACX,0BAAgC,IAAI,KAAK,UAAU,OAAO,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,SAAK,OAAO,MAAM;AAAA,MAAG;AAAA,MAAyB,MAC5C,KAAK,QAAQ,wDAAgD;AAAA,IAC/D;AACA,SAAK,OAAO,MAAM;AAAA,MAAG;AAAA,MAAc,MACjC,KAAK,QAAQ,kCAAqC;AAAA,IACpD;AACA,SAAK,OAAO,MAAM;AAAA,MAAG;AAAA,MAAW,MAC9B,KAAK,QAAQ,4BAAkC;AAAA,IACjD;AACA,SAAK,OAAO,MAAM;AAAA,MAAG;AAAA,MAAY,CAAC,aAChC,KAAK,QAAQ;AAAA,QACX,4BAAiC,IAAI,KAAK,UAAU,QAAQ,CAAC;AAAA,MAC/D;AAAA,IACF;AAKA,mBAAe,MAAM,KAAK,iBAAiB,CAAC;AAG5C,WAAO,GAAG,SAAS,KAAK,OAAO;AAC/B,WAAO,GAAG,WAAW,KAAK,SAAS;AAAA,EACrC;AAAA,EAEQ,OAAO,SAAgB;AAC7B,SAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7B;AAAA,EAEA,MAAc,eAAe;AAC3B,QAAI,KAAK,qBAAqB,KAAK,eAAe,WAAW,EAAG;AAEhE,SAAK,oBAAoB;AAEzB,WAAO,KAAK,eAAe,SAAS,GAAG;AACrC,YAAM,YAAY,KAAK,eAAe,MAAM;AAC5C,UAAI,WAAW;AACb,YAAI;AACF,gBAAM,UAAU;AAAA,QAClB,SAAS,OAAO;AACd,eAAK,IAAI,sCAAsC,KAAK;AAAA,QACtD;AAAA,MACF;AAAA,IACF;AAEA,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEQ,eAAe,WAAgC;AACrD,SAAK,eAAe,KAAK,SAAS;AAClC,SAAK,aAAa;AAAA,EACpB;AAAA,EAEO,SAAS;AACd,SAAK,QAAQ,IAAI,OAAO;AACxB,SAAK,QAAQ,MAAM,OAAO;AAE1B,SAAK,iBAAiB,CAAC;AAAA,EACzB;AAAA,EAqDQ,YAAY,OAAe;AACjC,SAAK,IAAI,mBAAmB,MAAM,UAAU,SAAS;AACrD,SAAK,mBAAmB,MAAM,KAAK;AACnC,SAAK;AACL,SAAK,KAAK,aAAa,KAAK;AAAA,EAC9B;AAAA,EAEQ,SAAS;AACf,SAAK,mBAAmB;AACxB,SAAK,mBAAmB,IAAI;AAC5B,SAAK,oBAAoB;AACzB,SAAK,OAAO;AAAA,EACd;AAAA,EAEQ,kBAAkB;AACxB,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,mBAAmB;AACxB,SAAK,mBAAmB,IAAI;AAC5B,SAAK,oBAAoB,IAAIC,aAAY;AACzC,SAAK,OAAO,IAAI,WAAW,KAAK,iBAAiB;AACjD,SAAK,OAAO;AAAA,EACd;AAAA,EAEQ,iBAAiB;AACvB,UAAM,kBACJ,CAAC,KAAK,qBAAqB,KAAK,qBAAqB;AACvD,SAAK,mBAAmB,IAAI;AAC5B,SAAK,oBAAoB;AACzB,SAAK,mBAAmB;AAGxB,QAAI,iBAAiB;AACnB,WAAK,QAAQ,kCAAqC;AAClD;AAAA,IACF;AAEA,UAAM,eAAe,KAAK,QAAQ,MAAM;AACxC,UAAM,cAAc,eAAe,aAAa,SAAS,CAAC;AAC1D,QACE,aAAa,SAAS,UACtB,KAAK,wBAAwB,aAC7B;AACA,WAAK;AAAA,QACH;AAAA,MACF;AACA,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA,EA6BQ,mBAAmB;AACzB,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI,KAAK,OAAO,cAAc;AAE5B,WAAK,OAAO,MAAM,oBAAoB,KAAK,OAAO,YAAY;AAC9D,WAAK,MAAM,KAAK,OAAO,YAAY;AAAA,IACrC,WAAW,KAAK,OAAO,sBAAsB;AAE3C,WAAK,OAAO;AAAA,IACd,OAAO;AAGL,WAAK,QAAQ,kCAAqC;AAAA,IACpD;AAAA,EACF;AAAA,EAEO,SAAS;AACd,SAAK,eAAe,YAAY;AAC9B,YAAM,KAAK,QAAQ;AAAA,IACrB,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,UAAU;AACtB,QAAI,CAAC,KAAK,OAAQ;AAGlB,UAAM,cACJ,KAAK,OAAO,MAAM,aAAa,KAAK,OAAO,MAAM,aAAa,SAAS,CAAC;AAC1E,QAAI,KAAK,wBAAwB,aAAa;AAC5C,WAAK,IAAI,4BAA4B;AACrC;AAAA,IACF;AACA,SAAK,sBAAsB;AAE3B,QAAI;AAEF,YAAM,SAAS,KAAK,OAAO,MAAM,OAAO;AAUxC,UAAI,MAAM,WAAW,MAAM,GAAG;AAC5B,cAAM,KAAK,OAAO,MAAM;AAAA,MAC1B;AAAA,IACF,SAAS,OAAO;AACd,WAAK,QAAQ,kCAAqC;AAClD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAGO,MAAM,SAA4B;AACvC,SAAK,eAAe,YAAY;AAC9B,YAAM,KAAK,OAAO,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,OAAO,SAA4B;AAC/C,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,OAAQ;AAGlC,QAAI;AACJ,QAAI,OAAO,YAAY,UAAU;AAC/B,YAAM,SAAS,IAAIA,aAAY;AAC/B,aAAO,MAAM,OAAO;AACpB,aAAO,IAAI;AACX,mBAAa;AAAA,IACf,OAAO;AACL,mBAAa;AAAA,IACf;AAGA,SAAK,OAAO,IAAI,MAAM,UAAU;AAAA,EAClC;AACF;AASA,SAAS,WAAW,QAAoC;AACtD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,aAAa,MAAM;AACvB,YAAM,QAAQ,OAAO,KAAK;AAC1B,UAAI,UAAU,KAAM;AACpB,aAAO,QAAQ,KAAK;AACpB,WAAK,IAAI;AAAA,IACX;AACA,UAAM,QAAQ,MAAM,KAAK,KAAK;AAC9B,UAAM,OAAO,CAAC,WAAoB;AAChC,aAAO,IAAI,YAAY,UAAU;AACjC,aAAO,IAAI,OAAO,KAAK;AACvB,cAAQ,MAAM;AAAA,IAChB;AACA,WAAO,GAAG,YAAY,UAAU;AAChC,WAAO,GAAG,OAAO,KAAK;AAAA,EACxB,CAAC;AACH;;;AEnWA,SAAS,gBAAAC,qBAAoB;AAiBtB,IAAM,kBAAN,cAA8BA,cAAoC;AAAA,EASvE,YAAoB,QAAuB;AACzC,UAAM;AADY;AANpB,SAAQ,gBAAgC,CAAC;AACzC,SAAQ,oBAA8B,CAAC;AACvC,SAAQ,yBAAmC,CAAC;AAC5C,SAAQ,uBAA+B;AACvC,SAAQ,4BAAoC;AAoB5C,SAAQ,cAAc,CAAC,UAAkB;AAEvC,UAAI,KAAK,uBAAuB,SAAS,GAAG;AAC1C,YAAI,KAAK,6BAA6B,GAAG;AACvC,eAAK,uBAAuB;AAAA,QAC9B,OAAO;AAEL,eAAK,IAAI,4CAA4C;AACrD,eAAK,yBAAyB,CAAC;AAAA,QACjC;AAAA,MACF;AAEA,WAAK,IAAI,4BAA4B;AACrC,WAAK,kBAAkB,KAAK,KAAK;AAAA,IACnC;AAEA,SAAQ,mBAAmB,CAAC,UAAkB;AAE5C,UAAI,KAAK,kBAAkB,SAAS,GAAG;AACrC,YAAI,KAAK,wBAAwB,GAAG;AAClC,eAAK,kBAAkB;AAAA,QACzB,OAAO;AAEL,eAAK,IAAI,uCAAuC;AAChD,eAAK,oBAAoB,CAAC;AAAA,QAC5B;AAAA,MACF;AAEA,WAAK,IAAI,iCAAiC;AAC1C,WAAK,uBAAuB,KAAK,KAAK;AAAA,IACxC;AAEA,SAAQ,YAAY,CAAC,YAAqC;AACxD,YAAM,eAAe,KAAK,OAAO,QAAQ,MAAM;AAC/C,UAAI,CAAC,aAAc;AAEnB,YAAM,eAAe,aAAa,SAAS;AAE3C,UAAI,QAAQ,SAAS,QAAQ;AAC3B,aAAK,uBAAuB;AAG5B,YAAI,KAAK,kBAAkB,SAAS,GAAG;AACrC,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF,WAAW,QAAQ,SAAS,aAAa;AACvC,aAAK,4BAA4B;AAAA,MAEnC;AAAA,IACF;AA0DA,SAAQ,QAAQ,MAAM;AAEpB,UAAI,KAAK,kBAAkB,SAAS,GAAG;AACrC,aAAK,kBAAkB;AAAA,MACzB;AACA,UAAI,KAAK,uBAAuB,SAAS,GAAG;AAC1C,aAAK,uBAAuB;AAAA,MAC9B;AAEA,WAAK,IAAI,uBAAuB,KAAK,cAAc,MAAM,iBAAiB;AAC1E,WAAK,KAAK,YAAY,KAAK,aAAa;AAAA,IAC1C;AAtIE,SAAK,eAAe;AAAA,EACtB;AAAA,EAEQ,iBAAiB;AAEvB,SAAK,OAAO,GAAG,aAAa,KAAK,WAAW;AAC5C,SAAK,OAAO,GAAG,kBAAkB,KAAK,gBAAgB;AACtD,SAAK,OAAO,GAAG,OAAO,KAAK,KAAK;AAGhC,UAAM,QAAQ,KAAK,OAAO,QAAQ;AAClC,QAAI,OAAO;AACT,YAAM,GAAG,WAAW,KAAK,SAAS;AAAA,IACpC;AAAA,EACF;AAAA,EAqDQ,oBAAoB;AAC1B,QAAI,KAAK,kBAAkB,WAAW,EAAG;AACzC,QAAI,KAAK,uBAAuB,EAAG;AAEnC,UAAM,eAAe,KAAK,OAAO,QAAQ,MAAM;AAC/C,QAAI,CAAC,aAAc;AAEnB,UAAM,UAAU,aAAa,KAAK,oBAAoB;AACtD,UAAM,SAAS,OAAO,OAAO,KAAK,iBAAiB;AAEnD,UAAM,eAA6B;AAAA,MACjC;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,SAAS,aAAa,UAAU,QAAQ,UAAU;AAAA,MAClD,MAAM;AAAA,IACR;AAEA,SAAK;AAAA,MACH,yBAAyB,OAAO,MAAM,yBAAyB,KAAK,oBAAoB;AAAA,IAC1F;AACA,SAAK,cAAc,KAAK,YAAY;AACpC,SAAK,KAAK,gBAAgB,YAAY;AAGtC,SAAK,oBAAoB,CAAC;AAC1B,SAAK,uBAAuB;AAAA,EAC9B;AAAA,EAEQ,yBAAyB;AAC/B,QAAI,KAAK,uBAAuB,WAAW,EAAG;AAC9C,QAAI,KAAK,4BAA4B,EAAG;AAExC,UAAM,eAAe,KAAK,OAAO,QAAQ,MAAM;AAC/C,QAAI,CAAC,aAAc;AAEnB,UAAM,UAAU,aAAa,KAAK,yBAAyB;AAC3D,UAAM,SAAS,OAAO,OAAO,KAAK,sBAAsB;AAExD,UAAM,eAA6B;AAAA,MACjC;AAAA,MACA,cAAc,KAAK;AAAA,MACnB,SAAS,aAAa,UAAU,QAAQ,UAAU;AAAA,MAClD,MAAM;AAAA,IACR;AAEA,SAAK;AAAA,MACH,8BAA8B,OAAO,MAAM,yBAAyB,KAAK,yBAAyB;AAAA,IACpG;AACA,SAAK,cAAc,KAAK,YAAY;AACpC,SAAK,KAAK,gBAAgB,YAAY;AAGtC,SAAK,yBAAyB,CAAC;AAC/B,SAAK,4BAA4B;AAAA,EACnC;AAAA,EAeO,mBAAmC;AACxC,WAAO,CAAC,GAAG,KAAK,aAAa;AAAA,EAC/B;AAAA,EAEO,UAAU;AACf,SAAK,IAAI,WAAW;AACpB,SAAK,OAAO,IAAI,aAAa,KAAK,WAAW;AAC7C,SAAK,OAAO,IAAI,kBAAkB,KAAK,gBAAgB;AACvD,SAAK,OAAO,IAAI,OAAO,KAAK,KAAK;AAEjC,UAAM,QAAQ,KAAK,OAAO,QAAQ;AAClC,QAAI,OAAO;AACT,YAAM,IAAI,WAAW,KAAK,SAAS;AAAA,IACrC;AAEA,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEU,OAAO,SAAgB;AAC/B,SAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7B;AACF;;;ACzLA,SAAS,gBAAAC,qBAAoB;AAStB,IAAe,MAAf,cAA2BA,cAAwB;AAAA,EAM9C,OAAO,SAAgB;AAC/B,SAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7B;AAAA,EAEA,UAAU;AACR,SAAK,IAAI,WAAW;AACpB,SAAK,mBAAmB;AAAA,EAC1B;AACF;;;ACrBO,IAAM,UAAN,cAAsB,IAAI;AAAA,EAA1B;AAAA;AACL,SAAQ,IAAI;AAAA;AAAA,EAEZ,MAAM,aAAa;AACjB,eAAW,MAAM;AACf,WAAK,KAAK,cAAc,gBAAgB,KAAK,GAAG,EAAE;AAAA,IACpD,GAAG,GAAG;AAAA,EACR;AACF;;;ACVA,SAAS,eAAAC,oBAA6B;AAQ/B,IAAM,cAAN,cAA0B,IAAI;AAAA;AAAA,EAInC,YAA6B,SAA6B;AACxD,UAAM;AADqB;AAH7B,SAAQ,MAAkB;AAC1B,SAAQ,WAAW;AAuCnB,SAAQ,eAAe,CAAC,eAAuB;AAC7C,WAAK,KAAK,cAAc,UAAU;AAAA,IACpC;AAEA,SAAQ,WAAW,CAAC,WAAqB;AACvC,WAAK,IAAI,6BAA6B;AACtC,WAAK,aAAa;AAElB,UAAI,OAAO,SAAS,GAAG;AACrB,aAAK,IAAI,4BAA4B;AACrC,cAAM,SAAS,IAAIC,aAAY;AAC/B,aAAK,KAAK,WAAW,MAAM;AAC3B,eAAO,QAAQ,CAAC,UAAU,OAAO,MAAM,KAAK,CAAC;AAC7C,eAAO,IAAI;AAAA,MACb;AAAA,IACF;AAlDE,QAAI,KAAK,QAAQ,UAAU,WAAW,GAAG;AACvC,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,WAAW,aAAuB;AAChC,SAAK,KAAK,WAAW,WAAW;AAAA,EAClC;AAAA,EAEA,UAAU;AACR,UAAM,QAAQ;AACd,SAAK,KAAK,QAAQ;AAClB,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,eAAe;AACrB,SAAK;AACL,QAAI,KAAK,YAAY,KAAK,QAAQ,UAAU,QAAQ;AAClD,WAAK,WAAW;AAAA,IAClB;AACA,SAAK,KAAK,QAAQ;AAClB,SAAK,MAAM,KAAK,QAAQ,UAAU,KAAK,QAAQ,EAAE;AACjD,SAAK,IAAI,GAAG,cAAc,KAAK,YAAY;AAC3C,SAAK,IAAI,GAAG,UAAU,KAAK,QAAQ;AAGnC,eAAW,MAAM;AACf,UAAI,KAAK,OAAO,KAAK,QAAQ;AAC3B,aAAK,IAAI,SAAS,IAAI,OAAO,KAAK,IAAI,YAAY,IAAI;AAAA,MACxD;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AAkBF;;;ACjEA,SAAS,eAAAC,oBAA6B;;;ACAtC,SAAS,gBAAAC,qBAAoB;AAStB,IAAe,MAAf,cAA2BA,cAAwB;AAAA,EAM9C,OAAO,SAAgB;AAC/B,SAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC7B;AAAA,EAEA,UAAU;AACR,SAAK,IAAI,WAAW;AACpB,SAAK,OAAO;AAAA,EACd;AACF;;;ADfO,IAAM,cAAN,cAA0B,IAAI;AAAA;AAAA,EAInC,YAA6B,SAA6B;AACxD,UAAM;AADqB;AAH7B,SAAQ,MAAkB;AAC1B,SAAQ,WAAW;AA2CnB,SAAQ,UAAU,CAAC,UAAkB;AACnC,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B;AAEA,SAAQ,WAAW,CAAC,WAAqB;AACvC,WAAK,IAAI,6BAA6B;AACtC,WAAK,aAAa;AAElB,UAAI,OAAO,SAAS,GAAG;AACrB,aAAK,IAAI,2BAA2B;AACpC,cAAM,SAAS,IAAIC,aAAY;AAC/B,aAAK,KAAK,MAAM,MAAM;AACtB,eAAO,QAAQ,CAAC,UAAU,OAAO,MAAM,KAAK,CAAC;AAC7C,eAAO,IAAI;AAAA,MACb;AAAA,IACF;AAtDE,QAAI,KAAK,QAAQ,UAAU,WAAW,GAAG;AACvC,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,YAAsB;AAC1B,SAAK,KAAK,MAAM,UAAU;AAAA,EAC5B;AAAA,EAEA,SAAS;AACP,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA,EAEA,UAAU;AACR,UAAM,QAAQ;AACd,SAAK,KAAK,QAAQ;AAClB,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,eAAe;AACrB,SAAK;AACL,QAAI,KAAK,YAAY,KAAK,QAAQ,UAAU,QAAQ;AAClD,WAAK,WAAW;AAAA,IAClB;AACA,SAAK,KAAK,QAAQ;AAClB,SAAK,MAAM,KAAK,QAAQ,UAAU,KAAK,QAAQ,EAAE;AACjD,SAAK,IAAI,GAAG,SAAS,KAAK,OAAO;AACjC,SAAK,IAAI,GAAG,UAAU,KAAK,QAAQ;AAGnC,eAAW,MAAM;AACf,UAAI,KAAK,OAAO,KAAK,QAAQ;AAC3B,aAAK,IAAI,SAAS,IAAI,OAAO,KAAK,IAAI,YAAY,IAAI;AAAA,MACxD;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AAkBF;;;AErEA,YAAY,QAAQ;AACpB,SAAS,eAAAC,oBAA6B;AAG/B,IAAM,UAAN,cAAsB,IAAI;AAAA,EAC/B,YAAoB,gBAA0B;AAC5C,UAAM;AADY;AAAA,EAEpB;AAAA,EAEA,MAAM,YAAsB;AAC1B,UAAM,cAAc,IAAIC,aAAY;AACpC,eAAW,KAAK,QAAQ,YAAY;AAClC,iBAAW,YAAY,KAAK,gBAAgB;AAC1C,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AACvD,cAAM,cAAiB,gBAAa,QAAQ;AAC5C,aAAK,IAAI,iBAAiB,YAAY,MAAM,SAAS;AACrD,oBAAY,MAAM,WAAW;AAAA,MAC/B;AACA,kBAAY,IAAI;AAAA,IAClB,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,SAAS;AAAA,EAAC;AACZ;;;ACbO,IAAM,mBAAN,MAAuB;AAAA,EAAvB;AACL,SAAQ,SAAS;AAAA;AAAA;AAAA,EAGjB,KAAK,MAAwB;AAC3B,SAAK,UAAU;AACf,WAAO,KAAK,QAAQ,KAAK;AAAA,EAC3B;AAAA;AAAA,EAGA,QAAkB;AAChB,UAAM,YAAY,KAAK,QAAQ,IAAI;AACnC,UAAM,OAAO,KAAK,OAAO,KAAK;AAC9B,SAAK,SAAS;AACd,QAAI,KAAM,WAAU,KAAK,IAAI;AAC7B,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAQ;AACN,SAAK,SAAS;AAAA,EAChB;AAAA,EAEQ,QAAQ,KAAwB;AACtC,UAAM,YAAsB,CAAC;AAC7B,UAAM,QAAQ;AACd,QAAI;AACJ,QAAI,YAAY;AAEhB,YAAQ,QAAQ,MAAM,KAAK,KAAK,MAAM,OAAO,MAAM;AAGjD,UAAI,CAAC,OAAO,MAAM,cAAc,KAAK,OAAO,OAAQ;AACpD,YAAM,WAAW,MAAM,CAAC,EAAE,KAAK;AAC/B,UAAI,SAAU,WAAU,KAAK,QAAQ;AACrC,kBAAY,MAAM;AAAA,IACpB;AAEA,SAAK,SAAS,KAAK,OAAO,MAAM,SAAS;AACzC,WAAO;AAAA,EACT;AACF;;;ACnCO,IAAe,cAAf,cAAmC,IAAI;AAAA,EAAvC;AAAA;AACL,SAAQ,WAAW,IAAI,iBAAiB;AACxC,SAAQ,QAAkB,CAAC;AAC3B,SAAQ,WAAW;AAInB;AAAA;AAAA,SAAQ,aAAa;AACrB,SAAQ,UAAU;AAClB;AAAA,SAAQ,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBb,UAAU,OAAwB;AAC1C,QAAI,KAAK,iBAAiB,KAAK,QAAS,QAAO;AAC/C,QAAI,MAAM,OAAQ,MAAK,KAAK,SAAS,KAAK;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAsB;AAC1B,UAAM,aAAa,EAAE,KAAK;AAC1B,QAAI,UAAU;AASd,UAAM,YAAY,MAAM;AACtB,UAAI,QAAS,QAAO;AAEpB,UAAI,KAAK,eAAe,WAAY,QAAO;AAC3C,WAAK;AACL,gBAAU,KAAK;AACf,WAAK,SAAS,MAAM;AACpB,aAAO;AAAA,IACT;AAEA,eAAW,GAAG,QAAQ,CAAC,UAAkB;AACvC,UAAI,CAAC,UAAU,EAAG;AAClB,UAAI,YAAY,KAAK,QAAS;AAC9B,WAAK,QAAQ,SAAS,KAAK,SAAS,KAAK,MAAM,SAAS,OAAO,CAAC,CAAC;AAAA,IACnE,CAAC;AAED,eAAW,GAAG,SAAS,CAAC,UAAU;AAChC,WAAK,IAAI,wBAAwB,KAAK;AAAA,IACxC,CAAC;AAED,eAAW,GAAG,OAAO,MAAM;AAEzB,UAAI,CAAC,WAAW,YAAY,KAAK,QAAS;AAC1C,WAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA,EAEA,SAAS;AACP,SAAK,IAAI,QAAQ;AACjB,SAAK;AAEL,SAAK;AACL,SAAK,SAAS,MAAM;AACpB,SAAK,QAAQ,CAAC;AACd,SAAK,YAAY,MAAM;AACvB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEQ,QAAQ,SAAiB,WAAqB;AACpD,QAAI,UAAU,WAAW,EAAG;AAC5B,QAAI,YAAY,KAAK,QAAS;AAC9B,SAAK,MAAM,KAAK,GAAG,SAAS;AAC5B,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,MAAc,QAAQ;AACpB,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAEhB,WAAO,KAAK,MAAM,SAAS,GAAG;AAC5B,YAAM,UAAU,KAAK;AACrB,WAAK,eAAe;AACpB,YAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,YAAM,aAAa,IAAI,gBAAgB;AACvC,WAAK,aAAa;AAElB,UAAI;AACF,aAAK,IAAI,kBAAkB,IAAI,GAAG;AAClC,cAAM,QAAQ,MAAM,KAAK,WAAW,MAAM,WAAW,MAAM;AAE3D,YAAI,YAAY,KAAK,QAAS;AAC9B,YAAI,OAAO,OAAQ,MAAK,KAAK,SAAS,KAAK;AAAA,MAC7C,SAAS,OAAO;AAEd,YAAI,YAAY,KAAK,QAAS;AAC9B,aAAK,IAAI,6BAA6B,KAAK;AAC3C,aAAK,KAAK,UAAU,CAAC,MAAM,GAAG,KAAK,KAAK,CAAC;AACzC,aAAK,QAAQ,CAAC;AAAA,MAChB,UAAE;AACA,YAAI,KAAK,eAAe,WAAY,MAAK,aAAa;AAAA,MACxD;AAAA,IACF;AAEA,SAAK,WAAW;AAEhB,QAAI,KAAK,MAAM,SAAS,EAAG,MAAK,MAAM;AAAA,EACxC;AACF;;;AC5IA,eAAsB,cACpB,QACA,UACqB;AACrB,SAAO,IAAI,QAAoB,CAAC,SAAS,WAAW;AAElD,UAAM,UAAU,WAAW,MAAM;AAC/B,aAAO,IAAI,oCAA0C,gBAAgB,CAAC;AAAA,IACxE,GAAG,GAAI;AAEP,UAAM,WAAW,CAAC,YAAoB;AAEpC,mBAAa,OAAO;AACpB,aAAO,IAAI,WAAW,QAAQ;AAE9B,UAAI;AAEF,cAAM,SAAS,SAAS,KAAK,MAAM,OAAO,CAAC;AAC3C,gBAAQ,MAAM;AAAA,MAChB,SAAS,OAAO;AACd,eAAO,IAAI,oCAA0C,gBAAgB,CAAC;AAAA,MACxE;AAAA,IACF;AAGA,WAAO,GAAG,WAAW,QAAQ;AAAA,EAC/B,CAAC;AACH;","names":["MicdropErrorCode","EventEmitter","PassThrough","MicdropClientCommands","MicdropServerCommands","EventEmitter","PassThrough","EventEmitter","EventEmitter","PassThrough","PassThrough","PassThrough","EventEmitter","PassThrough","PassThrough","PassThrough"]}
1
+ {"version":3,"sources":["../src/agent/Agent.ts","../src/agent/tools.ts","../src/Logger.ts","../src/agent/FallbackAgent.ts","../src/agent/MockAgent.ts","../src/audio/Pcm16Resampler.ts","../src/audio/pcm16.ts","../src/errors.ts","../src/MicdropServer.ts","../src/types.ts","../src/recorder/MicdropRecorder.ts","../src/stt/STT.ts","../src/stt/MockSTT.ts","../src/stt/FallbackSTT.ts","../src/tts/FallbackTTS.ts","../src/tts/TTS.ts","../src/tts/MockTTS.ts","../src/tts/SentenceSplitter.ts","../src/tts/SentenceTTS.ts","../src/waitForParams.ts"],"sourcesContent":["import { EventEmitter } from 'eventemitter3'\nimport { PassThrough, Readable, Writable } from 'stream'\nimport type { z } from 'zod'\nimport { Logger } from '../Logger'\nimport {\n MicdropAnswerMetadata,\n MicdropConversation,\n MicdropConversationItem,\n MicdropConversationMessage,\n MicdropConversationToolCall,\n MicdropConversationToolResult,\n MicdropToolCall,\n} from '../types'\nimport {\n AUTO_END_CALL_PROMPT,\n AUTO_END_CALL_TOOL_NAME,\n AUTO_IGNORE_USER_NOISE_PROMPT,\n AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n AUTO_SEMANTIC_TURN_PROMPT,\n AUTO_SEMANTIC_TURN_TOOL_NAME,\n Tool,\n} from './tools'\n\nexport interface AgentOptions {\n systemPrompt: string\n\n // Enable auto ending of the call when user asks to end the call\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoEndCall?: boolean | string\n\n // Enable detection of an incomplete sentence, and skip the answer (assistant waits)\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoSemanticTurn?: boolean | string\n\n // Ignore of the last user message when it's meaningless\n // You can provide a custom prompt to use instead of the default one by passing a string\n autoIgnoreUserNoise?: boolean | string\n\n // Extract a value from the answer\n // Value must be at the end of the answer, in JSON or between tags\n extract?: ExtractJsonOptions | ExtractTagOptions\n\n // Function called before any answer is generated\n // Return true to skip generation\n onBeforeAnswer?: (\n this: Agent,\n stream: Writable\n ) => void | boolean | Promise<boolean>\n}\n\nexport interface AgentEvents {\n Message: [MicdropConversationItem]\n CancelLastUserMessage: []\n SkipAnswer: []\n EndCall: []\n ToolCall: [MicdropToolCall]\n // Emitted when the agent gives up generating an answer (e.g. after exhausting\n // its retries). Used by FallbackAgent to switch to the next agent.\n Failed: []\n}\n\nexport interface ExtractOptions {\n callback?: (value: string) => void\n saveInMetadata?: boolean\n}\n\nexport interface ExtractJsonOptions extends ExtractOptions {\n json: true\n callback?: (value: any) => void\n}\n\nexport interface ExtractTagOptions extends ExtractOptions {\n startTag: string\n endTag: string\n}\n\nexport abstract class Agent<\n Options extends AgentOptions = AgentOptions,\n> extends EventEmitter<AgentEvents> {\n public logger?: Logger\n public conversation: MicdropConversation\n public tools: Tool[]\n\n protected answerCount = 0\n protected answering = false\n\n constructor(protected options: Options) {\n super()\n this.conversation = [{ role: 'system', content: options.systemPrompt }]\n this.tools = this.getDefaultTools()\n }\n\n protected abstract generateAnswer(stream: PassThrough): Promise<void>\n abstract cancel(): void\n\n answer(): Readable {\n this.log('Start answering')\n const answerCount = ++this.answerCount\n const stream = new PassThrough()\n this.answering = true\n\n Promise.resolve()\n // Call hook onBeforeAnswer\n .then(() => this.options.onBeforeAnswer?.bind(this)(stream))\n // Generate answer (if not skipped)\n .then((skip) => {\n if (skip) return\n return this.generateAnswer(stream)\n })\n // End stream\n .finally(() => {\n if (stream.writable) {\n stream.end()\n }\n if (answerCount === this.answerCount) {\n this.answering = false\n }\n })\n\n return stream\n }\n\n addUserMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('user', text, metadata)\n }\n\n addAssistantMessage(text: string, metadata?: MicdropAnswerMetadata) {\n this.addMessage('assistant', text, metadata)\n }\n\n addTool<Schema extends z.ZodObject>(tool: Tool<Schema>) {\n this.tools.push(tool)\n }\n\n removeTool(name: string) {\n const index = this.tools.findIndex((tool) => tool.name === name)\n if (index !== -1) {\n this.tools.splice(index, 1)\n }\n }\n\n getTool(name: string): Tool | undefined {\n return this.tools.find((tool) => tool.name === name)\n }\n\n addMessage(\n role: 'user' | 'assistant' | 'system',\n text: string,\n metadata?: MicdropAnswerMetadata\n ) {\n // A turn can carry no text at all, typically when the LLM answered with a\n // tool call only. Keeping it would send an empty message back to the LLM on\n // the next turn, and emit a Message event that consumers store as an empty\n // exchange in their transcripts.\n if (text.trim() === '') {\n this.log(`Skipping empty ${role} message`)\n return\n }\n\n this.log(`Adding ${role} message to conversation: ${text}`)\n const message: MicdropConversationMessage = {\n role,\n content: text,\n metadata,\n }\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n addToolMessage(\n message: MicdropConversationToolCall | MicdropConversationToolResult\n ) {\n this.log('Adding tool message:', message)\n this.conversation.push(message)\n this.emit('Message', message)\n }\n\n protected endCall() {\n this.log('Ending call')\n this.emit('EndCall')\n }\n\n protected cancelLastUserMessage() {\n this.log('Cancelling last user message')\n const lastMessageIndex = this.conversation.findLastIndex(\n (message) => message.role === 'user'\n )\n if (lastMessageIndex !== -1) {\n this.conversation.splice(lastMessageIndex, 1)\n }\n this.emit('CancelLastUserMessage')\n }\n\n protected skipAnswer() {\n this.log('Skipping answer')\n this.emit('SkipAnswer')\n }\n\n protected getDefaultTools() {\n const tools: Tool[] = []\n if (this.options.autoEndCall) {\n tools.push({\n name: AUTO_END_CALL_TOOL_NAME,\n description:\n typeof this.options.autoEndCall === 'string'\n ? this.options.autoEndCall\n : AUTO_END_CALL_PROMPT,\n execute: (_input, agent) => agent.endCall(),\n })\n }\n if (this.options.autoSemanticTurn) {\n tools.push({\n name: AUTO_SEMANTIC_TURN_TOOL_NAME,\n description:\n typeof this.options.autoSemanticTurn === 'string'\n ? this.options.autoSemanticTurn\n : AUTO_SEMANTIC_TURN_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.skipAnswer(),\n })\n }\n if (this.options.autoIgnoreUserNoise) {\n tools.push({\n name: AUTO_IGNORE_USER_NOISE_TOOL_NAME,\n description:\n typeof this.options.autoIgnoreUserNoise === 'string'\n ? this.options.autoIgnoreUserNoise\n : AUTO_IGNORE_USER_NOISE_PROMPT,\n skipAnswer: true,\n execute: (_input, agent) => agent.cancelLastUserMessage(),\n })\n }\n return tools\n }\n\n protected async executeTool(toolCall: MicdropConversationToolCall) {\n try {\n const tool = this.getTool(toolCall.toolName)\n if (!tool) {\n throw new Error(`Tool not found \"${toolCall.toolName}\"`)\n }\n\n this.log('Executing tool:', toolCall.toolName, toolCall.parameters)\n\n // Save tool call in conversation\n this.addToolMessage(toolCall)\n\n const parameters = JSON.parse(toolCall.parameters)\n const output = tool.execute ? await tool.execute(parameters, this) : {}\n\n // Save tool result in conversation\n this.addToolMessage({\n role: 'tool_result',\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n output: JSON.stringify(output ?? null),\n })\n\n // Emit output\n if (tool.emitOutput) {\n this.emit('ToolCall', {\n name: toolCall.toolName,\n parameters,\n output,\n })\n }\n\n return {\n output,\n skipAnswer: tool.skipAnswer,\n }\n } catch (error: any) {\n console.error('[OpenaiAgent] Error executing tool:', error)\n return {\n output: {\n error: error.message,\n },\n }\n }\n }\n\n protected getExtractOptions(): ExtractTagOptions | undefined {\n const extract = this.options.extract\n if (!extract) return undefined\n if ('json' in extract && extract.json) {\n return { ...extract, startTag: '{', endTag: '}' }\n }\n if ('startTag' in extract && 'endTag' in extract) {\n return extract\n }\n return undefined\n }\n\n public extract(message: string) {\n const extractOptions = this.getExtractOptions()\n let metadata: MicdropAnswerMetadata | undefined = undefined\n\n // Extract value?\n if (extractOptions) {\n const startTagIndex = message.indexOf(extractOptions.startTag)\n if (startTagIndex !== -1) {\n // Find end tag\n let endTagIndex = message.lastIndexOf(extractOptions.endTag)\n if (endTagIndex === -1) endTagIndex = message.length + 1\n else endTagIndex += extractOptions.endTag.length\n const extractedText = message.slice(startTagIndex, endTagIndex).trim()\n\n // Parse extracted value\n try {\n const extractedValue =\n 'json' in extractOptions && extractOptions.json\n ? JSON.parse(extractedText)\n : extractedText\n\n // Call callback\n if (extractOptions.callback) {\n extractOptions.callback(extractedValue)\n }\n\n // Save in metadata\n if (extractOptions.saveInMetadata) {\n metadata = { extracted: extractedValue }\n }\n } catch (error) {\n console.error(\n `[OpenaiAgent] Error parsing extracted value (${extractedText}):`,\n error\n )\n }\n\n // Remove extracted value from message\n message = message.slice(0, startTagIndex).trimEnd()\n }\n }\n return { message, metadata }\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n this.cancel()\n }\n}\n","import type { z } from 'zod'\nimport type { Agent } from './Agent'\n\nexport interface Tool<Schema extends z.ZodObject = z.ZodObject> {\n name: string\n description: string\n inputSchema?: Schema\n // The executing agent is passed as context so tools stay portable (no binding\n // to a specific agent instance), which lets them be shared between agents.\n execute?: (input: z.infer<Schema>, agent: Agent) => any | Promise<any>\n skipAnswer?: boolean\n emitOutput?: boolean\n}\n\nexport const AUTO_END_CALL_TOOL_NAME = 'end_call'\nexport const AUTO_END_CALL_PROMPT =\n 'Call this tool only if user asks to end the call'\n\nexport const AUTO_SEMANTIC_TURN_TOOL_NAME = 'semantic_turn'\nexport const AUTO_SEMANTIC_TURN_PROMPT =\n 'Call this tool only if last user message is obviously an incomplete sentence that you need to wait for the end before answering'\n\nexport const AUTO_IGNORE_USER_NOISE_TOOL_NAME = 'ignore_user_noise'\nexport const AUTO_IGNORE_USER_NOISE_PROMPT =\n 'Call this tool only if last user message is just an interjection or a sound that expresses emotion, hesitation, or reaction (ex: \"Uh\", \"Ahem\", \"Hmm\", \"Ah\") but doesn\\'t carry any clear meaning like agreeing, refusing, or commanding'\n","export class Logger {\n constructor(public name: string) {}\n\n log(...message: any[]) {\n const time = process.uptime().toFixed(3)\n console.log(`[${this.name} ${time}]`, ...message)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Logger } from '../Logger'\nimport { MicdropConversationItem, MicdropToolCall } from '../types'\nimport { Agent } from './Agent'\n\nexport interface FallbackAgentOptions {\n factories: Array<() => Agent>\n}\n\nexport class FallbackAgent extends Agent {\n private agent: Agent | null = null\n private agentIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly fallbackOptions: FallbackAgentOptions) {\n super({ systemPrompt: '' })\n if (this.fallbackOptions.factories.length === 0) {\n throw new Error('FallbackAgent: No factories provided')\n }\n this.startNextAgent()\n }\n\n // Delegate extraction to the active agent (extract config lives on children)\n extract(message: string) {\n return this.agent ? this.agent.extract(message) : super.extract(message)\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n // Try each agent once (one full rotation) until one answers successfully.\n // The conversation is shared between agents, so the next agent picks up\n // exactly where the failed one stopped.\n for (\n let attempt = 0;\n attempt < this.fallbackOptions.factories.length;\n attempt++\n ) {\n const agent = this.agent\n if (!agent) return\n\n let failed = false\n const onFailed = () => {\n failed = true\n }\n agent.once('Failed', onFailed)\n\n try {\n await this.pipeAnswer(agent, stream)\n } finally {\n agent.off('Failed', onFailed)\n }\n\n if (!failed) return\n\n this.log('Agent failed, trying next agent')\n this.startNextAgent()\n }\n\n // Every agent failed within this rotation: report it so an outer consumer\n // (e.g. a wrapping FallbackAgent) can react.\n this.log('All agents failed')\n this.emit('Failed')\n }\n\n cancel() {\n this.agent?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.agent?.destroy()\n this.agent = null\n this.agentIndex = -1\n }\n\n // Run the child agent and forward its answer chunks to our own stream\n private pipeAnswer(agent: Agent, stream: PassThrough): Promise<void> {\n return new Promise((resolve) => {\n const answerStream = agent.answer()\n answerStream.on('data', (chunk) => {\n if (stream.writable) {\n stream.write(chunk)\n }\n })\n answerStream.on('end', resolve)\n answerStream.on('error', resolve)\n })\n }\n\n private startNextAgent() {\n this.agentIndex++\n if (this.agentIndex >= this.fallbackOptions.factories.length) {\n this.agentIndex = 0\n }\n\n const previousAgent = this.agent\n const isFirstAgent = previousAgent === null\n const agent = this.fallbackOptions.factories[this.agentIndex]()\n this.agent = agent\n\n // Share the conversation and tools between the fallback and the child agent.\n // Both are now portable (tools no longer bind to a specific instance), so a\n // single reference is shared, exactly like the conversation.\n if (isFirstAgent) {\n // Adopt the first agent's conversation and tools (keeps its system prompt)\n this.conversation = agent.conversation\n this.tools = agent.tools\n } else {\n // Keep the accumulated history, but use the new agent's system prompt\n if (agent.conversation[0]?.role === 'system') {\n this.conversation[0] = agent.conversation[0]\n }\n agent.conversation = this.conversation\n agent.tools = this.tools\n }\n\n // Forward events from the child agent\n agent.on('Message', this.onMessage)\n agent.on('CancelLastUserMessage', this.onCancelLastUserMessage)\n agent.on('SkipAnswer', this.onSkipAnswer)\n agent.on('EndCall', this.onEndCall)\n agent.on('ToolCall', this.onToolCall)\n\n // Destroy the previous agent (after moving the conversation over)\n previousAgent?.destroy()\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.agent && this.logger) {\n this.agent.logger = new Logger(this.agent.constructor.name)\n }\n }, 0)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n this.emit('Message', message)\n }\n\n private onCancelLastUserMessage = () => {\n this.emit('CancelLastUserMessage')\n }\n\n private onSkipAnswer = () => {\n this.emit('SkipAnswer')\n }\n\n private onEndCall = () => {\n this.emit('EndCall')\n }\n\n private onToolCall = (toolCall: MicdropToolCall) => {\n this.emit('ToolCall', toolCall)\n }\n}\n","import { PassThrough } from 'stream'\nimport { Agent } from './Agent'\n\nexport class MockAgent extends Agent {\n private i = 0\n\n constructor() {\n super({ systemPrompt: '' })\n }\n\n protected async generateAnswer(stream: PassThrough): Promise<void> {\n const message = `Assistant Message ${this.i++}`\n this.addAssistantMessage(message)\n stream.write(message)\n }\n\n cancel() {}\n}\n","/**\n * Streaming linear-interpolation resampler for PCM16 mono audio.\n *\n * Works in both directions (up or downsampling). It is stateful: it handles\n * arbitrary byte boundaries (a network chunk can split a 16-bit sample) and\n * keeps the fractional sample position continuous across chunks, so feeding a\n * stream chunk by chunk yields the same result as resampling it in one go.\n *\n * Providers use it to bridge their own rate with the 16kHz PCM16 the Micdrop\n * client records and plays: OpenaiSTT (16kHz -> 24kHz, the GA Realtime API\n * requires >= 24kHz), OpenaiTTS and KokoroTTS (24kHz output -> 16kHz).\n */\nexport class Pcm16Resampler {\n private readonly step: number\n private leftover: Buffer<ArrayBufferLike> = Buffer.alloc(0)\n private pos = 0 // Fractional position into the first sample of the buffer\n\n constructor(inRate: number, outRate: number) {\n this.step = inRate / outRate\n }\n\n // Reset to the initial state, to resample a new independent stream\n // (e.g. resending buffered audio after a reconnection).\n reset() {\n this.leftover = Buffer.alloc(0)\n this.pos = 0\n }\n\n process(chunk: Buffer): Buffer {\n const buf = this.leftover.length\n ? Buffer.concat([this.leftover, chunk])\n : chunk\n const samples = Math.floor(buf.length / 2)\n\n // Need at least 2 samples to interpolate\n if (samples < 2) {\n this.leftover = buf\n return Buffer.alloc(0)\n }\n\n const out: number[] = []\n let p = this.pos\n while (Math.floor(p) + 1 < samples) {\n const i = Math.floor(p)\n const frac = p - i\n const s0 = buf.readInt16LE(i * 2)\n const s1 = buf.readInt16LE((i + 1) * 2)\n out.push(Math.round(s0 + (s1 - s0) * frac))\n p += this.step\n }\n\n // Keep the last still-needed sample (and any trailing odd byte) for the\n // next chunk, and carry the fractional position relative to it.\n const consumed = Math.floor(p)\n this.pos = p - consumed\n this.leftover = buf.subarray(consumed * 2)\n\n const result = Buffer.alloc(out.length * 2)\n for (let k = 0; k < out.length; k++) {\n result.writeInt16LE(out[k], k * 2)\n }\n return result\n }\n}\n","/**\n * Conversions between the PCM16 buffers exchanged with the Micdrop client and\n * the float samples that local speech models read and write.\n *\n * Both formats are mono. PCM16 is signed 16-bit little-endian, floats are in\n * the [-1, 1] range. Only the scale changes, the sample rate is left alone.\n */\n\nconst PCM16_MAX = 32767\nconst PCM16_MIN = -32768\n\n/** Turns float samples into a PCM16 buffer, clamping anything out of range. */\nexport function float32ToPcm16(samples: Float32Array): Buffer {\n const buffer = Buffer.alloc(samples.length * 2)\n for (let i = 0; i < samples.length; i++) {\n const scaled = Math.round(samples[i] * PCM16_MAX)\n const clamped = Math.max(PCM16_MIN, Math.min(PCM16_MAX, scaled))\n buffer.writeInt16LE(clamped, i * 2)\n }\n return buffer\n}\n\n/**\n * Turns a PCM16 buffer into float samples.\n *\n * A trailing odd byte is dropped: it is half of a sample whose other half has\n * not arrived, and a caller feeding whole utterances never produces one.\n */\nexport function pcm16ToFloat32(buffer: Buffer): Float32Array {\n const length = Math.floor(buffer.length / 2)\n const samples = new Float32Array(length)\n for (let i = 0; i < length; i++) {\n samples[i] = buffer.readInt16LE(i * 2) / PCM16_MAX\n }\n return samples\n}\n","import WebSocket from 'ws'\n\nexport enum MicdropErrorCode {\n BadRequest = 4400,\n Unauthorized = 4401,\n NotFound = 4404,\n}\n\nexport class MicdropError extends Error {\n code: number\n\n constructor(code: number, message: string) {\n super(message)\n this.code = code\n }\n}\n\nexport function handleError(socket: WebSocket, error: unknown) {\n if (error instanceof MicdropError) {\n socket.close(error.code, error.message)\n } else {\n console.error(error)\n socket.close(1011)\n }\n socket.terminate()\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Duplex, PassThrough, Readable } from 'stream'\nimport { WebSocket } from 'ws'\nimport type { Agent } from './agent'\nimport { pcm16ToFloat32 } from './audio'\nimport { Logger } from './Logger'\nimport type { STT } from './stt'\nimport type { TTS } from './tts'\nimport {\n MicdropCallSummary,\n MicdropClientCommands,\n MicdropConversationItem,\n MicdropServerCommands,\n TurnDetector,\n} from './types'\n\n/** Rate the client records at, and sends its chunks in */\nconst USER_SAMPLE_RATE = 16000\n\n/**\n * How long an answer waits once the detector asked for the rest of a sentence.\n *\n * The way out of a wrong verdict: a speaker who never comes back still gets an\n * answer instead of a call that goes quiet.\n */\nconst DEFAULT_TURN_MAX_WAIT = 4000 // ms\n\nexport interface MicdropServerEvents {\n End: [MicdropCallSummary]\n UserAudio: [Buffer]\n AssistantAudio: [Buffer]\n}\n\nexport interface MicdropConfig {\n firstMessage?: string\n generateFirstMessage?: boolean\n agent: Agent\n stt: STT\n tts: TTS\n\n /**\n * Waits for the rest of the sentence when the speaker paused in the middle\n * of one, instead of answering an unfinished question.\n *\n * Prefer running the detector in the client, which reaches the same decision\n * without the round trip and can then close its turns sooner. This is the\n * option for the browsers where the model has nowhere to run.\n */\n turnDetector?: TurnDetector\n\n /** How long to wait for the rest of a sentence, 4000 ms by default */\n turnMaxWait?: number\n}\n\nexport class MicdropServer extends EventEmitter<MicdropServerEvents> {\n public socket: WebSocket | null = null\n public config: MicdropConfig | null = null\n public logger?: Logger\n\n private startTime = Date.now()\n private lastMessageSpeeched?: MicdropConversationItem\n\n // Queue system for operations\n private operationQueue: Array<() => Promise<void>> = []\n private isProcessingQueue = false\n\n // When user is speaking, we're streaming chunks for STT\n private currentUserStream?: Duplex\n private userSpeechChunks = 0\n // Asked as soon as the speaker pauses, so it weighs that stretch of audio\n // and not the next one\n private turnComplete?: Promise<boolean>\n private heldTurnTimer?: ReturnType<typeof setTimeout>\n\n constructor(socket: WebSocket, config: MicdropConfig) {\n super()\n this.socket = socket\n this.config = config\n this.log(`Call started`)\n\n // Setup STT\n this.config.stt.on('Transcript', this.onTranscriptSTT)\n\n // Setup TTS\n this.config.tts.on('Audio', this.onAudioTTS)\n\n // Setup agent\n this.config.agent.on('Message', (message) =>\n this.socket?.send(\n `${MicdropServerCommands.Message} ${JSON.stringify(message)}`\n )\n )\n this.config.agent.on('CancelLastUserMessage', () =>\n this.socket?.send(MicdropServerCommands.CancelLastUserMessage)\n )\n this.config.agent.on('SkipAnswer', () =>\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n )\n this.config.agent.on('EndCall', () =>\n this.socket?.send(MicdropServerCommands.EndCall)\n )\n this.config.agent.on('ToolCall', (toolCall) =>\n this.socket?.send(\n `${MicdropServerCommands.ToolCall} ${JSON.stringify(toolCall)}`\n )\n )\n\n // Assistant speaks first\n // Deferred so consumers (e.g. MicdropRecorder) can subscribe to agent\n // events before the first message is added to the conversation.\n queueMicrotask(() => this.sendFirstMessage())\n\n // Listen to events\n socket.on('close', this.onClose)\n socket.on('message', this.onMessage)\n }\n\n private log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n private async processQueue() {\n if (this.isProcessingQueue || this.operationQueue.length === 0) return\n\n this.isProcessingQueue = true\n\n while (this.operationQueue.length > 0) {\n const operation = this.operationQueue.shift()\n if (operation) {\n try {\n await operation()\n } catch (error) {\n this.log('Error processing queued operation:', error)\n }\n }\n }\n\n this.isProcessingQueue = false\n }\n\n private queueOperation(operation: () => Promise<void>) {\n this.operationQueue.push(operation)\n this.processQueue()\n }\n\n public cancel() {\n this.config?.tts.cancel()\n this.config?.agent.cancel()\n // Clear the queue\n this.operationQueue = []\n }\n\n private onClose = () => {\n this.releaseHeldTurn()\n if (!this.config) return\n this.log('Connection closed')\n const duration = Math.round((Date.now() - this.startTime) / 1000)\n\n // Destroy instances\n this.config.agent.destroy()\n this.config.stt.destroy()\n this.config.tts.destroy()\n\n // Emit End event\n this.emit('End', {\n conversation: this.config.agent.conversation,\n duration,\n })\n\n // Unset params\n this.socket = null\n this.config = null\n }\n\n private onMessage = async (message: Buffer) => {\n if (message.byteLength === 0) return\n if (!Buffer.isBuffer(message)) {\n this.log('Message is not a buffer')\n return\n }\n\n // Commands\n if (message.byteLength < 15) {\n const cmd = message.toString()\n this.log(`Command: ${cmd}`)\n\n if (cmd === MicdropClientCommands.StartSpeaking) {\n // User started speaking\n this.onStartSpeaking()\n } else if (cmd === MicdropClientCommands.Mute) {\n // User muted the call\n this.onMute()\n } else if (cmd === MicdropClientCommands.StopSpeaking) {\n // User stopped speaking\n this.onStopSpeaking()\n }\n }\n\n // Audio chunk\n else if (this.currentUserStream) {\n this.onUserAudio(message)\n }\n }\n\n private onUserAudio(chunk: Buffer) {\n this.log(`Received chunk (${chunk.byteLength} bytes)`)\n this.currentUserStream?.write(chunk)\n this.userSpeechChunks++\n this.config?.turnDetector?.push(pcm16ToFloat32(chunk), USER_SAMPLE_RATE)\n this.emit('UserAudio', chunk)\n }\n\n private onMute() {\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.cancel()\n }\n\n private onStartSpeaking() {\n if (!this.config) return\n this.userSpeechChunks = 0\n this.currentUserStream?.end()\n this.currentUserStream = new PassThrough()\n this.config.turnDetector?.reset()\n this.turnComplete = undefined\n // The rest of the sentence is arriving, so the deadline can go\n this.releaseHeldTurn()\n this.config.stt.transcribe(this.currentUserStream)\n this.cancel()\n }\n\n private onStopSpeaking() {\n const hasNoUserSpeech =\n !this.currentUserStream || this.userSpeechChunks === 0\n this.currentUserStream?.end()\n this.currentUserStream = undefined\n this.userSpeechChunks = 0\n\n // If user is not speaking or no chunks were received, skip\n if (hasNoUserSpeech) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n // Weigh the stretch of audio that just ended, before the next one starts\n this.turnComplete = this.predictTurnComplete()\n\n const conversation = this.config?.agent.conversation\n const lastMessage = conversation?.[conversation.length - 1]\n if (\n lastMessage?.role === 'user' &&\n this.lastMessageSpeeched !== lastMessage\n ) {\n this.log(\n 'User stopped speaking and a transcript already exists, answering'\n )\n this.answerUserTurn()\n }\n }\n\n private async predictTurnComplete(): Promise<boolean> {\n const detector = this.config?.turnDetector\n if (!detector) return true\n try {\n const { complete } = await detector.predict()\n this.log(`Turn sounds ${complete ? 'finished' : 'unfinished'}`)\n return complete\n } catch (error) {\n this.log(`Turn detection failed: ${error}`)\n return true\n }\n }\n\n /** Answers, unless the sentence sounds like it has more coming */\n private async answerUserTurn() {\n const complete = await (this.turnComplete ?? Promise.resolve(true))\n if (!complete) {\n this.log('Waiting for the rest of the sentence')\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n this.holdTurn()\n return\n }\n this.releaseHeldTurn()\n this.cancel()\n this.answer()\n }\n\n /**\n * Answers anyway if the rest of the sentence never comes.\n *\n * Without it, a detector that hears an unfinished sentence where there is\n * none leaves the call silent for good.\n */\n private holdTurn() {\n this.releaseHeldTurn()\n this.heldTurnTimer = setTimeout(() => {\n this.heldTurnTimer = undefined\n this.log('Nothing more came, answering')\n this.cancel()\n this.answer()\n }, this.config?.turnMaxWait ?? DEFAULT_TURN_MAX_WAIT)\n }\n\n private releaseHeldTurn() {\n if (!this.heldTurnTimer) return\n clearTimeout(this.heldTurnTimer)\n this.heldTurnTimer = undefined\n }\n\n private onTranscriptSTT = async (transcript: string) => {\n if (!this.config) return\n\n // Skip answer if transcript is empty\n if (transcript === '') {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n return\n }\n\n this.log(`User transcript: \"${transcript}\"`)\n this.config.agent.addUserMessage(transcript)\n\n // Answer if user stopped speaking\n if (!this.currentUserStream) {\n this.log('User stopped speaking, answering')\n this.answerUserTurn()\n }\n }\n\n private onAudioTTS = (audio: Buffer) => {\n if (!this.socket) return\n this.log(`Send audio chunk (${audio.byteLength} bytes)`)\n this.socket.send(audio)\n this.emit('AssistantAudio', audio)\n }\n\n private sendFirstMessage() {\n if (!this.config) return\n if (this.config.firstMessage) {\n // Send first message\n this.config.agent.addAssistantMessage(this.config.firstMessage)\n this.speak(this.config.firstMessage)\n } else if (this.config.generateFirstMessage) {\n // Generate first message\n this.answer()\n } else {\n // Skip answer if no first message is provided\n // to avoid keeping the client in a processing state\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n }\n }\n\n public answer() {\n this.queueOperation(async () => {\n await this._answer()\n })\n }\n\n private async _answer() {\n if (!this.config) return\n\n // Prevent answering twice\n const lastMessage =\n this.config.agent.conversation[this.config.agent.conversation.length - 1]\n if (this.lastMessageSpeeched === lastMessage) {\n this.log('Already answered, skipping')\n return\n }\n this.lastMessageSpeeched = lastMessage\n\n try {\n // LLM: Generate answer\n const stream = this.config.agent.answer()\n\n // TTS: Generate answer audio, unless there is nothing to say.\n //\n // An answer can be skipped after the fact: a tool with skipAnswer, or an\n // onBeforeAnswer hook returning true, ends the stream without a word in\n // it. Handing that empty stream to the TTS opens a synthesis request for\n // nothing, and a provider that stamps each request (Gradium multiplexes\n // this way) then drops the audio of the sentence still playing, so a\n // skipped answer cuts the assistant off mid-word.\n if (await hasContent(stream)) {\n await this._speak(stream)\n }\n } catch (error) {\n this.socket?.send(MicdropServerCommands.SkipAnswer)\n throw error\n }\n }\n\n // Run text-to-speech and send to client\n public speak(message: string | Readable) {\n this.queueOperation(async () => {\n await this._speak(message)\n })\n }\n\n private async _speak(message: string | Readable) {\n if (!this.socket || !this.config) return\n\n // Convert message to stream if needed\n let textStream: Readable\n if (typeof message === 'string') {\n const stream = new PassThrough()\n stream.write(message)\n stream.end()\n textStream = stream\n } else {\n textStream = message\n }\n\n // Run TTS\n this.config.tts.speak(textStream)\n }\n}\n\n/**\n * Resolves true as soon as the stream holds something to read, false if it ends\n * without ever carrying anything.\n *\n * The chunk read to find out is put back, so the consumer that follows sees the\n * whole stream from its first byte.\n */\nfunction hasContent(stream: Readable): Promise<boolean> {\n return new Promise((resolve) => {\n const onReadable = () => {\n const chunk = stream.read()\n if (chunk === null) return\n stream.unshift(chunk)\n done(true)\n }\n const onEnd = () => done(false)\n const done = (result: boolean) => {\n stream.off('readable', onReadable)\n stream.off('end', onEnd)\n resolve(result)\n }\n stream.on('readable', onReadable)\n stream.on('end', onEnd)\n })\n}\n","export enum MicdropClientCommands {\n StartSpeaking = 'StartSpeaking',\n StopSpeaking = 'StopSpeaking',\n Mute = 'Mute',\n}\n\nexport enum MicdropServerCommands {\n Message = 'Message',\n CancelLastUserMessage = 'CancelLastUserMessage',\n SkipAnswer = 'SkipAnswer',\n EndCall = 'EndCall',\n ToolCall = 'ToolCall',\n}\n\n/**\n * Hears whether a sentence has landed, where voice activity detection only\n * hears whether someone is speaking.\n *\n * `SmartTurn` from `@micdrop/smart-turn` implements it, and so can anything\n * else, a call to a service included. Both sides of a call can hold one, the\n * client to decide when its turn ends and the server to decide when to answer.\n */\nexport interface TurnDetector {\n /**\n * Feeds the audio received since the last call\n * @param samples - Mono samples, in the -1..1 range\n * @param sampleRate - Sample rate of `samples`, in Hz\n */\n push(samples: Float32Array, sampleRate?: number): void\n\n /** Answers whether the turn pushed so far sounds finished */\n predict(): Promise<{ complete: boolean }>\n\n /** Starts a new turn, forgetting the previous one */\n reset(): void\n}\n\nexport interface MicdropCallSummary {\n conversation: MicdropConversation\n duration: number\n}\n\nexport type MicdropConversationItem =\n | MicdropConversationMessage\n | MicdropConversationToolCall\n | MicdropConversationToolResult\n\nexport type MicdropConversation = Array<MicdropConversationItem>\n\nexport type MicdropAnswerMetadata = {\n [key: string]: any\n}\n\nexport interface MicdropConversationMessage<\n Data extends MicdropAnswerMetadata = MicdropAnswerMetadata,\n> {\n role: 'system' | 'user' | 'assistant'\n content: string\n metadata?: Data\n}\n\nexport interface MicdropConversationToolCall {\n role: 'tool_call'\n toolCallId: string\n toolName: string\n parameters: string\n}\n\nexport interface MicdropConversationToolResult {\n role: 'tool_result'\n toolCallId: string\n toolName: string\n output: string\n}\n\nexport interface MicdropToolCall {\n name: string\n parameters: any\n output: any\n}\n\nexport type DeepPartial<T> = T extends object\n ? {\n [P in keyof T]?: DeepPartial<T[P]>\n }\n : T\n","import { EventEmitter } from 'eventemitter3'\nimport type { MicdropServer } from '../MicdropServer'\nimport type { MicdropConversationItem } from '../types'\nimport { Logger } from '../Logger'\n\nexport interface AudioMessage {\n buffer: Buffer\n messageIndex: number\n message: string\n role: 'user' | 'assistant'\n}\n\nexport interface MicdropRecorderEvents {\n AudioMessage: [AudioMessage]\n Complete: [AudioMessage[]]\n}\n\nexport class MicdropRecorder extends EventEmitter<MicdropRecorderEvents> {\n public logger?: Logger\n\n private audioMessages: AudioMessage[] = []\n private currentUserChunks: Buffer[] = []\n private currentAssistantChunks: Buffer[] = []\n private lastUserMessageIndex: number = -1\n private lastAssistantMessageIndex: number = -1\n\n constructor(private server: MicdropServer) {\n super()\n this.setupListeners()\n }\n\n private setupListeners() {\n // Listen to audio events from server\n this.server.on('UserAudio', this.onUserAudio)\n this.server.on('AssistantAudio', this.onAssistantAudio)\n this.server.on('End', this.onEnd)\n\n // Listen to message events from agent\n const agent = this.server.config?.agent\n if (agent) {\n agent.on('Message', this.onMessage)\n }\n }\n\n private onUserAudio = (chunk: Buffer) => {\n // Finalize or discard assistant audio when user starts speaking\n if (this.currentAssistantChunks.length > 0) {\n if (this.lastAssistantMessageIndex >= 0) {\n this.finalizeAssistantAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned assistant audio chunks')\n this.currentAssistantChunks = []\n }\n }\n\n this.log('Recording user audio chunk')\n this.currentUserChunks.push(chunk)\n }\n\n private onAssistantAudio = (chunk: Buffer) => {\n // Finalize or discard user audio when assistant starts speaking\n if (this.currentUserChunks.length > 0) {\n if (this.lastUserMessageIndex >= 0) {\n this.finalizeUserAudio()\n } else {\n // Discard orphaned chunks (no associated message)\n this.log('Discarding orphaned user audio chunks')\n this.currentUserChunks = []\n }\n }\n\n this.log('Recording assistant audio chunk')\n this.currentAssistantChunks.push(chunk)\n }\n\n private onMessage = (message: MicdropConversationItem) => {\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const messageIndex = conversation.length - 1\n\n if (message.role === 'user') {\n this.lastUserMessageIndex = messageIndex\n // User audio might already be complete, finalize if we have chunks\n // Audio chunks arrive BEFORE message, so we finalize when we know the message\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n } else if (message.role === 'assistant') {\n this.lastAssistantMessageIndex = messageIndex\n // Don't finalize assistant audio here - chunks can still arrive after message\n }\n }\n\n private finalizeUserAudio() {\n if (this.currentUserChunks.length === 0) return\n if (this.lastUserMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastUserMessageIndex]\n const buffer = Buffer.concat(this.currentUserChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastUserMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'user',\n }\n\n this.log(\n `Finalized user audio: ${buffer.length} bytes, message index ${this.lastUserMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentUserChunks = []\n this.lastUserMessageIndex = -1\n }\n\n private finalizeAssistantAudio() {\n if (this.currentAssistantChunks.length === 0) return\n if (this.lastAssistantMessageIndex < 0) return\n\n const conversation = this.server.config?.agent.conversation\n if (!conversation) return\n\n const message = conversation[this.lastAssistantMessageIndex]\n const buffer = Buffer.concat(this.currentAssistantChunks)\n\n const audioMessage: AudioMessage = {\n buffer,\n messageIndex: this.lastAssistantMessageIndex,\n message: 'content' in message ? message.content : '',\n role: 'assistant',\n }\n\n this.log(\n `Finalized assistant audio: ${buffer.length} bytes, message index ${this.lastAssistantMessageIndex}`\n )\n this.audioMessages.push(audioMessage)\n this.emit('AudioMessage', audioMessage)\n\n // Reset\n this.currentAssistantChunks = []\n this.lastAssistantMessageIndex = -1\n }\n\n private onEnd = () => {\n // Finalize any remaining audio\n if (this.currentUserChunks.length > 0) {\n this.finalizeUserAudio()\n }\n if (this.currentAssistantChunks.length > 0) {\n this.finalizeAssistantAudio()\n }\n\n this.log(`Recording complete: ${this.audioMessages.length} audio messages`)\n this.emit('Complete', this.audioMessages)\n }\n\n public getAudioMessages(): AudioMessage[] {\n return [...this.audioMessages]\n }\n\n public destroy() {\n this.log('Destroyed')\n this.server.off('UserAudio', this.onUserAudio)\n this.server.off('AssistantAudio', this.onAssistantAudio)\n this.server.off('End', this.onEnd)\n\n const agent = this.server.config?.agent\n if (agent) {\n agent.off('Message', this.onMessage)\n }\n\n this.removeAllListeners()\n }\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface STTEvents {\n Transcript: [string]\n Failed: [Buffer[]]\n}\n\nexport abstract class STT extends EventEmitter<STTEvents> {\n public logger?: Logger\n\n // Set stream of audio to transcribe\n abstract transcribe(audioStream: Readable): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.removeAllListeners()\n }\n}\n","import { STT } from './STT'\n\nexport class MockSTT extends STT {\n private i = 0\n\n async transcribe() {\n setTimeout(() => {\n this.emit('Transcript', `User Message ${this.i++}`)\n }, 300)\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { STT } from './STT'\nimport { Logger } from '..'\n\nexport interface FallbackSTTOptions {\n factories: Array<() => STT>\n}\n\nexport class FallbackSTT extends STT {\n private stt: STT | null = null\n private sttIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackSTTOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackSTT: No factories provided')\n }\n this.startNextSTT()\n }\n\n transcribe(audioStream: Readable) {\n this.stt?.transcribe(audioStream)\n }\n\n destroy() {\n super.destroy()\n this.stt?.destroy()\n this.stt = null\n this.sttIndex = -1\n }\n\n private startNextSTT() {\n this.sttIndex++\n if (this.sttIndex >= this.options.factories.length) {\n this.sttIndex = 0\n }\n this.stt?.destroy()\n this.stt = this.options.factories[this.sttIndex]()\n this.stt.on('Transcript', this.onTranscript)\n this.stt.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.stt && this.logger) {\n this.stt.logger = new Logger(this.stt.constructor.name)\n }\n }, 0)\n }\n\n private onTranscript = (transcript: string) => {\n this.emit('Transcript', transcript)\n }\n\n private onFailed = (chunks: Buffer[]) => {\n this.log('STT failed, trying next STT')\n this.startNextSTT()\n\n if (chunks.length > 0) {\n this.log('Sending audio chunks again')\n const stream = new PassThrough()\n this.stt?.transcribe(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\nimport { Logger } from '..'\n\nexport interface FallbackTTSOptions {\n factories: Array<() => TTS>\n}\n\nexport class FallbackTTS extends TTS {\n private tts: TTS | null = null\n private ttsIndex = -1 // Start at -1 because we need to increment it before using it\n\n constructor(private readonly options: FallbackTTSOptions) {\n super()\n if (this.options.factories.length === 0) {\n throw new Error('FallbackTTS: No factories provided')\n }\n this.startNextTTS()\n }\n\n speak(textStream: Readable) {\n this.tts?.speak(textStream)\n }\n\n cancel() {\n this.tts?.cancel()\n }\n\n destroy() {\n super.destroy()\n this.tts?.destroy()\n this.tts = null\n this.ttsIndex = -1\n }\n\n private startNextTTS() {\n this.ttsIndex++\n if (this.ttsIndex >= this.options.factories.length) {\n this.ttsIndex = 0\n }\n this.tts?.destroy()\n this.tts = this.options.factories[this.ttsIndex]()\n this.tts.on('Audio', this.onAudio)\n this.tts.on('Failed', this.onFailed)\n\n // Set logger after event loop\n setTimeout(() => {\n if (this.tts && this.logger) {\n this.tts.logger = new Logger(this.tts.constructor.name)\n }\n }, 0)\n }\n\n private onAudio = (audio: Buffer) => {\n this.emit('Audio', audio)\n }\n\n private onFailed = (chunks: string[]) => {\n this.log('TTS failed, trying next TTS')\n this.startNextTTS()\n\n if (chunks.length > 0) {\n this.log('Sending text chunks again')\n const stream = new PassThrough()\n this.tts?.speak(stream)\n chunks.forEach((chunk) => stream.write(chunk))\n stream.end()\n }\n }\n}\n","import { EventEmitter } from 'eventemitter3'\nimport { Readable } from 'stream'\nimport { Logger } from '../Logger'\n\nexport interface TTSEvents {\n Audio: [Buffer]\n Failed: [string[]]\n}\n\nexport abstract class TTS extends EventEmitter<TTSEvents> {\n public logger?: Logger\n\n abstract speak(textStream: Readable): void\n abstract cancel(): void\n\n protected log(...message: any[]) {\n this.logger?.log(...message)\n }\n\n destroy() {\n this.log('Destroyed')\n this.cancel()\n }\n}\n","import * as fs from 'fs'\nimport { PassThrough, Readable } from 'stream'\nimport { TTS } from './TTS'\n\nexport class MockTTS extends TTS {\n constructor(private audioFilePaths: string[]) {\n super()\n }\n\n speak(textStream: Readable) {\n const audioStream = new PassThrough()\n textStream.once('data', async () => {\n for (const filePath of this.audioFilePaths) {\n await new Promise((resolve) => setTimeout(resolve, 200))\n const audioBuffer = fs.readFileSync(filePath)\n this.log(`Loaded chunk (${audioBuffer.length} bytes)`)\n audioStream.write(audioBuffer)\n }\n audioStream.end()\n })\n return audioStream\n }\n\n cancel() {}\n}\n","/**\n * Cuts a stream of text into sentences as it arrives.\n *\n * Providers that synthesize a whole input at once need complete sentences, and\n * an agent writes its answer token by token. Feeding every fragment as it comes\n * would either cut words in half or wait for the end of the answer, so the text\n * is buffered until a sentence closes and released the moment it does.\n *\n * The splitter is stateful: `push` returns the sentences that are complete,\n * `flush` returns whatever is left when the stream ends.\n */\nexport class SentenceSplitter {\n private buffer = ''\n\n /** Adds text and returns the sentences it completes. */\n push(text: string): string[] {\n this.buffer += text\n return this.extract(false)\n }\n\n /** Returns the sentences left in the buffer and empties it. */\n flush(): string[] {\n const sentences = this.extract(true)\n const rest = this.buffer.trim()\n this.buffer = ''\n if (rest) sentences.push(rest)\n return sentences\n }\n\n /** Drops the buffered text, used when an utterance is cancelled. */\n reset() {\n this.buffer = ''\n }\n\n private extract(end: boolean): string[] {\n const sentences: string[] = []\n const regex = /[\\s\\S]*?[.!?…\\n]+(?=\\s|$)/g\n let match: RegExpExecArray | null\n let lastIndex = 0\n\n while ((match = regex.exec(this.buffer)) !== null) {\n // A sentence ending at the very end of an unfinished stream may still\n // grow, so keep it buffered until more text arrives or the stream ends.\n if (!end && regex.lastIndex === this.buffer.length) break\n const sentence = match[0].trim()\n if (sentence) sentences.push(sentence)\n lastIndex = regex.lastIndex\n }\n\n this.buffer = this.buffer.slice(lastIndex)\n return sentences\n }\n}\n","import { Readable } from 'stream'\nimport { SentenceSplitter } from './SentenceSplitter'\nimport { TTS } from './TTS'\n\n/**\n * Base class for text to speech engines that read a whole input at once.\n *\n * A local model, and a remote endpoint without a streaming interface, cannot\n * be fed the agent's answer token by token. This class buffers the answer into\n * sentences, hands them over one at a time, and emits the audio in the order\n * they were written. Subclasses only have to turn one sentence into PCM16 at\n * the rate the Micdrop client expects.\n *\n * Sentences are synthesized one after the other rather than at once: a local\n * model is single threaded, so racing two sentences through it slows both down\n * without bringing the first word any closer.\n */\nexport abstract class SentenceTTS extends TTS {\n private splitter = new SentenceSplitter()\n private queue: string[] = []\n private draining = false\n private controller?: AbortController\n // Bumped by every speak() and every cancel(), so a call claimed late can tell\n // whether it is still the one that should be heard.\n private generation = 0\n private counter = 0 // Identifies the current speak() call\n private synthesizing = 0 // Stamp of the sentence being synthesized\n\n /**\n * Turns one sentence into PCM16 audio at the client's sample rate.\n *\n * The signal is aborted when the utterance is cancelled, which is the moment\n * to stop a subprocess or an inference that is no longer needed. Returning\n * nothing emits nothing, which is how a cancelled synthesis reports back.\n */\n protected abstract synthesize(\n text: string,\n signal: AbortSignal\n ): Promise<Buffer | undefined>\n\n /**\n * Emits a piece of the sentence being synthesized.\n *\n * A model that generates progressively can hand its chunks over as they\n * come rather than waiting for the sentence to be finished, which brings\n * the first word forward by the duration of that sentence. The false it\n * returns says the utterance was cancelled or replaced, so the generation\n * it comes from can be stopped there.\n */\n protected emitAudio(audio: Buffer): boolean {\n if (this.synthesizing !== this.counter) return false\n if (audio.length) this.emit('Audio', audio)\n return true\n }\n\n speak(textStream: Readable) {\n const generation = ++this.generation\n let counter = 0\n\n // Claiming the call is deferred until there is something to say.\n //\n // Taking the next number right away would drop the utterance still being\n // spoken, since the queue skips anything stamped with an older one. A\n // stream that never carries a word, which is what an answer skipped by a\n // tool or by onBeforeAnswer hands over, would then cut the assistant off\n // and throw away the sentences still queued.\n const claimCall = () => {\n if (counter) return true\n // Cancelled, or superseded by another speak(), before the first word\n if (this.generation !== generation) return false\n this.counter++\n counter = this.counter\n this.splitter.reset()\n return true\n }\n\n textStream.on('data', (chunk: Buffer) => {\n if (!claimCall()) return\n if (counter !== this.counter) return\n this.enqueue(counter, this.splitter.push(chunk.toString('utf-8')))\n })\n\n textStream.on('error', (error) => {\n this.log('Error in text stream', error)\n })\n\n textStream.on('end', () => {\n // Nothing was ever said, so there is nothing left to flush\n if (!counter || counter !== this.counter) return\n this.enqueue(counter, this.splitter.flush())\n })\n }\n\n cancel() {\n this.log('Cancel')\n this.generation++\n // Increment counter to ignore queued work and the sentence in flight\n this.counter++\n this.splitter.reset()\n this.queue = []\n this.controller?.abort()\n this.controller = undefined\n }\n\n private enqueue(counter: number, sentences: string[]) {\n if (sentences.length === 0) return\n if (counter !== this.counter) return\n this.queue.push(...sentences)\n this.drain()\n }\n\n private async drain() {\n if (this.draining) return\n this.draining = true\n\n while (this.queue.length > 0) {\n const counter = this.counter\n this.synthesizing = counter\n const text = this.queue.shift()!\n const controller = new AbortController()\n this.controller = controller\n\n try {\n this.log(`Synthesizing: \"${text}\"`)\n const audio = await this.synthesize(text, controller.signal)\n // The utterance may have been cancelled while it was being synthesized\n if (counter !== this.counter) continue\n if (audio?.length) this.emit('Audio', audio)\n } catch (error) {\n // A cancelled utterance is not a failure, it left its queue on purpose\n if (counter !== this.counter) continue\n this.log('Error synthesizing speech', error)\n this.emit('Failed', [text, ...this.queue])\n this.queue = []\n } finally {\n if (this.controller === controller) this.controller = undefined\n }\n }\n\n this.draining = false\n // Sentences may have arrived right as we exited the loop\n if (this.queue.length > 0) this.drain()\n }\n}\n","import { WebSocket } from 'ws'\nimport { MicdropError, MicdropErrorCode } from './errors'\n\nexport async function waitForParams<CallParams>(\n socket: WebSocket,\n validate: (params: any) => CallParams\n): Promise<CallParams> {\n return new Promise<CallParams>((resolve, reject) => {\n // Handle timeout\n const timeout = setTimeout(() => {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Missing params'))\n }, 3000)\n\n const onParams = (payload: string) => {\n // Clear timeout and listener\n clearTimeout(timeout)\n socket.off('message', onParams)\n\n try {\n // Parse JSON payload\n const params = validate(JSON.parse(payload))\n resolve(params)\n } catch (error) {\n reject(new MicdropError(MicdropErrorCode.BadRequest, 'Invalid params'))\n }\n }\n\n // Listen for params\n socket.on('message', onParams)\n })\n}\n"],"mappings":"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","names":["MicdropErrorCode","EventEmitter","PassThrough","MicdropClientCommands","MicdropServerCommands","EventEmitter","PassThrough","EventEmitter","EventEmitter","PassThrough","PassThrough","PassThrough","EventEmitter","PassThrough","PassThrough","PassThrough"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micdrop/server",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "\ud83d\udd90\ufe0f\ud83c\udfa4 Micdrop: Real-Time Voice Conversations with AI",
5
5
  "author": "Godefroy de Compreignac",
6
6
  "license": "MIT",