@teamlearners/clawops 0.5.1 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_BASE_URL, AgentError, AgentConnectionError } from '../chunk-7S2OEBS6.js';
2
+ import pino from 'pino';
2
3
  import * as fs from 'fs';
3
4
  import * as path from 'path';
4
5
 
@@ -322,6 +323,14 @@ function resamplePcm16(pcm, fromRate, toRate) {
322
323
  }
323
324
  return out;
324
325
  }
326
+ var DEFAULT_LOGGER = pino({ name: "clawops.agent" });
327
+ var NOOP_LOGGER = pino({ level: "silent" });
328
+ function createAgentLogger(userLogger) {
329
+ return userLogger ?? DEFAULT_LOGGER;
330
+ }
331
+ function createPipelineLogger(parent) {
332
+ return parent.child({ module: "pipeline" });
333
+ }
325
334
 
326
335
  // src/agent/control-ws.ts
327
336
  var INITIAL_RECONNECT_DELAY = 1e3;
@@ -350,6 +359,10 @@ var ControlWebSocket = class {
350
359
  _closed = false;
351
360
  _connectedResolve = null;
352
361
  _connectedPromise;
362
+ _log = NOOP_LOGGER;
363
+ setLogger(logger) {
364
+ this._log = logger;
365
+ }
353
366
  /** Register an event handler for a specific event type. */
354
367
  on(event, handler) {
355
368
  let list = this._handlers.get(event);
@@ -397,13 +410,14 @@ var ControlWebSocket = class {
397
410
  this._connectedResolve();
398
411
  this._connectedResolve = null;
399
412
  }
413
+ this._log.info("Control WS connected: %s", this._url);
400
414
  });
401
415
  ws.on("message", (data) => {
402
416
  try {
403
417
  const msg = JSON.parse(data.toString());
404
418
  this._dispatchEvent(msg);
405
419
  } catch {
406
- console.error("[ControlWebSocket] Failed to parse message");
420
+ this._log.warn("Control WS parse error");
407
421
  }
408
422
  });
409
423
  ws.on("close", () => {
@@ -412,7 +426,7 @@ var ControlWebSocket = class {
412
426
  }
413
427
  });
414
428
  ws.on("error", (err) => {
415
- console.error("[ControlWebSocket] Error:", err.message);
429
+ this._log.warn("Control WS error: %s", err.message);
416
430
  });
417
431
  }
418
432
  _dispatchEvent(event) {
@@ -423,11 +437,11 @@ var ControlWebSocket = class {
423
437
  const result = handler(event);
424
438
  if (result && typeof result.catch === "function") {
425
439
  result.catch((err) => {
426
- console.error(`[ControlWebSocket] Error in handler for ${event.event}:`, err);
440
+ this._log.error({ err }, "Control WS handler error: %s", event.event);
427
441
  });
428
442
  }
429
443
  } catch (err) {
430
- console.error(`[ControlWebSocket] Error in handler for ${event.event}:`, err);
444
+ this._log.error({ err }, "Control WS handler error: %s", event.event);
431
445
  }
432
446
  }
433
447
  }
@@ -435,10 +449,11 @@ var ControlWebSocket = class {
435
449
  _scheduleReconnect() {
436
450
  const delay = this._reconnectDelay;
437
451
  this._reconnectDelay = Math.min(this._reconnectDelay * 2, MAX_RECONNECT_DELAY);
452
+ this._log.info("Control WS reconnecting in %ds...", delay / 1e3);
438
453
  setTimeout(() => {
439
454
  if (!this._closed) {
440
455
  this._doConnect().catch((err) => {
441
- console.error("[ControlWebSocket] Reconnect failed:", err);
456
+ this._log.warn({ err }, "Control WS reconnect failed");
442
457
  this._scheduleReconnect();
443
458
  });
444
459
  }
@@ -450,6 +465,10 @@ var ControlWebSocket = class {
450
465
  var MCPClient = class {
451
466
  _servers = /* @__PURE__ */ new Map();
452
467
  _clients = /* @__PURE__ */ new Map();
468
+ _log = NOOP_LOGGER;
469
+ setLogger(logger) {
470
+ this._log = logger;
471
+ }
453
472
  /** Add an MCP server configuration. */
454
473
  addServer(name, config) {
455
474
  this._servers.set(name, config);
@@ -462,7 +481,7 @@ var MCPClient = class {
462
481
  const tools = await this._connectServer(name, config);
463
482
  allTools.push(...tools);
464
483
  } catch (err) {
465
- console.error(`[MCPClient] Failed to connect to server '${name}':`, err);
484
+ this._log.error({ err }, "MCP connection failed: %s", name);
466
485
  }
467
486
  }
468
487
  return allTools;
@@ -471,12 +490,13 @@ var MCPClient = class {
471
490
  async disconnect() {
472
491
  for (const [name, client] of this._clients) {
473
492
  try {
493
+ this._log.debug("MCP closing: %s", name);
474
494
  const c = client;
475
495
  if (c.close) {
476
496
  await c.close();
477
497
  }
478
498
  } catch (err) {
479
- console.error(`[MCPClient] Error disconnecting from '${name}':`, err);
499
+ this._log.error({ err }, "MCP disconnect error: %s", name);
480
500
  }
481
501
  }
482
502
  this._clients.clear();
@@ -485,6 +505,11 @@ var MCPClient = class {
485
505
  const sdk = await import('@modelcontextprotocol/sdk/client/index.js');
486
506
  const { Client } = sdk;
487
507
  const client = new Client({ name: `clawops-${name}`, version: "1.0.0" });
508
+ if (config.type === "stdio") {
509
+ this._log.debug("MCP connecting (stdio): %s", config["command"]);
510
+ } else if (config.type === "http") {
511
+ this._log.debug("MCP connecting (http): %s", config["url"]);
512
+ }
488
513
  let transport;
489
514
  if (config.type === "stdio") {
490
515
  const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
@@ -513,6 +538,7 @@ var MCPClient = class {
513
538
  parameters: inputSchema.properties ?? {},
514
539
  required: inputSchema.required ?? [],
515
540
  handler: async (args) => {
541
+ this._log.debug("MCP call_tool: %s", toolDef.name);
516
542
  const result = await client.callTool({
517
543
  name: toolDef.name,
518
544
  arguments: args
@@ -525,6 +551,8 @@ var MCPClient = class {
525
551
  }
526
552
  });
527
553
  }
554
+ this._log.info("MCP server connected: %d tools found", tools.length);
555
+ this._log.debug("MCP tools: %s", tools.map((t) => t.name));
528
556
  return tools;
529
557
  }
530
558
  };
@@ -555,6 +583,20 @@ function buildMediaResponse(audioBase64) {
555
583
  }
556
584
  });
557
585
  }
586
+ var VALID_DTMF_DIGITS = new Set("0123456789*#");
587
+ function parseDtmfEvent(data) {
588
+ const dtmf = data["dtmf"];
589
+ return {
590
+ digit: dtmf["digit"] ?? "",
591
+ track: dtmf["track"] ?? ""
592
+ };
593
+ }
594
+ function buildDtmfMessage(digit) {
595
+ if (!VALID_DTMF_DIGITS.has(digit)) {
596
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 DTMF digit: ${digit}`);
597
+ }
598
+ return JSON.stringify({ event: "dtmf", dtmf: { digit } });
599
+ }
558
600
  var MediaWebSocket = class {
559
601
  _ws = null;
560
602
  _audioQueue = [];
@@ -563,6 +605,12 @@ var MediaWebSocket = class {
563
605
  _onAudio = null;
564
606
  _onStart = null;
565
607
  _onClose = null;
608
+ _onDtmf = null;
609
+ _markWaiters = /* @__PURE__ */ new Map();
610
+ _log = NOOP_LOGGER;
611
+ setLogger(logger) {
612
+ this._log = logger;
613
+ }
566
614
  /** Set the handler for inbound audio data. */
567
615
  onAudio(handler) {
568
616
  this._onAudio = handler;
@@ -575,6 +623,20 @@ var MediaWebSocket = class {
575
623
  onClose(handler) {
576
624
  this._onClose = handler;
577
625
  }
626
+ /** Set the handler for inbound DTMF events. */
627
+ onDtmf(handler) {
628
+ this._onDtmf = handler;
629
+ }
630
+ /** Send a single DTMF digit to the platform. */
631
+ sendDtmf(digit) {
632
+ if (this._ws && this._ws.readyState === 1) {
633
+ this._ws.send(buildDtmfMessage(digit));
634
+ }
635
+ }
636
+ /** Whether the WebSocket is connected. */
637
+ get isConnected() {
638
+ return this._ws !== null && this._ws.readyState === 1 && !this._closed;
639
+ }
578
640
  /** Connect to a media WebSocket URL with Bearer authentication. */
579
641
  async connect(url, apiKey) {
580
642
  const { WebSocket } = await import('ws');
@@ -590,6 +652,7 @@ var MediaWebSocket = class {
590
652
  ws.on("open", () => {
591
653
  this._startSendLoop();
592
654
  resolve();
655
+ this._log.info("Media WS connected: %s", url);
593
656
  });
594
657
  ws.on("message", (data) => {
595
658
  try {
@@ -608,7 +671,7 @@ var MediaWebSocket = class {
608
671
  if (!this._ws) {
609
672
  reject(err);
610
673
  }
611
- console.error("[MediaWebSocket] Error:", err.message);
674
+ this._log.error({ err }, "Media WS error");
612
675
  });
613
676
  });
614
677
  }
@@ -634,6 +697,34 @@ var MediaWebSocket = class {
634
697
  );
635
698
  }
636
699
  }
700
+ /** Wait for all queued audio to be sent. */
701
+ flush() {
702
+ if (this._audioQueue.length === 0 || this._closed) return Promise.resolve();
703
+ return new Promise((resolve) => {
704
+ const check = () => {
705
+ if (this._audioQueue.length === 0 || this._closed) {
706
+ resolve();
707
+ } else {
708
+ setTimeout(check, 5);
709
+ }
710
+ };
711
+ setTimeout(check, 5);
712
+ });
713
+ }
714
+ /** Wait for a named mark to be echoed back by the server. */
715
+ waitForMark(name, timeoutMs = 5e3) {
716
+ if (this._closed) return Promise.resolve();
717
+ return new Promise((resolve) => {
718
+ const timer = setTimeout(() => {
719
+ this._markWaiters.delete(name);
720
+ resolve();
721
+ }, timeoutMs);
722
+ this._markWaiters.set(name, () => {
723
+ clearTimeout(timer);
724
+ resolve();
725
+ });
726
+ });
727
+ }
637
728
  /** Close the media WebSocket. */
638
729
  close() {
639
730
  this._closed = true;
@@ -659,6 +750,24 @@ var MediaWebSocket = class {
659
750
  }
660
751
  break;
661
752
  }
753
+ case "dtmf": {
754
+ const dtmfEvt = parseDtmfEvent(msg);
755
+ if (this._onDtmf) {
756
+ this._onDtmf(dtmfEvt.digit);
757
+ }
758
+ break;
759
+ }
760
+ case "mark": {
761
+ const markName = msg["mark"]?.["name"];
762
+ if (markName) {
763
+ const resolve = this._markWaiters.get(markName);
764
+ if (resolve) {
765
+ this._markWaiters.delete(markName);
766
+ resolve();
767
+ }
768
+ }
769
+ break;
770
+ }
662
771
  case "stop": {
663
772
  this.close();
664
773
  break;
@@ -723,6 +832,10 @@ var AudioRecorder = class {
723
832
  _mixWritten = 0;
724
833
  _startTime = 0;
725
834
  _started = false;
835
+ _log = NOOP_LOGGER;
836
+ setLogger(logger) {
837
+ this._log = logger;
838
+ }
726
839
  constructor(recordingPath, callId) {
727
840
  this._dir = path.join(recordingPath, callId);
728
841
  }
@@ -737,6 +850,7 @@ var AudioRecorder = class {
737
850
  fs.writeSync(this._fdMix, header);
738
851
  this._startTime = performance.now();
739
852
  this._started = true;
853
+ this._log.info("Recording started: %s", this._dir);
740
854
  }
741
855
  _expectedBytes() {
742
856
  const elapsed = (performance.now() - this._startTime) / 1e3;
@@ -789,7 +903,7 @@ var AudioRecorder = class {
789
903
  this._inWritten += pcm16_8k.length;
790
904
  this._writeToMix(pcm16_8k, posBefore);
791
905
  } catch (err) {
792
- console.error("Error writing inbound audio:", err);
906
+ this._log.error({ err }, "Recording write error (inbound)");
793
907
  }
794
908
  }
795
909
  writeOutbound(pcm16_8k) {
@@ -802,7 +916,7 @@ var AudioRecorder = class {
802
916
  this._outWritten += pcm16_8k.length;
803
917
  this._writeToMix(pcm16_8k, posBefore);
804
918
  } catch (err) {
805
- console.error("Error writing outbound audio:", err);
919
+ this._log.error({ err }, "Recording write error (outbound)");
806
920
  }
807
921
  }
808
922
  stop() {
@@ -823,8 +937,10 @@ var AudioRecorder = class {
823
937
  fs.writeSync(fd, makeWavHeader(maxWritten), 0, 44, 0);
824
938
  fs.closeSync(fd);
825
939
  }
940
+ const maxSec = maxWritten / 16e3;
941
+ this._log.info("Recording stopped: %s (%ds)", this._dir, maxSec);
826
942
  } catch (err) {
827
- console.error("Error stopping recorder:", err);
943
+ this._log.error({ err }, "Recording stop error");
828
944
  } finally {
829
945
  this._fdIn = null;
830
946
  this._fdOut = null;
@@ -847,6 +963,14 @@ var CallSession = class {
847
963
  _sendAudioFn = null;
848
964
  _clearAudioFn = null;
849
965
  _hangupFn = null;
966
+ /** @internal */
967
+ _sendDtmfFn = null;
968
+ /** @internal */
969
+ _isTransportConnected = null;
970
+ _dtmfCollectorActive = false;
971
+ _dtmfResolvers = [];
972
+ _dtmfBuffer = [];
973
+ _log = NOOP_LOGGER;
850
974
  _handlers = /* @__PURE__ */ new Map();
851
975
  _endedPromise;
852
976
  _resolveEnded;
@@ -863,6 +987,9 @@ var CallSession = class {
863
987
  this._resolveEnded = resolve;
864
988
  });
865
989
  }
990
+ setLogger(logger) {
991
+ this._log = logger;
992
+ }
866
993
  get status() {
867
994
  return this._status;
868
995
  }
@@ -870,10 +997,12 @@ var CallSession = class {
870
997
  return (Date.now() - this.startTime.getTime()) / 1e3;
871
998
  }
872
999
  /** Bind transport functions (called internally by the agent). */
873
- _bindTransport(send, clear, hangup) {
1000
+ _bindTransport(send, clear, hangup, sendDtmf, isConnected) {
874
1001
  this._sendAudioFn = send;
875
1002
  this._clearAudioFn = clear;
876
1003
  this._hangupFn = hangup;
1004
+ if (sendDtmf) this._sendDtmfFn = sendDtmf;
1005
+ if (isConnected) this._isTransportConnected = isConnected;
877
1006
  this._status = "active";
878
1007
  }
879
1008
  /** Send PCM16 or ulaw audio to the caller. */
@@ -888,10 +1017,78 @@ var CallSession = class {
888
1017
  this._clearAudioFn();
889
1018
  }
890
1019
  }
891
- /** Hang up the call. */
892
- hangup() {
1020
+ /** Hang up the call, waiting for pending audio to finish. */
1021
+ async hangup() {
893
1022
  if (this._hangupFn) {
894
- this._hangupFn();
1023
+ await this._hangupFn();
1024
+ }
1025
+ }
1026
+ /** @internal Route a received DTMF digit to an active collector or buffer. */
1027
+ _routeDtmf(digit) {
1028
+ if (this._dtmfCollectorActive && this._dtmfResolvers.length > 0) {
1029
+ const resolve = this._dtmfResolvers.shift();
1030
+ resolve(digit);
1031
+ } else {
1032
+ this._dtmfBuffer.push(digit);
1033
+ }
1034
+ }
1035
+ /** Collect DTMF digits from the caller. */
1036
+ async collectDtmf(options) {
1037
+ if (this._dtmfCollectorActive) {
1038
+ throw new Error("\uC774\uBBF8 DTMF \uC218\uC9D1 \uC911\uC785\uB2C8\uB2E4");
1039
+ }
1040
+ const { maxDigits, finishOnKey = "#", timeout = 5 } = options;
1041
+ this._dtmfCollectorActive = true;
1042
+ const collected = [];
1043
+ try {
1044
+ while (collected.length < maxDigits) {
1045
+ if (this._dtmfBuffer.length > 0) {
1046
+ const digit2 = this._dtmfBuffer.shift();
1047
+ if (digit2 === finishOnKey) break;
1048
+ collected.push(digit2);
1049
+ continue;
1050
+ }
1051
+ const digit = await Promise.race([
1052
+ new Promise((resolve) => {
1053
+ this._dtmfResolvers.push(resolve);
1054
+ }),
1055
+ new Promise((resolve) => {
1056
+ setTimeout(() => resolve(null), timeout * 1e3);
1057
+ })
1058
+ ]);
1059
+ if (digit === null) break;
1060
+ if (digit === finishOnKey) break;
1061
+ collected.push(digit);
1062
+ }
1063
+ } finally {
1064
+ this._dtmfCollectorActive = false;
1065
+ this._dtmfResolvers = [];
1066
+ this._dtmfBuffer = [];
1067
+ }
1068
+ const result = collected.join("");
1069
+ this._log.info("DTMF collected: %s", result);
1070
+ return result;
1071
+ }
1072
+ /** Send a sequence of DTMF digits. */
1073
+ async sendDtmfSequence(digits) {
1074
+ if (!this._sendDtmfFn) {
1075
+ throw new Error("DTMF \uC804\uC1A1 \uD568\uC218\uAC00 \uBC14\uC778\uB529\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
1076
+ }
1077
+ for (const ch of digits) {
1078
+ if (this._isTransportConnected && !this._isTransportConnected()) {
1079
+ throw new Error("DTMF \uC804\uC1A1 \uC911 \uC5F0\uACB0\uC774 \uB04A\uC5B4\uC84C\uC2B5\uB2C8\uB2E4");
1080
+ }
1081
+ if (ch === "w") {
1082
+ await new Promise((r) => setTimeout(r, 500));
1083
+ } else if (ch === "W") {
1084
+ await new Promise((r) => setTimeout(r, 1e3));
1085
+ } else if ("0123456789*#".includes(ch)) {
1086
+ if (this._sendDtmfFn) {
1087
+ await this._sendDtmfFn(ch);
1088
+ }
1089
+ } else {
1090
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 DTMF \uBB38\uC790: ${ch}`);
1091
+ }
895
1092
  }
896
1093
  }
897
1094
  /** Register an event handler. */
@@ -922,17 +1119,44 @@ var CallSession = class {
922
1119
  const result = handler(this, ...args);
923
1120
  if (result && typeof result.catch === "function") {
924
1121
  result.catch((err) => {
925
- console.error(`[CallSession] Error in ${event} handler:`, err);
1122
+ this._log.error({ err }, "CallSession handler error: %s", event);
926
1123
  });
927
1124
  }
928
1125
  } catch (err) {
929
- console.error(`[CallSession] Error in ${event} handler:`, err);
1126
+ this._log.error({ err }, "CallSession handler error: %s", event);
930
1127
  }
931
1128
  }
932
1129
  }
933
1130
  }
934
1131
  };
935
1132
 
1133
+ // src/agent/builtin-tool.ts
1134
+ var BuiltinTool = /* @__PURE__ */ ((BuiltinTool2) => {
1135
+ BuiltinTool2["HANG_UP"] = "hang_up";
1136
+ BuiltinTool2["COLLECT_DTMF"] = "collect_dtmf";
1137
+ BuiltinTool2["SEND_DTMF"] = "send_dtmf";
1138
+ BuiltinTool2["ALL"] = "all";
1139
+ BuiltinTool2["NONE"] = "none";
1140
+ return BuiltinTool2;
1141
+ })(BuiltinTool || {});
1142
+ var INDIVIDUAL_TOOLS = /* @__PURE__ */ new Set([
1143
+ "hang_up" /* HANG_UP */,
1144
+ "collect_dtmf" /* COLLECT_DTMF */,
1145
+ "send_dtmf" /* SEND_DTMF */
1146
+ ]);
1147
+ function resolveBuiltinTools(value) {
1148
+ if (typeof value === "string") {
1149
+ if (value === "all" /* ALL */) {
1150
+ return new Set(INDIVIDUAL_TOOLS);
1151
+ }
1152
+ if (value === "none" /* NONE */) {
1153
+ return /* @__PURE__ */ new Set();
1154
+ }
1155
+ return /* @__PURE__ */ new Set([value]);
1156
+ }
1157
+ return new Set(value.filter((t) => INDIVIDUAL_TOOLS.has(t)));
1158
+ }
1159
+
936
1160
  // src/agent/tool.ts
937
1161
  function functionTool(fn) {
938
1162
  return fn;
@@ -1141,6 +1365,15 @@ var ClawOpsAgent = class {
1141
1365
  _recording;
1142
1366
  _recordingPath;
1143
1367
  _activeSessions = /* @__PURE__ */ new Map();
1368
+ _builtinTools;
1369
+ _passiveDtmfDebounceMs;
1370
+ _passiveDtmfBuffer = [];
1371
+ _passiveDtmfTimer = null;
1372
+ _passiveDtmfCallId = null;
1373
+ _callSessions = /* @__PURE__ */ new Map();
1374
+ _log;
1375
+ _pipelineLog;
1376
+ _isPipelineSession = false;
1144
1377
  constructor(options) {
1145
1378
  this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
1146
1379
  this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
@@ -1150,9 +1383,14 @@ var ClawOpsAgent = class {
1150
1383
  this._recording = options.recording ?? false;
1151
1384
  this._recordingPath = options.recordingPath ?? "./recordings";
1152
1385
  this._mcpServers = options.mcpServers ?? [];
1386
+ this._builtinTools = resolveBuiltinTools(options.builtinTools ?? "all" /* ALL */);
1387
+ this._passiveDtmfDebounceMs = options.passiveDtmfDebounceMs ?? 500;
1153
1388
  if (options.tracing) {
1154
1389
  setTracingConfig(options.tracing);
1155
1390
  }
1391
+ this._log = createAgentLogger(options.logger);
1392
+ this._pipelineLog = createPipelineLogger(this._log);
1393
+ this._isPipelineSession = "_stt" in this._session && "_llm" in this._session;
1156
1394
  }
1157
1395
  /**
1158
1396
  * Register a function tool.
@@ -1164,7 +1402,9 @@ var ClawOpsAgent = class {
1164
1402
  tool(nameOrTool, description, parameters, handler) {
1165
1403
  if (typeof nameOrTool === "string") {
1166
1404
  if (!description || !parameters || !handler) {
1167
- throw new AgentError("tool(name, description, parameters, handler) requires all arguments.");
1405
+ throw new AgentError(
1406
+ "tool(name, description, parameters, handler) requires all arguments."
1407
+ );
1168
1408
  }
1169
1409
  this._tools.register({
1170
1410
  name: nameOrTool,
@@ -1200,7 +1440,9 @@ var ClawOpsAgent = class {
1200
1440
  throw new AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1201
1441
  }
1202
1442
  if (!this._accountId) {
1203
- throw new AgentError("Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option.");
1443
+ throw new AgentError(
1444
+ "Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option."
1445
+ );
1204
1446
  }
1205
1447
  this._controlWs = new ControlWebSocket({
1206
1448
  baseUrl: this._baseUrl,
@@ -1208,6 +1450,7 @@ var ClawOpsAgent = class {
1208
1450
  accountId: this._accountId,
1209
1451
  number: this._fromNumber
1210
1452
  });
1453
+ this._controlWs.setLogger(this._log);
1211
1454
  this._controlWs.on("call.incoming", (event) => this._handleIncoming(event));
1212
1455
  this._controlWs.on("call.ended", (event) => this._handleEnded(event));
1213
1456
  this._controlWs.on("call.outbound_ready", (event) => this._handleOutboundReady(event));
@@ -1221,7 +1464,7 @@ var ClawOpsAgent = class {
1221
1464
  `Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
1222
1465
  );
1223
1466
  }
1224
- console.log(`[ClawOpsAgent] Connected on ${this._fromNumber}`);
1467
+ this._log.info("ClawOpsAgent connected on %s", this._fromNumber);
1225
1468
  }
1226
1469
  /**
1227
1470
  * Connect and block until disconnected.
@@ -1247,7 +1490,8 @@ var ClawOpsAgent = class {
1247
1490
  session._markEnded();
1248
1491
  }
1249
1492
  this._activeSessions.clear();
1250
- console.log("[ClawOpsAgent] Disconnected");
1493
+ this._callSessions.clear();
1494
+ this._log.info("ClawOpsAgent disconnected");
1251
1495
  }
1252
1496
  /**
1253
1497
  * Initiate an outbound call.
@@ -1282,8 +1526,9 @@ var ClawOpsAgent = class {
1282
1526
  callSession.on(evt, handler);
1283
1527
  }
1284
1528
  }
1529
+ callSession.setLogger(this._log);
1285
1530
  this._activeSessions.set(callSession.callId, callSession);
1286
- console.log(`[ClawOpsAgent] Outbound call initiated: ${this._fromNumber} -> ${to} (${callSession.callId})`);
1531
+ this._log.info("Outbound call initiated: %s -> %s (%s)", this._fromNumber, to, callSession.callId);
1287
1532
  return callSession;
1288
1533
  }
1289
1534
  _handleIncoming(event) {
@@ -1302,13 +1547,15 @@ var ClawOpsAgent = class {
1302
1547
  session.on(evt, handler);
1303
1548
  }
1304
1549
  }
1550
+ session.setLogger(this._log);
1305
1551
  this._activeSessions.set(callId, session);
1552
+ this._log.info("Incoming call: %s -> %s (%s)", fromNumber, this._fromNumber, callId);
1306
1553
  if (this._controlWs) {
1307
1554
  this._controlWs.send({ event: "call.accept", callId });
1308
1555
  }
1309
1556
  if (mediaUrl) {
1310
1557
  this._startCallSession(session, mediaUrl).catch((err) => {
1311
- console.error(`[ClawOpsAgent] Error in call session ${callId}:`, err);
1558
+ this._log.error({ err }, "Call session error: %s", callId);
1312
1559
  });
1313
1560
  }
1314
1561
  }
@@ -1316,6 +1563,7 @@ var ClawOpsAgent = class {
1316
1563
  const callId = event["callId"];
1317
1564
  const session = this._activeSessions.get(callId);
1318
1565
  if (session) {
1566
+ this._log.info("Call ended (server): %s", callId);
1319
1567
  session._markEnded();
1320
1568
  this._activeSessions.delete(callId);
1321
1569
  }
@@ -1332,6 +1580,7 @@ var ClawOpsAgent = class {
1332
1580
  accountId: this._accountId,
1333
1581
  direction: "outbound"
1334
1582
  });
1583
+ session.setLogger(this._log);
1335
1584
  for (const [evt, handlers] of this._handlers) {
1336
1585
  for (const handler of handlers) {
1337
1586
  session.on(evt, handler);
@@ -1340,8 +1589,9 @@ var ClawOpsAgent = class {
1340
1589
  this._activeSessions.set(callId, session);
1341
1590
  }
1342
1591
  if (mediaUrl) {
1592
+ this._log.info("Outbound call answered: %s -> %s (%s)", this._fromNumber, session.toNumber, callId);
1343
1593
  this._startCallSession(session, mediaUrl).catch((err) => {
1344
- console.error(`[ClawOpsAgent] Error in call session ${callId}:`, err);
1594
+ this._log.error({ err }, "Call session error: %s", callId);
1345
1595
  });
1346
1596
  }
1347
1597
  }
@@ -1349,18 +1599,43 @@ var ClawOpsAgent = class {
1349
1599
  const callId = event["callId"];
1350
1600
  const session = this._activeSessions.get(callId);
1351
1601
  if (session) {
1352
- console.log(`[ClawOpsAgent] Outbound call ringing: ${callId}`);
1602
+ this._log.info("Outbound call ringing: %s", callId);
1353
1603
  }
1354
1604
  }
1355
1605
  _handleFailed(event) {
1356
1606
  const callId = event["callId"];
1357
1607
  const session = this._activeSessions.get(callId);
1358
1608
  if (session) {
1609
+ this._log.info("Outbound call failed: %s (%s)", callId, event["reason"] ?? "failed");
1359
1610
  session._emit("call_failed", event["reason"] ?? "failed");
1360
1611
  session._markEnded();
1361
1612
  this._activeSessions.delete(callId);
1362
1613
  }
1363
1614
  }
1615
+ _onDtmfEvent(callSession, digit) {
1616
+ callSession._emit("dtmf", digit);
1617
+ callSession._routeDtmf(digit);
1618
+ if (callSession._dtmfCollectorActive) {
1619
+ callSession.clearAudio();
1620
+ return;
1621
+ }
1622
+ this._passiveDtmfBuffer.push(digit);
1623
+ this._passiveDtmfCallId = callSession.callId;
1624
+ if (this._passiveDtmfTimer) {
1625
+ clearTimeout(this._passiveDtmfTimer);
1626
+ }
1627
+ this._passiveDtmfTimer = setTimeout(() => {
1628
+ const digits = this._passiveDtmfBuffer.join("");
1629
+ this._passiveDtmfBuffer = [];
1630
+ const sessionHandler = this._passiveDtmfCallId ? this._callSessions.get(this._passiveDtmfCallId) : null;
1631
+ this._passiveDtmfCallId = null;
1632
+ if (digits && sessionHandler && sessionHandler.feedDtmf) {
1633
+ sessionHandler.feedDtmf(digits).catch((err) => {
1634
+ this._log.error({ err }, "DTMF feed error");
1635
+ });
1636
+ }
1637
+ }, this._passiveDtmfDebounceMs);
1638
+ }
1364
1639
  async _startCallSession(session, mediaWsUrl) {
1365
1640
  await withSpan(
1366
1641
  "clawops.call_session",
@@ -1375,22 +1650,25 @@ var ClawOpsAgent = class {
1375
1650
  if (this._mcpServers.length > 0) {
1376
1651
  for (const serverConfig of this._mcpServers) {
1377
1652
  const client = new MCPClient();
1653
+ client.setLogger(this._log);
1378
1654
  client.addServer("mcp", serverConfig);
1379
1655
  try {
1380
1656
  const tools = await client.connect();
1381
1657
  sessionTools.registerMcpTools(tools);
1382
1658
  mcpClients.push(client);
1383
1659
  } catch (err) {
1384
- console.error("[ClawOpsAgent] MCP connection error:", err);
1660
+ this._log.error({ err }, "MCP connection error");
1385
1661
  }
1386
1662
  }
1387
1663
  }
1388
1664
  let recorder = null;
1389
1665
  if (this._recording) {
1390
1666
  recorder = new AudioRecorder(this._recordingPath, session.callId);
1667
+ recorder.setLogger(this._log);
1391
1668
  recorder.start();
1392
1669
  }
1393
1670
  const mediaWs = new MediaWebSocket();
1671
+ mediaWs.setLogger(this._log);
1394
1672
  session._bindTransport(
1395
1673
  (audio) => {
1396
1674
  mediaWs.sendAudio(audio.toString("base64"));
@@ -1398,9 +1676,17 @@ var ClawOpsAgent = class {
1398
1676
  () => {
1399
1677
  mediaWs.sendClear();
1400
1678
  },
1401
- () => {
1679
+ async () => {
1680
+ await mediaWs.flush();
1681
+ const markName = `hangup-${Date.now()}`;
1682
+ mediaWs.sendMark(markName);
1683
+ await mediaWs.waitForMark(markName, 5e3);
1402
1684
  mediaWs.close();
1403
- }
1685
+ },
1686
+ async (digit) => {
1687
+ mediaWs.sendDtmf(digit);
1688
+ },
1689
+ () => mediaWs.isConnected
1404
1690
  );
1405
1691
  const sessionHandler = this._session;
1406
1692
  if ("setToolRegistry" in sessionHandler && typeof sessionHandler.setToolRegistry === "function") {
@@ -1409,6 +1695,13 @@ var ClawOpsAgent = class {
1409
1695
  if (recorder && "setRecorder" in sessionHandler && typeof sessionHandler.setRecorder === "function") {
1410
1696
  sessionHandler.setRecorder(recorder);
1411
1697
  }
1698
+ if ("setBuiltinTools" in sessionHandler && typeof sessionHandler.setBuiltinTools === "function") {
1699
+ sessionHandler.setBuiltinTools(this._builtinTools);
1700
+ }
1701
+ if ("setLogger" in sessionHandler && typeof sessionHandler.setLogger === "function") {
1702
+ sessionHandler.setLogger(this._isPipelineSession ? this._pipelineLog : this._log);
1703
+ }
1704
+ this._callSessions.set(session.callId, sessionHandler);
1412
1705
  mediaWs.onAudio((ulawAudio, _timestamp) => {
1413
1706
  if (sessionHandler) {
1414
1707
  sessionHandler.feedAudio(ulawAudio);
@@ -1417,7 +1710,11 @@ var ClawOpsAgent = class {
1417
1710
  recorder.writeInbound(ulawToPcm16(ulawAudio));
1418
1711
  }
1419
1712
  });
1713
+ mediaWs.onDtmf((digit) => {
1714
+ this._onDtmfEvent(session, digit);
1715
+ });
1420
1716
  mediaWs.onClose(() => {
1717
+ this._log.info("Media stream stopped: %s", session.callId);
1421
1718
  if (recorder) {
1422
1719
  recorder.stop();
1423
1720
  }
@@ -1426,11 +1723,12 @@ var ClawOpsAgent = class {
1426
1723
  session._emit("call_start");
1427
1724
  try {
1428
1725
  await mediaWs.connect(mediaWsUrl, this._apiKey);
1726
+ this._log.info("Media stream started: %s", session.callId);
1429
1727
  await sessionHandler.start(session, sessionTools);
1430
1728
  await session.wait();
1431
1729
  await sessionHandler.stop();
1432
1730
  } catch (err) {
1433
- console.error(`[ClawOpsAgent] Call session error:`, err);
1731
+ this._log.error({ err }, "Call session error: %s", session.callId);
1434
1732
  } finally {
1435
1733
  if (mcpClients.length > 0) {
1436
1734
  sessionTools.clearMcpTools();
@@ -1445,6 +1743,7 @@ var ClawOpsAgent = class {
1445
1743
  session._emit("call_end");
1446
1744
  session._markEnded();
1447
1745
  this._activeSessions.delete(session.callId);
1746
+ this._callSessions.delete(session.callId);
1448
1747
  }
1449
1748
  }
1450
1749
  );
@@ -1457,7 +1756,40 @@ var HANG_UP_TOOL = {
1457
1756
  type: "function",
1458
1757
  name: "hang_up",
1459
1758
  description: "End the phone call. Use when the conversation is finished or the caller says goodbye.",
1460
- parameters: { type: "object", properties: {}, required: [] }
1759
+ parameters: {
1760
+ type: "object",
1761
+ properties: {},
1762
+ required: []
1763
+ }
1764
+ };
1765
+ var COLLECT_DTMF_TOOL = {
1766
+ type: "function",
1767
+ name: "collect_dtmf",
1768
+ description: "\uC0AC\uC6A9\uC790\uB85C\uBD80\uD130 DTMF(\uC804\uD654 \uD0A4\uD328\uB4DC) \uC785\uB825\uC744 \uC218\uC9D1\uD569\uB2C8\uB2E4. \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB34\uC5C7\uC744 \uC785\uB825\uD574\uC57C \uD558\uB294\uC9C0 \uC548\uB0B4\uD55C \uD6C4 \uD638\uCD9C\uD558\uC138\uC694.",
1769
+ parameters: {
1770
+ type: "object",
1771
+ properties: {
1772
+ max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
1773
+ finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
1774
+ timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
1775
+ },
1776
+ required: ["max_digits"]
1777
+ }
1778
+ };
1779
+ var SEND_DTMF_TOOL = {
1780
+ type: "function",
1781
+ name: "send_dtmf",
1782
+ description: "DTMF \uC2E0\uD638\uB97C \uC804\uC1A1\uD569\uB2C8\uB2E4. ARS \uBA54\uB274 \uD0D0\uC0C9\uC774\uB098 \uB0B4\uC120\uBC88\uD638 \uC785\uB825 \uC2DC \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",
1783
+ parameters: {
1784
+ type: "object",
1785
+ properties: {
1786
+ digits: {
1787
+ type: "string",
1788
+ description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30."
1789
+ }
1790
+ },
1791
+ required: ["digits"]
1792
+ }
1461
1793
  };
1462
1794
  var OpenAIRealtime = class {
1463
1795
  _apiKey;
@@ -1467,6 +1799,14 @@ var OpenAIRealtime = class {
1467
1799
  _language;
1468
1800
  _eagerness;
1469
1801
  _greeting;
1802
+ _log = NOOP_LOGGER;
1803
+ _builtinTools = null;
1804
+ setLogger(logger) {
1805
+ this._log = logger;
1806
+ }
1807
+ setBuiltinTools(tools) {
1808
+ this._builtinTools = tools;
1809
+ }
1470
1810
  _ws = null;
1471
1811
  _call = null;
1472
1812
  _tools = null;
@@ -1477,6 +1817,9 @@ var OpenAIRealtime = class {
1477
1817
  _responseStartTs = null;
1478
1818
  _sentAudioChunks = 0;
1479
1819
  _audioRemainder = Buffer.alloc(0);
1820
+ // Response state tracking — prevent sending response.create while one is active
1821
+ _responseInProgress = false;
1822
+ _onResponseDone = null;
1480
1823
  constructor(options = {}) {
1481
1824
  this._apiKey = options.apiKey ?? process.env["OPENAI_API_KEY"] ?? "";
1482
1825
  this._systemPrompt = options.systemPrompt ?? "";
@@ -1517,6 +1860,7 @@ var OpenAIRealtime = class {
1517
1860
  const ws = this._ws;
1518
1861
  ws.on("open", () => {
1519
1862
  this._sendSessionUpdate();
1863
+ this._log.info("OpenAI Realtime connected");
1520
1864
  if (this._greeting) {
1521
1865
  this._send({ type: "response.create" });
1522
1866
  }
@@ -1536,10 +1880,22 @@ var OpenAIRealtime = class {
1536
1880
  if (!this._ws) {
1537
1881
  reject(err);
1538
1882
  }
1539
- console.error("[OpenAIRealtime] WebSocket error:", err.message);
1883
+ this._log.error({ err }, "OpenAI Realtime WS error");
1540
1884
  });
1541
1885
  });
1542
1886
  }
1887
+ async feedDtmf(digits) {
1888
+ await this._waitForResponseDone();
1889
+ this._send({
1890
+ type: "conversation.item.create",
1891
+ item: {
1892
+ type: "message",
1893
+ role: "user",
1894
+ content: [{ type: "input_text", text: `[DTMF \uC785\uB825: ${digits}]` }]
1895
+ }
1896
+ });
1897
+ this._send({ type: "response.create" });
1898
+ }
1543
1899
  feedAudio(audio) {
1544
1900
  if (this._ws && this._ws.readyState === 1 && !this._closed) {
1545
1901
  this._send({
@@ -1558,7 +1914,12 @@ var OpenAIRealtime = class {
1558
1914
  _sendSessionUpdate() {
1559
1915
  if (!this._ws || this._ws.readyState !== 1) return;
1560
1916
  const toolSchemas = this._tools ? this._tools.toOpenAITools().map((t) => ({ type: "function", ...t.function })) : [];
1561
- toolSchemas.push(HANG_UP_TOOL);
1917
+ if (!this._builtinTools || this._builtinTools.has("hang_up" /* HANG_UP */))
1918
+ toolSchemas.push(HANG_UP_TOOL);
1919
+ if (!this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */))
1920
+ toolSchemas.push(COLLECT_DTMF_TOOL);
1921
+ if (!this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */))
1922
+ toolSchemas.push(SEND_DTMF_TOOL);
1562
1923
  this._send({
1563
1924
  type: "session.update",
1564
1925
  session: {
@@ -1568,7 +1929,7 @@ var OpenAIRealtime = class {
1568
1929
  input_audio_format: "g711_ulaw",
1569
1930
  output_audio_format: "g711_ulaw",
1570
1931
  input_audio_transcription: {
1571
- model: "gpt-4o-mini-transcribe",
1932
+ model: "whisper-1",
1572
1933
  language: this._language
1573
1934
  },
1574
1935
  input_audio_noise_reduction: { type: "far_field" },
@@ -1625,8 +1986,21 @@ var OpenAIRealtime = class {
1625
1986
  }
1626
1987
  break;
1627
1988
  }
1989
+ case "response.created": {
1990
+ this._responseInProgress = true;
1991
+ break;
1992
+ }
1993
+ case "response.done": {
1994
+ this._responseInProgress = false;
1995
+ if (this._onResponseDone) {
1996
+ const cb = this._onResponseDone;
1997
+ this._onResponseDone = null;
1998
+ cb();
1999
+ }
2000
+ break;
2001
+ }
1628
2002
  case "error": {
1629
- console.error("[OpenAIRealtime] API error:", msg["error"]);
2003
+ this._log.error({ apiError: msg["error"] }, "OpenAI error");
1630
2004
  break;
1631
2005
  }
1632
2006
  }
@@ -1676,14 +2050,64 @@ var OpenAIRealtime = class {
1676
2050
  async _handleToolCall(item) {
1677
2051
  const funcName = item["name"];
1678
2052
  const callId = item["call_id"];
2053
+ this._log.info("Tool call: %s", funcName);
1679
2054
  if (funcName === "hang_up") {
1680
2055
  if (this._call) {
1681
- this._call.hangup();
2056
+ await this._call.hangup();
2057
+ }
2058
+ return;
2059
+ }
2060
+ if (funcName === "collect_dtmf") {
2061
+ if (this._call) {
2062
+ let result2;
2063
+ try {
2064
+ const args = JSON.parse(item["arguments"] ?? "{}");
2065
+ result2 = await this._call.collectDtmf({
2066
+ maxDigits: args["max_digits"] ?? 4,
2067
+ finishOnKey: args["finish_on_key"] ?? "#",
2068
+ timeout: args["timeout"] ?? 5
2069
+ });
2070
+ } catch (err) {
2071
+ result2 = `Error: ${err}`;
2072
+ }
2073
+ await this._waitForResponseDone();
2074
+ this._send({
2075
+ type: "conversation.item.create",
2076
+ item: {
2077
+ type: "function_call_output",
2078
+ call_id: callId,
2079
+ output: result2 || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)"
2080
+ }
2081
+ });
2082
+ this._send({ type: "response.create" });
2083
+ }
2084
+ return;
2085
+ }
2086
+ if (funcName === "send_dtmf") {
2087
+ if (this._call) {
2088
+ let result2;
2089
+ try {
2090
+ const args = JSON.parse(item["arguments"] ?? "{}");
2091
+ await this._call.sendDtmfSequence(args["digits"] ?? "");
2092
+ result2 = "sent";
2093
+ } catch (err) {
2094
+ result2 = `Error: ${err}`;
2095
+ }
2096
+ await this._waitForResponseDone();
2097
+ this._send({
2098
+ type: "conversation.item.create",
2099
+ item: {
2100
+ type: "function_call_output",
2101
+ call_id: callId,
2102
+ output: result2
2103
+ }
2104
+ });
2105
+ this._send({ type: "response.create" });
1682
2106
  }
1683
2107
  return;
1684
2108
  }
1685
2109
  if (!this._tools || !this._tools.has(funcName)) {
1686
- console.error(`[OpenAIRealtime] Unknown tool: ${funcName}`);
2110
+ this._log.error("Unknown tool: %s", funcName);
1687
2111
  return;
1688
2112
  }
1689
2113
  let result;
@@ -1691,9 +2115,10 @@ var OpenAIRealtime = class {
1691
2115
  const args = JSON.parse(item["arguments"] ?? "{}");
1692
2116
  result = await this._tools.call(funcName, args);
1693
2117
  } catch (err) {
1694
- console.error(`[OpenAIRealtime] Tool call failed: ${funcName}:`, err);
2118
+ this._log.error({ err }, "Tool call failed: %s", funcName);
1695
2119
  result = `Error: ${err}`;
1696
2120
  }
2121
+ await this._waitForResponseDone();
1697
2122
  this._send({
1698
2123
  type: "conversation.item.create",
1699
2124
  item: {
@@ -1704,6 +2129,12 @@ var OpenAIRealtime = class {
1704
2129
  });
1705
2130
  this._send({ type: "response.create" });
1706
2131
  }
2132
+ _waitForResponseDone() {
2133
+ if (!this._responseInProgress) return Promise.resolve();
2134
+ return new Promise((resolve) => {
2135
+ this._onResponseDone = resolve;
2136
+ });
2137
+ }
1707
2138
  _send(data) {
1708
2139
  if (this._ws && this._ws.readyState === 1 && !this._closed) {
1709
2140
  this._ws.send(JSON.stringify(data));
@@ -1717,6 +2148,33 @@ var HANG_UP_TOOL2 = {
1717
2148
  description: "End the phone call. Use when the conversation is finished or the caller says goodbye.",
1718
2149
  parameters: { type: "object", properties: {} }
1719
2150
  };
2151
+ var COLLECT_DTMF_TOOL2 = {
2152
+ name: "collect_dtmf",
2153
+ description: "\uC0AC\uC6A9\uC790\uB85C\uBD80\uD130 DTMF(\uC804\uD654 \uD0A4\uD328\uB4DC) \uC785\uB825\uC744 \uC218\uC9D1\uD569\uB2C8\uB2E4. \uBC18\uB4DC\uC2DC \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB34\uC5C7\uC744 \uC785\uB825\uD574\uC57C \uD558\uB294\uC9C0 \uC548\uB0B4\uD55C \uD6C4 \uD638\uCD9C\uD558\uC138\uC694.",
2154
+ parameters: {
2155
+ type: "object",
2156
+ properties: {
2157
+ max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
2158
+ finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
2159
+ timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
2160
+ },
2161
+ required: ["max_digits"]
2162
+ }
2163
+ };
2164
+ var SEND_DTMF_TOOL2 = {
2165
+ name: "send_dtmf",
2166
+ description: "DTMF \uC2E0\uD638\uB97C \uC804\uC1A1\uD569\uB2C8\uB2E4. ARS \uBA54\uB274 \uD0D0\uC0C9\uC774\uB098 \uB0B4\uC120\uBC88\uD638 \uC785\uB825 \uC2DC \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",
2167
+ parameters: {
2168
+ type: "object",
2169
+ properties: {
2170
+ digits: {
2171
+ type: "string",
2172
+ description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30."
2173
+ }
2174
+ },
2175
+ required: ["digits"]
2176
+ }
2177
+ };
1720
2178
  function resolveRef(ref, defs) {
1721
2179
  const parts = ref.replace(/^#\//, "").split("/");
1722
2180
  let result = defs;
@@ -1779,7 +2237,11 @@ function sanitizeSchemaForGemini(schema, defs, depth = 0) {
1779
2237
  result["properties"] = props;
1780
2238
  }
1781
2239
  if (schema["items"] && typeof schema["items"] === "object" && !Array.isArray(schema["items"])) {
1782
- result["items"] = sanitizeSchemaForGemini(schema["items"], defs, depth + 1);
2240
+ result["items"] = sanitizeSchemaForGemini(
2241
+ schema["items"],
2242
+ defs,
2243
+ depth + 1
2244
+ );
1783
2245
  }
1784
2246
  if (!result["type"] && result["properties"]) result["type"] = "object";
1785
2247
  if (result["type"] === "object" && !result["properties"]) result["properties"] = {};
@@ -1800,6 +2262,9 @@ var GeminiRealtime = class {
1800
2262
  _closed = false;
1801
2263
  _sentAudioChunks = 0;
1802
2264
  _audioRemainder = Buffer.alloc(0);
2265
+ _builtinTools = null;
2266
+ _toolCallInProgress = false;
2267
+ _log = NOOP_LOGGER;
1803
2268
  constructor(options = {}) {
1804
2269
  this._apiKey = options.apiKey ?? process.env["GOOGLE_API_KEY"] ?? "";
1805
2270
  this._systemPrompt = options.systemPrompt ?? "";
@@ -1816,6 +2281,12 @@ var GeminiRealtime = class {
1816
2281
  setRecorder(recorder) {
1817
2282
  this._recorder = recorder;
1818
2283
  }
2284
+ setBuiltinTools(tools) {
2285
+ this._builtinTools = tools;
2286
+ }
2287
+ setLogger(logger) {
2288
+ this._log = logger;
2289
+ }
1819
2290
  async start(callSession, tools) {
1820
2291
  this._call = callSession;
1821
2292
  if (tools) this._tools = tools;
@@ -1852,9 +2323,10 @@ var GeminiRealtime = class {
1852
2323
  callbacks: {
1853
2324
  onmessage: (msg) => this._handleMessage(msg),
1854
2325
  onerror: (err) => {
1855
- console.error("[GeminiRealtime] SDK error:", err);
2326
+ this._log.error({ err }, "Gemini SDK error");
1856
2327
  },
1857
- onclose: () => {
2328
+ onclose: (ev) => {
2329
+ this._log.info({ code: ev?.code ?? "unknown" }, "Gemini connection closed");
1858
2330
  this._closed = true;
1859
2331
  }
1860
2332
  }
@@ -1872,7 +2344,7 @@ var GeminiRealtime = class {
1872
2344
  }
1873
2345
  }
1874
2346
  feedAudio(audio) {
1875
- if (this._session && !this._closed) {
2347
+ if (this._session && !this._closed && !this._toolCallInProgress) {
1876
2348
  const pcm8k = ulawToPcm16(audio);
1877
2349
  if (this._recorder) {
1878
2350
  this._recorder.writeInbound(pcm8k);
@@ -1886,6 +2358,14 @@ var GeminiRealtime = class {
1886
2358
  });
1887
2359
  }
1888
2360
  }
2361
+ async feedDtmf(digits) {
2362
+ if (this._session) {
2363
+ this._session.sendClientContent({
2364
+ turns: [{ role: "user", parts: [{ text: `[DTMF \uC785\uB825: ${digits}]` }] }],
2365
+ turnComplete: true
2366
+ });
2367
+ }
2368
+ }
1889
2369
  async stop() {
1890
2370
  this._closed = true;
1891
2371
  if (this._session) {
@@ -1904,7 +2384,9 @@ var GeminiRealtime = class {
1904
2384
  t.function.parameters ?? { type: "object", properties: {} }
1905
2385
  )
1906
2386
  })) : [];
1907
- toolDefs.push(HANG_UP_TOOL2);
2387
+ if (!this._builtinTools || this._builtinTools.has("hang_up" /* HANG_UP */)) toolDefs.push(HANG_UP_TOOL2);
2388
+ if (!this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */)) toolDefs.push(COLLECT_DTMF_TOOL2);
2389
+ if (!this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */)) toolDefs.push(SEND_DTMF_TOOL2);
1908
2390
  return toolDefs;
1909
2391
  }
1910
2392
  _handleMessage(msg) {
@@ -1924,9 +2406,11 @@ var GeminiRealtime = class {
1924
2406
  }
1925
2407
  }
1926
2408
  if (serverContent.turnComplete) {
2409
+ this._log.debug("Turn complete");
1927
2410
  this._flushAudioRemainder();
1928
2411
  }
1929
2412
  if (serverContent.interrupted) {
2413
+ this._log.info("Barge-in detected");
1930
2414
  if (this._call) {
1931
2415
  this._call.clearAudio();
1932
2416
  }
@@ -1935,16 +2419,22 @@ var GeminiRealtime = class {
1935
2419
  }
1936
2420
  const inputText = serverContent.inputTranscription?.text;
1937
2421
  if (inputText && this._call) {
2422
+ this._log.info("User: %s", inputText);
1938
2423
  this._call._emit("transcript", "user", inputText);
1939
2424
  }
1940
2425
  const outputText = serverContent.outputTranscription?.text;
1941
2426
  if (outputText && this._call) {
2427
+ this._log.info("Assistant: %s", outputText);
1942
2428
  this._call._emit("transcript", "assistant", outputText);
1943
2429
  }
1944
2430
  }
1945
2431
  if (msg.toolCall) {
1946
2432
  this._handleToolCall(msg.toolCall);
1947
2433
  }
2434
+ const toolCancellation = msg["toolCallCancellation"];
2435
+ if (toolCancellation) {
2436
+ this._log.info({ ids: toolCancellation.ids }, "Tool call cancelled");
2437
+ }
1948
2438
  }
1949
2439
  _handleAudioData(b64Data) {
1950
2440
  if (!this._call) return;
@@ -1957,9 +2447,9 @@ var GeminiRealtime = class {
1957
2447
  const combined = Buffer.concat([this._audioRemainder, ulaw]);
1958
2448
  const chunkSize = 160;
1959
2449
  const fullEnd = Math.floor(combined.length / chunkSize) * chunkSize;
1960
- for (let off = 0; off < fullEnd; off += chunkSize) {
1961
- this._call.sendAudio(combined.subarray(off, off + chunkSize));
1962
- this._sentAudioChunks++;
2450
+ if (fullEnd > 0) {
2451
+ this._call.sendAudio(combined.subarray(0, fullEnd));
2452
+ this._sentAudioChunks += fullEnd / chunkSize;
1963
2453
  }
1964
2454
  this._audioRemainder = combined.subarray(fullEnd);
1965
2455
  }
@@ -1977,31 +2467,75 @@ var GeminiRealtime = class {
1977
2467
  async _handleToolCall(toolCall) {
1978
2468
  const functionCalls = toolCall.functionCalls;
1979
2469
  if (!functionCalls) return;
2470
+ this._toolCallInProgress = true;
1980
2471
  const responses = [];
1981
2472
  for (const fc of functionCalls) {
1982
2473
  const name = fc.name ?? "";
1983
2474
  const fcId = fc.id ?? "";
1984
2475
  const args = fc.args ?? {};
2476
+ this._log.info({ tool: name, args }, "Tool call: %s", name);
1985
2477
  if (name === "hang_up") {
2478
+ this._log.info("hang_up: ending call");
1986
2479
  if (this._call) {
1987
- this._call.hangup();
2480
+ await this._call.hangup();
1988
2481
  }
1989
2482
  return;
1990
2483
  }
2484
+ if (name === "collect_dtmf") {
2485
+ if (this._call) {
2486
+ let result;
2487
+ try {
2488
+ this._log.info({ maxDigits: args["max_digits"] ?? 4, timeout: args["timeout"] ?? 5 }, "collect_dtmf: waiting for digits");
2489
+ result = await this._call.collectDtmf({
2490
+ maxDigits: args["max_digits"] ?? 4,
2491
+ finishOnKey: args["finish_on_key"] ?? "#",
2492
+ timeout: args["timeout"] ?? 5
2493
+ });
2494
+ this._log.info("DTMF collected: %s", result || "(empty)");
2495
+ } catch (err) {
2496
+ this._log.error({ err }, "collect_dtmf error");
2497
+ result = `Error: ${err}`;
2498
+ }
2499
+ responses.push({
2500
+ id: fcId,
2501
+ name,
2502
+ response: { result: result || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)" }
2503
+ });
2504
+ }
2505
+ continue;
2506
+ }
2507
+ if (name === "send_dtmf") {
2508
+ if (this._call) {
2509
+ let result;
2510
+ try {
2511
+ this._log.info('send_dtmf: digits="%s"', args["digits"] ?? "");
2512
+ await this._call.sendDtmfSequence(args["digits"] ?? "");
2513
+ result = "sent";
2514
+ this._log.info("send_dtmf: sent");
2515
+ } catch (err) {
2516
+ this._log.error({ err }, "send_dtmf error");
2517
+ result = `Error: ${err}`;
2518
+ }
2519
+ responses.push({ id: fcId, name, response: { result } });
2520
+ }
2521
+ continue;
2522
+ }
1991
2523
  if (!this._tools || !this._tools.has(name)) {
1992
- console.error(`[GeminiRealtime] Unknown tool: ${name}`);
2524
+ this._log.error("Unknown tool: %s", name);
1993
2525
  responses.push({ id: fcId, name, response: { error: `Unknown tool: ${name}` } });
1994
2526
  continue;
1995
2527
  }
1996
2528
  try {
1997
2529
  const result = await this._tools.call(name, args);
2530
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
2531
+ this._log.info("Tool result: %s -> %s", name, resultStr.substring(0, 200));
1998
2532
  responses.push({
1999
2533
  id: fcId,
2000
2534
  name,
2001
- response: { result: typeof result === "string" ? result : JSON.stringify(result) }
2535
+ response: { result: resultStr }
2002
2536
  });
2003
2537
  } catch (err) {
2004
- console.error(`[GeminiRealtime] Tool call error for ${name}:`, err);
2538
+ this._log.error({ err }, "Tool call failed: %s", name);
2005
2539
  responses.push({
2006
2540
  id: fcId,
2007
2541
  name,
@@ -2009,15 +2543,41 @@ var GeminiRealtime = class {
2009
2543
  });
2010
2544
  }
2011
2545
  }
2012
- if (this._session) {
2546
+ if (responses.length > 0 && this._session) {
2547
+ this._log.debug("Sending %d tool response(s)", responses.length);
2013
2548
  this._session.sendToolResponse({
2014
2549
  functionResponses: responses
2015
2550
  });
2016
2551
  }
2552
+ this._toolCallInProgress = false;
2017
2553
  }
2018
2554
  };
2019
2555
 
2020
2556
  // src/agent/pipeline/pipeline-session.ts
2557
+ var COLLECT_DTMF_TOOL3 = {
2558
+ function: {
2559
+ description: "\uC0AC\uC6A9\uC790\uB85C\uBD80\uD130 DTMF(\uC804\uD654 \uD0A4\uD328\uB4DC) \uC785\uB825\uC744 \uC218\uC9D1\uD569\uB2C8\uB2E4.",
2560
+ parameters: {
2561
+ properties: {
2562
+ max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
2563
+ finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
2564
+ timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
2565
+ },
2566
+ required: ["max_digits"]
2567
+ }
2568
+ }
2569
+ };
2570
+ var SEND_DTMF_TOOL3 = {
2571
+ function: {
2572
+ description: "DTMF \uC2E0\uD638\uB97C \uC804\uC1A1\uD569\uB2C8\uB2E4. ARS \uBA54\uB274 \uD0D0\uC0C9\uC774\uB098 \uB0B4\uC120\uBC88\uD638 \uC785\uB825 \uC2DC \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",
2573
+ parameters: {
2574
+ properties: {
2575
+ digits: { type: "string", description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30." }
2576
+ },
2577
+ required: ["digits"]
2578
+ }
2579
+ }
2580
+ };
2021
2581
  var PipelineSession = class {
2022
2582
  _stt;
2023
2583
  _llm;
@@ -2036,6 +2596,8 @@ var PipelineSession = class {
2036
2596
  _audioBuffer = [];
2037
2597
  _running = false;
2038
2598
  _speaking = false;
2599
+ _builtinTools = null;
2600
+ _log = NOOP_LOGGER;
2039
2601
  constructor(options) {
2040
2602
  this._stt = options.stt;
2041
2603
  this._llm = options.llm;
@@ -2056,10 +2618,23 @@ var PipelineSession = class {
2056
2618
  setRecorder(recorder) {
2057
2619
  this._recorder = recorder;
2058
2620
  }
2621
+ setBuiltinTools(tools) {
2622
+ this._builtinTools = tools;
2623
+ }
2624
+ setLogger(logger) {
2625
+ this._log = logger;
2626
+ if ("setLogger" in this._stt && typeof this._stt.setLogger === "function") {
2627
+ this._stt.setLogger(logger);
2628
+ }
2629
+ if ("setLogger" in this._tts && typeof this._tts.setLogger === "function") {
2630
+ this._tts.setLogger(logger);
2631
+ }
2632
+ }
2059
2633
  async start(callSession, tools) {
2060
2634
  this._callSession = callSession;
2061
2635
  this._tools = tools ?? null;
2062
2636
  this._running = true;
2637
+ this._log.info("PipelineSession started");
2063
2638
  this._conversation = [];
2064
2639
  if (this._systemPrompt) {
2065
2640
  this._conversation.push({
@@ -2069,11 +2644,11 @@ var PipelineSession = class {
2069
2644
  }
2070
2645
  if (this._greeting) {
2071
2646
  this._generateGreeting().catch((err) => {
2072
- console.error("[PipelineSession] Greeting error:", err);
2647
+ this._log.error({ err }, "Greeting error");
2073
2648
  });
2074
2649
  }
2075
2650
  this._runSttLoop().catch((err) => {
2076
- console.error("[PipelineSession] STT loop error:", err);
2651
+ this._log.error({ err }, "STT loop error");
2077
2652
  });
2078
2653
  }
2079
2654
  feedAudio(audio) {
@@ -2081,8 +2656,16 @@ var PipelineSession = class {
2081
2656
  this._audioBuffer.push(audio);
2082
2657
  }
2083
2658
  }
2659
+ async feedDtmf(digits) {
2660
+ this._conversation.push({
2661
+ role: "user",
2662
+ content: `[DTMF \uC785\uB825: ${digits}]`
2663
+ });
2664
+ await this._respond();
2665
+ }
2084
2666
  async stop() {
2085
2667
  this._running = false;
2668
+ this._log.info("PipelineSession stopped");
2086
2669
  this._audioBuffer = [];
2087
2670
  }
2088
2671
  async _runSttLoop() {
@@ -2096,8 +2679,10 @@ var PipelineSession = class {
2096
2679
  if (this._callSession) {
2097
2680
  this._callSession.clearAudio();
2098
2681
  }
2682
+ this._log.info('Barge-in: "%s"', event.transcript.substring(0, 30));
2099
2683
  }
2100
2684
  if (event.type === "final" && event.transcript.trim()) {
2685
+ this._log.info("STT: %s", event.transcript);
2101
2686
  await this._handleUserSpeech(event.transcript);
2102
2687
  }
2103
2688
  }
@@ -2122,11 +2707,37 @@ var PipelineSession = class {
2122
2707
  this._conversation.push({ role: "user", content: transcript });
2123
2708
  await this._respond();
2124
2709
  }
2710
+ _buildEffectiveTools() {
2711
+ const includeCollectDtmf = !this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */);
2712
+ const includeSendDtmf = !this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */);
2713
+ if (!includeCollectDtmf && !includeSendDtmf) return this._tools ?? void 0;
2714
+ const base = this._tools ? this._tools.fork() : new ToolRegistry();
2715
+ if (includeCollectDtmf) {
2716
+ base.register({
2717
+ name: "collect_dtmf",
2718
+ description: COLLECT_DTMF_TOOL3.function.description,
2719
+ parameters: COLLECT_DTMF_TOOL3.function.parameters.properties,
2720
+ required: COLLECT_DTMF_TOOL3.function.parameters.required,
2721
+ handler: async () => ""
2722
+ });
2723
+ }
2724
+ if (includeSendDtmf) {
2725
+ base.register({
2726
+ name: "send_dtmf",
2727
+ description: SEND_DTMF_TOOL3.function.description,
2728
+ parameters: SEND_DTMF_TOOL3.function.parameters.properties,
2729
+ required: SEND_DTMF_TOOL3.function.parameters.required,
2730
+ handler: async () => ""
2731
+ });
2732
+ }
2733
+ return base;
2734
+ }
2125
2735
  async _respond() {
2126
2736
  let fullResponse = "";
2127
2737
  const textChunks = [];
2738
+ const effectiveTools = this._buildEffectiveTools();
2128
2739
  const llmStream = this._llm.generate(this._conversation, {
2129
- tools: this._tools ?? void 0,
2740
+ tools: effectiveTools,
2130
2741
  temperature: this._temperature,
2131
2742
  maxTokens: this._maxTokens
2132
2743
  });
@@ -2140,15 +2751,44 @@ var PipelineSession = class {
2140
2751
  }
2141
2752
  }
2142
2753
  if (fullResponse.trim()) {
2754
+ this._log.info("Assistant: %s", fullResponse.substring(0, 100));
2143
2755
  this._conversation.push({ role: "assistant", content: fullResponse });
2144
2756
  await this._synthesizeAndSend(fullResponse);
2145
2757
  }
2146
2758
  }
2147
2759
  async _handleToolCall(chunk) {
2148
- if (!chunk.toolCall || !this._tools) return;
2760
+ if (!chunk.toolCall) return;
2149
2761
  const { id, name, arguments: argsStr } = chunk.toolCall;
2150
2762
  try {
2151
2763
  const args = JSON.parse(argsStr);
2764
+ if (name === "collect_dtmf" && this._callSession) {
2765
+ let result2;
2766
+ try {
2767
+ result2 = await this._callSession.collectDtmf({
2768
+ maxDigits: args["max_digits"] ?? 4,
2769
+ finishOnKey: args["finish_on_key"] ?? "#",
2770
+ timeout: args["timeout"] ?? 5
2771
+ });
2772
+ } catch (err) {
2773
+ result2 = `Error: ${err}`;
2774
+ }
2775
+ this._conversation.push({ role: "tool", content: result2 || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)", tool_call_id: id, name });
2776
+ await this._respond();
2777
+ return;
2778
+ }
2779
+ if (name === "send_dtmf" && this._callSession) {
2780
+ let result2;
2781
+ try {
2782
+ await this._callSession.sendDtmfSequence(args["digits"] ?? "");
2783
+ result2 = "sent";
2784
+ } catch (err) {
2785
+ result2 = `Error: ${err}`;
2786
+ }
2787
+ this._conversation.push({ role: "tool", content: result2, tool_call_id: id, name });
2788
+ await this._respond();
2789
+ return;
2790
+ }
2791
+ if (!this._tools) return;
2152
2792
  const result = await this._tools.call(name, args);
2153
2793
  this._conversation.push({
2154
2794
  role: "assistant",
@@ -2161,9 +2801,10 @@ var PipelineSession = class {
2161
2801
  tool_call_id: id,
2162
2802
  name
2163
2803
  });
2804
+ const effectiveTools = this._buildEffectiveTools();
2164
2805
  let followUpText = "";
2165
2806
  const followUpStream = this._llm.generate(this._conversation, {
2166
- tools: this._tools ?? void 0,
2807
+ tools: effectiveTools,
2167
2808
  temperature: this._temperature,
2168
2809
  maxTokens: this._maxTokens
2169
2810
  });
@@ -2178,7 +2819,7 @@ var PipelineSession = class {
2178
2819
  await this._synthesizeAndSend(followUpText);
2179
2820
  }
2180
2821
  } catch (err) {
2181
- console.error(`[PipelineSession] Tool call error for ${name}:`, err);
2822
+ this._log.error({ err }, "Tool call failed: %s", name);
2182
2823
  }
2183
2824
  }
2184
2825
  async _synthesizeAndSend(text) {
@@ -2204,7 +2845,7 @@ var PipelineSession = class {
2204
2845
  }
2205
2846
  }
2206
2847
  } catch (err) {
2207
- console.error("[PipelineSession] TTS error:", err);
2848
+ this._log.error({ err }, "TTS error");
2208
2849
  } finally {
2209
2850
  this._speaking = false;
2210
2851
  }
@@ -2214,6 +2855,10 @@ var PipelineSession = class {
2214
2855
  // src/agent/pipeline/deepgram-stt.ts
2215
2856
  var DeepgramSTT = class {
2216
2857
  _options;
2858
+ _log = NOOP_LOGGER;
2859
+ setLogger(logger) {
2860
+ this._log = logger;
2861
+ }
2217
2862
  constructor(options = {}) {
2218
2863
  this._options = {
2219
2864
  model: "nova-3",
@@ -2283,7 +2928,7 @@ var DeepgramSTT = class {
2283
2928
  }
2284
2929
  });
2285
2930
  ws.on("error", (err) => {
2286
- console.error("[DeepgramSTT] WebSocket error:", err.message);
2931
+ this._log.error({ err }, "Deepgram STT error");
2287
2932
  done = true;
2288
2933
  if (resolveWait) {
2289
2934
  resolveWait();
@@ -2294,6 +2939,7 @@ var DeepgramSTT = class {
2294
2939
  ws.on("open", resolve);
2295
2940
  ws.on("error", reject);
2296
2941
  });
2942
+ this._log.info("Deepgram STT connected");
2297
2943
  const feedPromise = (async () => {
2298
2944
  try {
2299
2945
  for await (const chunk of audioStream) {
@@ -2330,6 +2976,10 @@ var DeepgramSTT = class {
2330
2976
  // src/agent/pipeline/elevenlabs-tts.ts
2331
2977
  var ElevenLabsTTS = class {
2332
2978
  _options;
2979
+ _log = NOOP_LOGGER;
2980
+ setLogger(logger) {
2981
+ this._log = logger;
2982
+ }
2333
2983
  constructor(options = {}) {
2334
2984
  this._options = {
2335
2985
  voiceId: "EXAVITQu4vr4xnSDxMaL",
@@ -2429,7 +3079,7 @@ var ElevenLabsTTS = class {
2429
3079
  }
2430
3080
  });
2431
3081
  ws.on("error", (err) => {
2432
- console.error("[ElevenLabsTTS] WebSocket error:", err.message);
3082
+ this._log.error({ err }, "ElevenLabs TTS error");
2433
3083
  done = true;
2434
3084
  if (resolveWait) {
2435
3085
  resolveWait();
@@ -2440,16 +3090,19 @@ var ElevenLabsTTS = class {
2440
3090
  ws.on("open", resolve);
2441
3091
  ws.on("error", reject);
2442
3092
  });
3093
+ this._log.info("ElevenLabs TTS connected");
2443
3094
  const feedPromise = (async () => {
2444
3095
  try {
2445
3096
  for await (const chunk of textStream) {
2446
3097
  if (done) break;
2447
3098
  if (ws.readyState === 1) {
3099
+ this._log.debug("ElevenLabs sending text: %s", chunk.substring(0, 60));
2448
3100
  ws.send(JSON.stringify({ text: chunk }));
2449
3101
  }
2450
3102
  }
2451
3103
  } finally {
2452
3104
  if (ws.readyState === 1) {
3105
+ this._log.debug("ElevenLabs sending EOS");
2453
3106
  ws.send(JSON.stringify({ text: "" }));
2454
3107
  }
2455
3108
  }
@@ -2987,6 +3640,6 @@ function mcpServerHTTP(options) {
2987
3640
  };
2988
3641
  }
2989
3642
 
2990
- export { AnthropicLLM, AudioRecorder, CallSession, ClawOpsAgent, DECODE_TABLE, DeepSeekLLM, DeepgramSTT, ElevenLabsTTS, FireworksLLM, GeminiLLM, GeminiRealtime, GroqLLM, MCPClient, MistralLLM, OllamaLLM, OpenAICompatLLM, OpenAILLM, OpenAIRealtime, PerplexityLLM, PipelineSession, TogetherLLM, ToolRegistry, XaiLLM, functionTool, getTracingConfig, mcpServerHTTP, mcpServerStdio, pcm16ToUlaw, resamplePcm16, resetTracingConfig, setTracingConfig, ulawToPcm16, zodToToolParams };
3643
+ export { AnthropicLLM, AudioRecorder, BuiltinTool, CallSession, ClawOpsAgent, DECODE_TABLE, DeepSeekLLM, DeepgramSTT, ElevenLabsTTS, FireworksLLM, GeminiLLM, GeminiRealtime, GroqLLM, MCPClient, MistralLLM, OllamaLLM, OpenAICompatLLM, OpenAILLM, OpenAIRealtime, PerplexityLLM, PipelineSession, TogetherLLM, ToolRegistry, XaiLLM, createAgentLogger, createPipelineLogger, functionTool, getTracingConfig, mcpServerHTTP, mcpServerStdio, pcm16ToUlaw, resamplePcm16, resetTracingConfig, setTracingConfig, ulawToPcm16, zodToToolParams };
2991
3644
  //# sourceMappingURL=index.js.map
2992
3645
  //# sourceMappingURL=index.js.map