@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,9 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var chunk6IQN5RQD_cjs = require('../chunk-6IQN5RQD.cjs');
4
+ var pino = require('pino');
4
5
  var fs = require('fs');
5
6
  var path = require('path');
6
7
 
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
7
10
  function _interopNamespace(e) {
8
11
  if (e && e.__esModule) return e;
9
12
  var n = Object.create(null);
@@ -22,6 +25,7 @@ function _interopNamespace(e) {
22
25
  return Object.freeze(n);
23
26
  }
24
27
 
28
+ var pino__default = /*#__PURE__*/_interopDefault(pino);
25
29
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
26
30
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
31
 
@@ -345,6 +349,14 @@ function resamplePcm16(pcm, fromRate, toRate) {
345
349
  }
346
350
  return out;
347
351
  }
352
+ var DEFAULT_LOGGER = pino__default.default({ name: "clawops.agent" });
353
+ var NOOP_LOGGER = pino__default.default({ level: "silent" });
354
+ function createAgentLogger(userLogger) {
355
+ return userLogger ?? DEFAULT_LOGGER;
356
+ }
357
+ function createPipelineLogger(parent) {
358
+ return parent.child({ module: "pipeline" });
359
+ }
348
360
 
349
361
  // src/agent/control-ws.ts
350
362
  var INITIAL_RECONNECT_DELAY = 1e3;
@@ -373,6 +385,10 @@ var ControlWebSocket = class {
373
385
  _closed = false;
374
386
  _connectedResolve = null;
375
387
  _connectedPromise;
388
+ _log = NOOP_LOGGER;
389
+ setLogger(logger) {
390
+ this._log = logger;
391
+ }
376
392
  /** Register an event handler for a specific event type. */
377
393
  on(event, handler) {
378
394
  let list = this._handlers.get(event);
@@ -420,13 +436,14 @@ var ControlWebSocket = class {
420
436
  this._connectedResolve();
421
437
  this._connectedResolve = null;
422
438
  }
439
+ this._log.info("Control WS connected: %s", this._url);
423
440
  });
424
441
  ws.on("message", (data) => {
425
442
  try {
426
443
  const msg = JSON.parse(data.toString());
427
444
  this._dispatchEvent(msg);
428
445
  } catch {
429
- console.error("[ControlWebSocket] Failed to parse message");
446
+ this._log.warn("Control WS parse error");
430
447
  }
431
448
  });
432
449
  ws.on("close", () => {
@@ -435,7 +452,7 @@ var ControlWebSocket = class {
435
452
  }
436
453
  });
437
454
  ws.on("error", (err) => {
438
- console.error("[ControlWebSocket] Error:", err.message);
455
+ this._log.warn("Control WS error: %s", err.message);
439
456
  });
440
457
  }
441
458
  _dispatchEvent(event) {
@@ -446,11 +463,11 @@ var ControlWebSocket = class {
446
463
  const result = handler(event);
447
464
  if (result && typeof result.catch === "function") {
448
465
  result.catch((err) => {
449
- console.error(`[ControlWebSocket] Error in handler for ${event.event}:`, err);
466
+ this._log.error({ err }, "Control WS handler error: %s", event.event);
450
467
  });
451
468
  }
452
469
  } catch (err) {
453
- console.error(`[ControlWebSocket] Error in handler for ${event.event}:`, err);
470
+ this._log.error({ err }, "Control WS handler error: %s", event.event);
454
471
  }
455
472
  }
456
473
  }
@@ -458,10 +475,11 @@ var ControlWebSocket = class {
458
475
  _scheduleReconnect() {
459
476
  const delay = this._reconnectDelay;
460
477
  this._reconnectDelay = Math.min(this._reconnectDelay * 2, MAX_RECONNECT_DELAY);
478
+ this._log.info("Control WS reconnecting in %ds...", delay / 1e3);
461
479
  setTimeout(() => {
462
480
  if (!this._closed) {
463
481
  this._doConnect().catch((err) => {
464
- console.error("[ControlWebSocket] Reconnect failed:", err);
482
+ this._log.warn({ err }, "Control WS reconnect failed");
465
483
  this._scheduleReconnect();
466
484
  });
467
485
  }
@@ -473,6 +491,10 @@ var ControlWebSocket = class {
473
491
  var MCPClient = class {
474
492
  _servers = /* @__PURE__ */ new Map();
475
493
  _clients = /* @__PURE__ */ new Map();
494
+ _log = NOOP_LOGGER;
495
+ setLogger(logger) {
496
+ this._log = logger;
497
+ }
476
498
  /** Add an MCP server configuration. */
477
499
  addServer(name, config) {
478
500
  this._servers.set(name, config);
@@ -485,7 +507,7 @@ var MCPClient = class {
485
507
  const tools = await this._connectServer(name, config);
486
508
  allTools.push(...tools);
487
509
  } catch (err) {
488
- console.error(`[MCPClient] Failed to connect to server '${name}':`, err);
510
+ this._log.error({ err }, "MCP connection failed: %s", name);
489
511
  }
490
512
  }
491
513
  return allTools;
@@ -494,12 +516,13 @@ var MCPClient = class {
494
516
  async disconnect() {
495
517
  for (const [name, client] of this._clients) {
496
518
  try {
519
+ this._log.debug("MCP closing: %s", name);
497
520
  const c = client;
498
521
  if (c.close) {
499
522
  await c.close();
500
523
  }
501
524
  } catch (err) {
502
- console.error(`[MCPClient] Error disconnecting from '${name}':`, err);
525
+ this._log.error({ err }, "MCP disconnect error: %s", name);
503
526
  }
504
527
  }
505
528
  this._clients.clear();
@@ -508,6 +531,11 @@ var MCPClient = class {
508
531
  const sdk = await import('@modelcontextprotocol/sdk/client/index.js');
509
532
  const { Client } = sdk;
510
533
  const client = new Client({ name: `clawops-${name}`, version: "1.0.0" });
534
+ if (config.type === "stdio") {
535
+ this._log.debug("MCP connecting (stdio): %s", config["command"]);
536
+ } else if (config.type === "http") {
537
+ this._log.debug("MCP connecting (http): %s", config["url"]);
538
+ }
511
539
  let transport;
512
540
  if (config.type === "stdio") {
513
541
  const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
@@ -536,6 +564,7 @@ var MCPClient = class {
536
564
  parameters: inputSchema.properties ?? {},
537
565
  required: inputSchema.required ?? [],
538
566
  handler: async (args) => {
567
+ this._log.debug("MCP call_tool: %s", toolDef.name);
539
568
  const result = await client.callTool({
540
569
  name: toolDef.name,
541
570
  arguments: args
@@ -548,6 +577,8 @@ var MCPClient = class {
548
577
  }
549
578
  });
550
579
  }
580
+ this._log.info("MCP server connected: %d tools found", tools.length);
581
+ this._log.debug("MCP tools: %s", tools.map((t) => t.name));
551
582
  return tools;
552
583
  }
553
584
  };
@@ -578,6 +609,20 @@ function buildMediaResponse(audioBase64) {
578
609
  }
579
610
  });
580
611
  }
612
+ var VALID_DTMF_DIGITS = new Set("0123456789*#");
613
+ function parseDtmfEvent(data) {
614
+ const dtmf = data["dtmf"];
615
+ return {
616
+ digit: dtmf["digit"] ?? "",
617
+ track: dtmf["track"] ?? ""
618
+ };
619
+ }
620
+ function buildDtmfMessage(digit) {
621
+ if (!VALID_DTMF_DIGITS.has(digit)) {
622
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 DTMF digit: ${digit}`);
623
+ }
624
+ return JSON.stringify({ event: "dtmf", dtmf: { digit } });
625
+ }
581
626
  var MediaWebSocket = class {
582
627
  _ws = null;
583
628
  _audioQueue = [];
@@ -586,6 +631,12 @@ var MediaWebSocket = class {
586
631
  _onAudio = null;
587
632
  _onStart = null;
588
633
  _onClose = null;
634
+ _onDtmf = null;
635
+ _markWaiters = /* @__PURE__ */ new Map();
636
+ _log = NOOP_LOGGER;
637
+ setLogger(logger) {
638
+ this._log = logger;
639
+ }
589
640
  /** Set the handler for inbound audio data. */
590
641
  onAudio(handler) {
591
642
  this._onAudio = handler;
@@ -598,6 +649,20 @@ var MediaWebSocket = class {
598
649
  onClose(handler) {
599
650
  this._onClose = handler;
600
651
  }
652
+ /** Set the handler for inbound DTMF events. */
653
+ onDtmf(handler) {
654
+ this._onDtmf = handler;
655
+ }
656
+ /** Send a single DTMF digit to the platform. */
657
+ sendDtmf(digit) {
658
+ if (this._ws && this._ws.readyState === 1) {
659
+ this._ws.send(buildDtmfMessage(digit));
660
+ }
661
+ }
662
+ /** Whether the WebSocket is connected. */
663
+ get isConnected() {
664
+ return this._ws !== null && this._ws.readyState === 1 && !this._closed;
665
+ }
601
666
  /** Connect to a media WebSocket URL with Bearer authentication. */
602
667
  async connect(url, apiKey) {
603
668
  const { WebSocket } = await import('ws');
@@ -613,6 +678,7 @@ var MediaWebSocket = class {
613
678
  ws.on("open", () => {
614
679
  this._startSendLoop();
615
680
  resolve();
681
+ this._log.info("Media WS connected: %s", url);
616
682
  });
617
683
  ws.on("message", (data) => {
618
684
  try {
@@ -631,7 +697,7 @@ var MediaWebSocket = class {
631
697
  if (!this._ws) {
632
698
  reject(err);
633
699
  }
634
- console.error("[MediaWebSocket] Error:", err.message);
700
+ this._log.error({ err }, "Media WS error");
635
701
  });
636
702
  });
637
703
  }
@@ -657,6 +723,34 @@ var MediaWebSocket = class {
657
723
  );
658
724
  }
659
725
  }
726
+ /** Wait for all queued audio to be sent. */
727
+ flush() {
728
+ if (this._audioQueue.length === 0 || this._closed) return Promise.resolve();
729
+ return new Promise((resolve) => {
730
+ const check = () => {
731
+ if (this._audioQueue.length === 0 || this._closed) {
732
+ resolve();
733
+ } else {
734
+ setTimeout(check, 5);
735
+ }
736
+ };
737
+ setTimeout(check, 5);
738
+ });
739
+ }
740
+ /** Wait for a named mark to be echoed back by the server. */
741
+ waitForMark(name, timeoutMs = 5e3) {
742
+ if (this._closed) return Promise.resolve();
743
+ return new Promise((resolve) => {
744
+ const timer = setTimeout(() => {
745
+ this._markWaiters.delete(name);
746
+ resolve();
747
+ }, timeoutMs);
748
+ this._markWaiters.set(name, () => {
749
+ clearTimeout(timer);
750
+ resolve();
751
+ });
752
+ });
753
+ }
660
754
  /** Close the media WebSocket. */
661
755
  close() {
662
756
  this._closed = true;
@@ -682,6 +776,24 @@ var MediaWebSocket = class {
682
776
  }
683
777
  break;
684
778
  }
779
+ case "dtmf": {
780
+ const dtmfEvt = parseDtmfEvent(msg);
781
+ if (this._onDtmf) {
782
+ this._onDtmf(dtmfEvt.digit);
783
+ }
784
+ break;
785
+ }
786
+ case "mark": {
787
+ const markName = msg["mark"]?.["name"];
788
+ if (markName) {
789
+ const resolve = this._markWaiters.get(markName);
790
+ if (resolve) {
791
+ this._markWaiters.delete(markName);
792
+ resolve();
793
+ }
794
+ }
795
+ break;
796
+ }
685
797
  case "stop": {
686
798
  this.close();
687
799
  break;
@@ -746,6 +858,10 @@ var AudioRecorder = class {
746
858
  _mixWritten = 0;
747
859
  _startTime = 0;
748
860
  _started = false;
861
+ _log = NOOP_LOGGER;
862
+ setLogger(logger) {
863
+ this._log = logger;
864
+ }
749
865
  constructor(recordingPath, callId) {
750
866
  this._dir = path__namespace.join(recordingPath, callId);
751
867
  }
@@ -760,6 +876,7 @@ var AudioRecorder = class {
760
876
  fs__namespace.writeSync(this._fdMix, header);
761
877
  this._startTime = performance.now();
762
878
  this._started = true;
879
+ this._log.info("Recording started: %s", this._dir);
763
880
  }
764
881
  _expectedBytes() {
765
882
  const elapsed = (performance.now() - this._startTime) / 1e3;
@@ -812,7 +929,7 @@ var AudioRecorder = class {
812
929
  this._inWritten += pcm16_8k.length;
813
930
  this._writeToMix(pcm16_8k, posBefore);
814
931
  } catch (err) {
815
- console.error("Error writing inbound audio:", err);
932
+ this._log.error({ err }, "Recording write error (inbound)");
816
933
  }
817
934
  }
818
935
  writeOutbound(pcm16_8k) {
@@ -825,7 +942,7 @@ var AudioRecorder = class {
825
942
  this._outWritten += pcm16_8k.length;
826
943
  this._writeToMix(pcm16_8k, posBefore);
827
944
  } catch (err) {
828
- console.error("Error writing outbound audio:", err);
945
+ this._log.error({ err }, "Recording write error (outbound)");
829
946
  }
830
947
  }
831
948
  stop() {
@@ -846,8 +963,10 @@ var AudioRecorder = class {
846
963
  fs__namespace.writeSync(fd, makeWavHeader(maxWritten), 0, 44, 0);
847
964
  fs__namespace.closeSync(fd);
848
965
  }
966
+ const maxSec = maxWritten / 16e3;
967
+ this._log.info("Recording stopped: %s (%ds)", this._dir, maxSec);
849
968
  } catch (err) {
850
- console.error("Error stopping recorder:", err);
969
+ this._log.error({ err }, "Recording stop error");
851
970
  } finally {
852
971
  this._fdIn = null;
853
972
  this._fdOut = null;
@@ -870,6 +989,14 @@ var CallSession = class {
870
989
  _sendAudioFn = null;
871
990
  _clearAudioFn = null;
872
991
  _hangupFn = null;
992
+ /** @internal */
993
+ _sendDtmfFn = null;
994
+ /** @internal */
995
+ _isTransportConnected = null;
996
+ _dtmfCollectorActive = false;
997
+ _dtmfResolvers = [];
998
+ _dtmfBuffer = [];
999
+ _log = NOOP_LOGGER;
873
1000
  _handlers = /* @__PURE__ */ new Map();
874
1001
  _endedPromise;
875
1002
  _resolveEnded;
@@ -886,6 +1013,9 @@ var CallSession = class {
886
1013
  this._resolveEnded = resolve;
887
1014
  });
888
1015
  }
1016
+ setLogger(logger) {
1017
+ this._log = logger;
1018
+ }
889
1019
  get status() {
890
1020
  return this._status;
891
1021
  }
@@ -893,10 +1023,12 @@ var CallSession = class {
893
1023
  return (Date.now() - this.startTime.getTime()) / 1e3;
894
1024
  }
895
1025
  /** Bind transport functions (called internally by the agent). */
896
- _bindTransport(send, clear, hangup) {
1026
+ _bindTransport(send, clear, hangup, sendDtmf, isConnected) {
897
1027
  this._sendAudioFn = send;
898
1028
  this._clearAudioFn = clear;
899
1029
  this._hangupFn = hangup;
1030
+ if (sendDtmf) this._sendDtmfFn = sendDtmf;
1031
+ if (isConnected) this._isTransportConnected = isConnected;
900
1032
  this._status = "active";
901
1033
  }
902
1034
  /** Send PCM16 or ulaw audio to the caller. */
@@ -911,10 +1043,78 @@ var CallSession = class {
911
1043
  this._clearAudioFn();
912
1044
  }
913
1045
  }
914
- /** Hang up the call. */
915
- hangup() {
1046
+ /** Hang up the call, waiting for pending audio to finish. */
1047
+ async hangup() {
916
1048
  if (this._hangupFn) {
917
- this._hangupFn();
1049
+ await this._hangupFn();
1050
+ }
1051
+ }
1052
+ /** @internal Route a received DTMF digit to an active collector or buffer. */
1053
+ _routeDtmf(digit) {
1054
+ if (this._dtmfCollectorActive && this._dtmfResolvers.length > 0) {
1055
+ const resolve = this._dtmfResolvers.shift();
1056
+ resolve(digit);
1057
+ } else {
1058
+ this._dtmfBuffer.push(digit);
1059
+ }
1060
+ }
1061
+ /** Collect DTMF digits from the caller. */
1062
+ async collectDtmf(options) {
1063
+ if (this._dtmfCollectorActive) {
1064
+ throw new Error("\uC774\uBBF8 DTMF \uC218\uC9D1 \uC911\uC785\uB2C8\uB2E4");
1065
+ }
1066
+ const { maxDigits, finishOnKey = "#", timeout = 5 } = options;
1067
+ this._dtmfCollectorActive = true;
1068
+ const collected = [];
1069
+ try {
1070
+ while (collected.length < maxDigits) {
1071
+ if (this._dtmfBuffer.length > 0) {
1072
+ const digit2 = this._dtmfBuffer.shift();
1073
+ if (digit2 === finishOnKey) break;
1074
+ collected.push(digit2);
1075
+ continue;
1076
+ }
1077
+ const digit = await Promise.race([
1078
+ new Promise((resolve) => {
1079
+ this._dtmfResolvers.push(resolve);
1080
+ }),
1081
+ new Promise((resolve) => {
1082
+ setTimeout(() => resolve(null), timeout * 1e3);
1083
+ })
1084
+ ]);
1085
+ if (digit === null) break;
1086
+ if (digit === finishOnKey) break;
1087
+ collected.push(digit);
1088
+ }
1089
+ } finally {
1090
+ this._dtmfCollectorActive = false;
1091
+ this._dtmfResolvers = [];
1092
+ this._dtmfBuffer = [];
1093
+ }
1094
+ const result = collected.join("");
1095
+ this._log.info("DTMF collected: %s", result);
1096
+ return result;
1097
+ }
1098
+ /** Send a sequence of DTMF digits. */
1099
+ async sendDtmfSequence(digits) {
1100
+ if (!this._sendDtmfFn) {
1101
+ throw new Error("DTMF \uC804\uC1A1 \uD568\uC218\uAC00 \uBC14\uC778\uB529\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
1102
+ }
1103
+ for (const ch of digits) {
1104
+ if (this._isTransportConnected && !this._isTransportConnected()) {
1105
+ throw new Error("DTMF \uC804\uC1A1 \uC911 \uC5F0\uACB0\uC774 \uB04A\uC5B4\uC84C\uC2B5\uB2C8\uB2E4");
1106
+ }
1107
+ if (ch === "w") {
1108
+ await new Promise((r) => setTimeout(r, 500));
1109
+ } else if (ch === "W") {
1110
+ await new Promise((r) => setTimeout(r, 1e3));
1111
+ } else if ("0123456789*#".includes(ch)) {
1112
+ if (this._sendDtmfFn) {
1113
+ await this._sendDtmfFn(ch);
1114
+ }
1115
+ } else {
1116
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 DTMF \uBB38\uC790: ${ch}`);
1117
+ }
918
1118
  }
919
1119
  }
920
1120
  /** Register an event handler. */
@@ -945,17 +1145,44 @@ var CallSession = class {
945
1145
  const result = handler(this, ...args);
946
1146
  if (result && typeof result.catch === "function") {
947
1147
  result.catch((err) => {
948
- console.error(`[CallSession] Error in ${event} handler:`, err);
1148
+ this._log.error({ err }, "CallSession handler error: %s", event);
949
1149
  });
950
1150
  }
951
1151
  } catch (err) {
952
- console.error(`[CallSession] Error in ${event} handler:`, err);
1152
+ this._log.error({ err }, "CallSession handler error: %s", event);
953
1153
  }
954
1154
  }
955
1155
  }
956
1156
  }
957
1157
  };
958
1158
 
1159
+ // src/agent/builtin-tool.ts
1160
+ var BuiltinTool = /* @__PURE__ */ ((BuiltinTool2) => {
1161
+ BuiltinTool2["HANG_UP"] = "hang_up";
1162
+ BuiltinTool2["COLLECT_DTMF"] = "collect_dtmf";
1163
+ BuiltinTool2["SEND_DTMF"] = "send_dtmf";
1164
+ BuiltinTool2["ALL"] = "all";
1165
+ BuiltinTool2["NONE"] = "none";
1166
+ return BuiltinTool2;
1167
+ })(BuiltinTool || {});
1168
+ var INDIVIDUAL_TOOLS = /* @__PURE__ */ new Set([
1169
+ "hang_up" /* HANG_UP */,
1170
+ "collect_dtmf" /* COLLECT_DTMF */,
1171
+ "send_dtmf" /* SEND_DTMF */
1172
+ ]);
1173
+ function resolveBuiltinTools(value) {
1174
+ if (typeof value === "string") {
1175
+ if (value === "all" /* ALL */) {
1176
+ return new Set(INDIVIDUAL_TOOLS);
1177
+ }
1178
+ if (value === "none" /* NONE */) {
1179
+ return /* @__PURE__ */ new Set();
1180
+ }
1181
+ return /* @__PURE__ */ new Set([value]);
1182
+ }
1183
+ return new Set(value.filter((t) => INDIVIDUAL_TOOLS.has(t)));
1184
+ }
1185
+
959
1186
  // src/agent/tool.ts
960
1187
  function functionTool(fn) {
961
1188
  return fn;
@@ -1164,6 +1391,15 @@ var ClawOpsAgent = class {
1164
1391
  _recording;
1165
1392
  _recordingPath;
1166
1393
  _activeSessions = /* @__PURE__ */ new Map();
1394
+ _builtinTools;
1395
+ _passiveDtmfDebounceMs;
1396
+ _passiveDtmfBuffer = [];
1397
+ _passiveDtmfTimer = null;
1398
+ _passiveDtmfCallId = null;
1399
+ _callSessions = /* @__PURE__ */ new Map();
1400
+ _log;
1401
+ _pipelineLog;
1402
+ _isPipelineSession = false;
1167
1403
  constructor(options) {
1168
1404
  this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
1169
1405
  this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
@@ -1173,9 +1409,14 @@ var ClawOpsAgent = class {
1173
1409
  this._recording = options.recording ?? false;
1174
1410
  this._recordingPath = options.recordingPath ?? "./recordings";
1175
1411
  this._mcpServers = options.mcpServers ?? [];
1412
+ this._builtinTools = resolveBuiltinTools(options.builtinTools ?? "all" /* ALL */);
1413
+ this._passiveDtmfDebounceMs = options.passiveDtmfDebounceMs ?? 500;
1176
1414
  if (options.tracing) {
1177
1415
  setTracingConfig(options.tracing);
1178
1416
  }
1417
+ this._log = createAgentLogger(options.logger);
1418
+ this._pipelineLog = createPipelineLogger(this._log);
1419
+ this._isPipelineSession = "_stt" in this._session && "_llm" in this._session;
1179
1420
  }
1180
1421
  /**
1181
1422
  * Register a function tool.
@@ -1187,7 +1428,9 @@ var ClawOpsAgent = class {
1187
1428
  tool(nameOrTool, description, parameters, handler) {
1188
1429
  if (typeof nameOrTool === "string") {
1189
1430
  if (!description || !parameters || !handler) {
1190
- throw new chunk6IQN5RQD_cjs.AgentError("tool(name, description, parameters, handler) requires all arguments.");
1431
+ throw new chunk6IQN5RQD_cjs.AgentError(
1432
+ "tool(name, description, parameters, handler) requires all arguments."
1433
+ );
1191
1434
  }
1192
1435
  this._tools.register({
1193
1436
  name: nameOrTool,
@@ -1223,7 +1466,9 @@ var ClawOpsAgent = class {
1223
1466
  throw new chunk6IQN5RQD_cjs.AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1224
1467
  }
1225
1468
  if (!this._accountId) {
1226
- throw new chunk6IQN5RQD_cjs.AgentError("Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option.");
1469
+ throw new chunk6IQN5RQD_cjs.AgentError(
1470
+ "Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option."
1471
+ );
1227
1472
  }
1228
1473
  this._controlWs = new ControlWebSocket({
1229
1474
  baseUrl: this._baseUrl,
@@ -1231,6 +1476,7 @@ var ClawOpsAgent = class {
1231
1476
  accountId: this._accountId,
1232
1477
  number: this._fromNumber
1233
1478
  });
1479
+ this._controlWs.setLogger(this._log);
1234
1480
  this._controlWs.on("call.incoming", (event) => this._handleIncoming(event));
1235
1481
  this._controlWs.on("call.ended", (event) => this._handleEnded(event));
1236
1482
  this._controlWs.on("call.outbound_ready", (event) => this._handleOutboundReady(event));
@@ -1244,7 +1490,7 @@ var ClawOpsAgent = class {
1244
1490
  `Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
1245
1491
  );
1246
1492
  }
1247
- console.log(`[ClawOpsAgent] Connected on ${this._fromNumber}`);
1493
+ this._log.info("ClawOpsAgent connected on %s", this._fromNumber);
1248
1494
  }
1249
1495
  /**
1250
1496
  * Connect and block until disconnected.
@@ -1270,7 +1516,8 @@ var ClawOpsAgent = class {
1270
1516
  session._markEnded();
1271
1517
  }
1272
1518
  this._activeSessions.clear();
1273
- console.log("[ClawOpsAgent] Disconnected");
1519
+ this._callSessions.clear();
1520
+ this._log.info("ClawOpsAgent disconnected");
1274
1521
  }
1275
1522
  /**
1276
1523
  * Initiate an outbound call.
@@ -1305,8 +1552,9 @@ var ClawOpsAgent = class {
1305
1552
  callSession.on(evt, handler);
1306
1553
  }
1307
1554
  }
1555
+ callSession.setLogger(this._log);
1308
1556
  this._activeSessions.set(callSession.callId, callSession);
1309
- console.log(`[ClawOpsAgent] Outbound call initiated: ${this._fromNumber} -> ${to} (${callSession.callId})`);
1557
+ this._log.info("Outbound call initiated: %s -> %s (%s)", this._fromNumber, to, callSession.callId);
1310
1558
  return callSession;
1311
1559
  }
1312
1560
  _handleIncoming(event) {
@@ -1325,13 +1573,15 @@ var ClawOpsAgent = class {
1325
1573
  session.on(evt, handler);
1326
1574
  }
1327
1575
  }
1576
+ session.setLogger(this._log);
1328
1577
  this._activeSessions.set(callId, session);
1578
+ this._log.info("Incoming call: %s -> %s (%s)", fromNumber, this._fromNumber, callId);
1329
1579
  if (this._controlWs) {
1330
1580
  this._controlWs.send({ event: "call.accept", callId });
1331
1581
  }
1332
1582
  if (mediaUrl) {
1333
1583
  this._startCallSession(session, mediaUrl).catch((err) => {
1334
- console.error(`[ClawOpsAgent] Error in call session ${callId}:`, err);
1584
+ this._log.error({ err }, "Call session error: %s", callId);
1335
1585
  });
1336
1586
  }
1337
1587
  }
@@ -1339,6 +1589,7 @@ var ClawOpsAgent = class {
1339
1589
  const callId = event["callId"];
1340
1590
  const session = this._activeSessions.get(callId);
1341
1591
  if (session) {
1592
+ this._log.info("Call ended (server): %s", callId);
1342
1593
  session._markEnded();
1343
1594
  this._activeSessions.delete(callId);
1344
1595
  }
@@ -1355,6 +1606,7 @@ var ClawOpsAgent = class {
1355
1606
  accountId: this._accountId,
1356
1607
  direction: "outbound"
1357
1608
  });
1609
+ session.setLogger(this._log);
1358
1610
  for (const [evt, handlers] of this._handlers) {
1359
1611
  for (const handler of handlers) {
1360
1612
  session.on(evt, handler);
@@ -1363,8 +1615,9 @@ var ClawOpsAgent = class {
1363
1615
  this._activeSessions.set(callId, session);
1364
1616
  }
1365
1617
  if (mediaUrl) {
1618
+ this._log.info("Outbound call answered: %s -> %s (%s)", this._fromNumber, session.toNumber, callId);
1366
1619
  this._startCallSession(session, mediaUrl).catch((err) => {
1367
- console.error(`[ClawOpsAgent] Error in call session ${callId}:`, err);
1620
+ this._log.error({ err }, "Call session error: %s", callId);
1368
1621
  });
1369
1622
  }
1370
1623
  }
@@ -1372,18 +1625,43 @@ var ClawOpsAgent = class {
1372
1625
  const callId = event["callId"];
1373
1626
  const session = this._activeSessions.get(callId);
1374
1627
  if (session) {
1375
- console.log(`[ClawOpsAgent] Outbound call ringing: ${callId}`);
1628
+ this._log.info("Outbound call ringing: %s", callId);
1376
1629
  }
1377
1630
  }
1378
1631
  _handleFailed(event) {
1379
1632
  const callId = event["callId"];
1380
1633
  const session = this._activeSessions.get(callId);
1381
1634
  if (session) {
1635
+ this._log.info("Outbound call failed: %s (%s)", callId, event["reason"] ?? "failed");
1382
1636
  session._emit("call_failed", event["reason"] ?? "failed");
1383
1637
  session._markEnded();
1384
1638
  this._activeSessions.delete(callId);
1385
1639
  }
1386
1640
  }
1641
+ _onDtmfEvent(callSession, digit) {
1642
+ callSession._emit("dtmf", digit);
1643
+ callSession._routeDtmf(digit);
1644
+ if (callSession._dtmfCollectorActive) {
1645
+ callSession.clearAudio();
1646
+ return;
1647
+ }
1648
+ this._passiveDtmfBuffer.push(digit);
1649
+ this._passiveDtmfCallId = callSession.callId;
1650
+ if (this._passiveDtmfTimer) {
1651
+ clearTimeout(this._passiveDtmfTimer);
1652
+ }
1653
+ this._passiveDtmfTimer = setTimeout(() => {
1654
+ const digits = this._passiveDtmfBuffer.join("");
1655
+ this._passiveDtmfBuffer = [];
1656
+ const sessionHandler = this._passiveDtmfCallId ? this._callSessions.get(this._passiveDtmfCallId) : null;
1657
+ this._passiveDtmfCallId = null;
1658
+ if (digits && sessionHandler && sessionHandler.feedDtmf) {
1659
+ sessionHandler.feedDtmf(digits).catch((err) => {
1660
+ this._log.error({ err }, "DTMF feed error");
1661
+ });
1662
+ }
1663
+ }, this._passiveDtmfDebounceMs);
1664
+ }
1387
1665
  async _startCallSession(session, mediaWsUrl) {
1388
1666
  await withSpan(
1389
1667
  "clawops.call_session",
@@ -1398,22 +1676,25 @@ var ClawOpsAgent = class {
1398
1676
  if (this._mcpServers.length > 0) {
1399
1677
  for (const serverConfig of this._mcpServers) {
1400
1678
  const client = new MCPClient();
1679
+ client.setLogger(this._log);
1401
1680
  client.addServer("mcp", serverConfig);
1402
1681
  try {
1403
1682
  const tools = await client.connect();
1404
1683
  sessionTools.registerMcpTools(tools);
1405
1684
  mcpClients.push(client);
1406
1685
  } catch (err) {
1407
- console.error("[ClawOpsAgent] MCP connection error:", err);
1686
+ this._log.error({ err }, "MCP connection error");
1408
1687
  }
1409
1688
  }
1410
1689
  }
1411
1690
  let recorder = null;
1412
1691
  if (this._recording) {
1413
1692
  recorder = new AudioRecorder(this._recordingPath, session.callId);
1693
+ recorder.setLogger(this._log);
1414
1694
  recorder.start();
1415
1695
  }
1416
1696
  const mediaWs = new MediaWebSocket();
1697
+ mediaWs.setLogger(this._log);
1417
1698
  session._bindTransport(
1418
1699
  (audio) => {
1419
1700
  mediaWs.sendAudio(audio.toString("base64"));
@@ -1421,9 +1702,17 @@ var ClawOpsAgent = class {
1421
1702
  () => {
1422
1703
  mediaWs.sendClear();
1423
1704
  },
1424
- () => {
1705
+ async () => {
1706
+ await mediaWs.flush();
1707
+ const markName = `hangup-${Date.now()}`;
1708
+ mediaWs.sendMark(markName);
1709
+ await mediaWs.waitForMark(markName, 5e3);
1425
1710
  mediaWs.close();
1426
- }
1711
+ },
1712
+ async (digit) => {
1713
+ mediaWs.sendDtmf(digit);
1714
+ },
1715
+ () => mediaWs.isConnected
1427
1716
  );
1428
1717
  const sessionHandler = this._session;
1429
1718
  if ("setToolRegistry" in sessionHandler && typeof sessionHandler.setToolRegistry === "function") {
@@ -1432,6 +1721,13 @@ var ClawOpsAgent = class {
1432
1721
  if (recorder && "setRecorder" in sessionHandler && typeof sessionHandler.setRecorder === "function") {
1433
1722
  sessionHandler.setRecorder(recorder);
1434
1723
  }
1724
+ if ("setBuiltinTools" in sessionHandler && typeof sessionHandler.setBuiltinTools === "function") {
1725
+ sessionHandler.setBuiltinTools(this._builtinTools);
1726
+ }
1727
+ if ("setLogger" in sessionHandler && typeof sessionHandler.setLogger === "function") {
1728
+ sessionHandler.setLogger(this._isPipelineSession ? this._pipelineLog : this._log);
1729
+ }
1730
+ this._callSessions.set(session.callId, sessionHandler);
1435
1731
  mediaWs.onAudio((ulawAudio, _timestamp) => {
1436
1732
  if (sessionHandler) {
1437
1733
  sessionHandler.feedAudio(ulawAudio);
@@ -1440,7 +1736,11 @@ var ClawOpsAgent = class {
1440
1736
  recorder.writeInbound(ulawToPcm16(ulawAudio));
1441
1737
  }
1442
1738
  });
1739
+ mediaWs.onDtmf((digit) => {
1740
+ this._onDtmfEvent(session, digit);
1741
+ });
1443
1742
  mediaWs.onClose(() => {
1743
+ this._log.info("Media stream stopped: %s", session.callId);
1444
1744
  if (recorder) {
1445
1745
  recorder.stop();
1446
1746
  }
@@ -1449,11 +1749,12 @@ var ClawOpsAgent = class {
1449
1749
  session._emit("call_start");
1450
1750
  try {
1451
1751
  await mediaWs.connect(mediaWsUrl, this._apiKey);
1752
+ this._log.info("Media stream started: %s", session.callId);
1452
1753
  await sessionHandler.start(session, sessionTools);
1453
1754
  await session.wait();
1454
1755
  await sessionHandler.stop();
1455
1756
  } catch (err) {
1456
- console.error(`[ClawOpsAgent] Call session error:`, err);
1757
+ this._log.error({ err }, "Call session error: %s", session.callId);
1457
1758
  } finally {
1458
1759
  if (mcpClients.length > 0) {
1459
1760
  sessionTools.clearMcpTools();
@@ -1468,6 +1769,7 @@ var ClawOpsAgent = class {
1468
1769
  session._emit("call_end");
1469
1770
  session._markEnded();
1470
1771
  this._activeSessions.delete(session.callId);
1772
+ this._callSessions.delete(session.callId);
1471
1773
  }
1472
1774
  }
1473
1775
  );
@@ -1480,7 +1782,40 @@ var HANG_UP_TOOL = {
1480
1782
  type: "function",
1481
1783
  name: "hang_up",
1482
1784
  description: "End the phone call. Use when the conversation is finished or the caller says goodbye.",
1483
- parameters: { type: "object", properties: {}, required: [] }
1785
+ parameters: {
1786
+ type: "object",
1787
+ properties: {},
1788
+ required: []
1789
+ }
1790
+ };
1791
+ var COLLECT_DTMF_TOOL = {
1792
+ type: "function",
1793
+ name: "collect_dtmf",
1794
+ 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.",
1795
+ parameters: {
1796
+ type: "object",
1797
+ properties: {
1798
+ max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
1799
+ finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
1800
+ timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
1801
+ },
1802
+ required: ["max_digits"]
1803
+ }
1804
+ };
1805
+ var SEND_DTMF_TOOL = {
1806
+ type: "function",
1807
+ name: "send_dtmf",
1808
+ 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.",
1809
+ parameters: {
1810
+ type: "object",
1811
+ properties: {
1812
+ digits: {
1813
+ type: "string",
1814
+ description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30."
1815
+ }
1816
+ },
1817
+ required: ["digits"]
1818
+ }
1484
1819
  };
1485
1820
  var OpenAIRealtime = class {
1486
1821
  _apiKey;
@@ -1490,6 +1825,14 @@ var OpenAIRealtime = class {
1490
1825
  _language;
1491
1826
  _eagerness;
1492
1827
  _greeting;
1828
+ _log = NOOP_LOGGER;
1829
+ _builtinTools = null;
1830
+ setLogger(logger) {
1831
+ this._log = logger;
1832
+ }
1833
+ setBuiltinTools(tools) {
1834
+ this._builtinTools = tools;
1835
+ }
1493
1836
  _ws = null;
1494
1837
  _call = null;
1495
1838
  _tools = null;
@@ -1500,6 +1843,9 @@ var OpenAIRealtime = class {
1500
1843
  _responseStartTs = null;
1501
1844
  _sentAudioChunks = 0;
1502
1845
  _audioRemainder = Buffer.alloc(0);
1846
+ // Response state tracking — prevent sending response.create while one is active
1847
+ _responseInProgress = false;
1848
+ _onResponseDone = null;
1503
1849
  constructor(options = {}) {
1504
1850
  this._apiKey = options.apiKey ?? process.env["OPENAI_API_KEY"] ?? "";
1505
1851
  this._systemPrompt = options.systemPrompt ?? "";
@@ -1540,6 +1886,7 @@ var OpenAIRealtime = class {
1540
1886
  const ws = this._ws;
1541
1887
  ws.on("open", () => {
1542
1888
  this._sendSessionUpdate();
1889
+ this._log.info("OpenAI Realtime connected");
1543
1890
  if (this._greeting) {
1544
1891
  this._send({ type: "response.create" });
1545
1892
  }
@@ -1559,10 +1906,22 @@ var OpenAIRealtime = class {
1559
1906
  if (!this._ws) {
1560
1907
  reject(err);
1561
1908
  }
1562
- console.error("[OpenAIRealtime] WebSocket error:", err.message);
1909
+ this._log.error({ err }, "OpenAI Realtime WS error");
1563
1910
  });
1564
1911
  });
1565
1912
  }
1913
+ async feedDtmf(digits) {
1914
+ await this._waitForResponseDone();
1915
+ this._send({
1916
+ type: "conversation.item.create",
1917
+ item: {
1918
+ type: "message",
1919
+ role: "user",
1920
+ content: [{ type: "input_text", text: `[DTMF \uC785\uB825: ${digits}]` }]
1921
+ }
1922
+ });
1923
+ this._send({ type: "response.create" });
1924
+ }
1566
1925
  feedAudio(audio) {
1567
1926
  if (this._ws && this._ws.readyState === 1 && !this._closed) {
1568
1927
  this._send({
@@ -1581,7 +1940,12 @@ var OpenAIRealtime = class {
1581
1940
  _sendSessionUpdate() {
1582
1941
  if (!this._ws || this._ws.readyState !== 1) return;
1583
1942
  const toolSchemas = this._tools ? this._tools.toOpenAITools().map((t) => ({ type: "function", ...t.function })) : [];
1584
- toolSchemas.push(HANG_UP_TOOL);
1943
+ if (!this._builtinTools || this._builtinTools.has("hang_up" /* HANG_UP */))
1944
+ toolSchemas.push(HANG_UP_TOOL);
1945
+ if (!this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */))
1946
+ toolSchemas.push(COLLECT_DTMF_TOOL);
1947
+ if (!this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */))
1948
+ toolSchemas.push(SEND_DTMF_TOOL);
1585
1949
  this._send({
1586
1950
  type: "session.update",
1587
1951
  session: {
@@ -1591,7 +1955,7 @@ var OpenAIRealtime = class {
1591
1955
  input_audio_format: "g711_ulaw",
1592
1956
  output_audio_format: "g711_ulaw",
1593
1957
  input_audio_transcription: {
1594
- model: "gpt-4o-mini-transcribe",
1958
+ model: "whisper-1",
1595
1959
  language: this._language
1596
1960
  },
1597
1961
  input_audio_noise_reduction: { type: "far_field" },
@@ -1648,8 +2012,21 @@ var OpenAIRealtime = class {
1648
2012
  }
1649
2013
  break;
1650
2014
  }
2015
+ case "response.created": {
2016
+ this._responseInProgress = true;
2017
+ break;
2018
+ }
2019
+ case "response.done": {
2020
+ this._responseInProgress = false;
2021
+ if (this._onResponseDone) {
2022
+ const cb = this._onResponseDone;
2023
+ this._onResponseDone = null;
2024
+ cb();
2025
+ }
2026
+ break;
2027
+ }
1651
2028
  case "error": {
1652
- console.error("[OpenAIRealtime] API error:", msg["error"]);
2029
+ this._log.error({ apiError: msg["error"] }, "OpenAI error");
1653
2030
  break;
1654
2031
  }
1655
2032
  }
@@ -1699,14 +2076,64 @@ var OpenAIRealtime = class {
1699
2076
  async _handleToolCall(item) {
1700
2077
  const funcName = item["name"];
1701
2078
  const callId = item["call_id"];
2079
+ this._log.info("Tool call: %s", funcName);
1702
2080
  if (funcName === "hang_up") {
1703
2081
  if (this._call) {
1704
- this._call.hangup();
2082
+ await this._call.hangup();
2083
+ }
2084
+ return;
2085
+ }
2086
+ if (funcName === "collect_dtmf") {
2087
+ if (this._call) {
2088
+ let result2;
2089
+ try {
2090
+ const args = JSON.parse(item["arguments"] ?? "{}");
2091
+ result2 = await this._call.collectDtmf({
2092
+ maxDigits: args["max_digits"] ?? 4,
2093
+ finishOnKey: args["finish_on_key"] ?? "#",
2094
+ timeout: args["timeout"] ?? 5
2095
+ });
2096
+ } catch (err) {
2097
+ result2 = `Error: ${err}`;
2098
+ }
2099
+ await this._waitForResponseDone();
2100
+ this._send({
2101
+ type: "conversation.item.create",
2102
+ item: {
2103
+ type: "function_call_output",
2104
+ call_id: callId,
2105
+ output: result2 || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)"
2106
+ }
2107
+ });
2108
+ this._send({ type: "response.create" });
2109
+ }
2110
+ return;
2111
+ }
2112
+ if (funcName === "send_dtmf") {
2113
+ if (this._call) {
2114
+ let result2;
2115
+ try {
2116
+ const args = JSON.parse(item["arguments"] ?? "{}");
2117
+ await this._call.sendDtmfSequence(args["digits"] ?? "");
2118
+ result2 = "sent";
2119
+ } catch (err) {
2120
+ result2 = `Error: ${err}`;
2121
+ }
2122
+ await this._waitForResponseDone();
2123
+ this._send({
2124
+ type: "conversation.item.create",
2125
+ item: {
2126
+ type: "function_call_output",
2127
+ call_id: callId,
2128
+ output: result2
2129
+ }
2130
+ });
2131
+ this._send({ type: "response.create" });
1705
2132
  }
1706
2133
  return;
1707
2134
  }
1708
2135
  if (!this._tools || !this._tools.has(funcName)) {
1709
- console.error(`[OpenAIRealtime] Unknown tool: ${funcName}`);
2136
+ this._log.error("Unknown tool: %s", funcName);
1710
2137
  return;
1711
2138
  }
1712
2139
  let result;
@@ -1714,9 +2141,10 @@ var OpenAIRealtime = class {
1714
2141
  const args = JSON.parse(item["arguments"] ?? "{}");
1715
2142
  result = await this._tools.call(funcName, args);
1716
2143
  } catch (err) {
1717
- console.error(`[OpenAIRealtime] Tool call failed: ${funcName}:`, err);
2144
+ this._log.error({ err }, "Tool call failed: %s", funcName);
1718
2145
  result = `Error: ${err}`;
1719
2146
  }
2147
+ await this._waitForResponseDone();
1720
2148
  this._send({
1721
2149
  type: "conversation.item.create",
1722
2150
  item: {
@@ -1727,6 +2155,12 @@ var OpenAIRealtime = class {
1727
2155
  });
1728
2156
  this._send({ type: "response.create" });
1729
2157
  }
2158
+ _waitForResponseDone() {
2159
+ if (!this._responseInProgress) return Promise.resolve();
2160
+ return new Promise((resolve) => {
2161
+ this._onResponseDone = resolve;
2162
+ });
2163
+ }
1730
2164
  _send(data) {
1731
2165
  if (this._ws && this._ws.readyState === 1 && !this._closed) {
1732
2166
  this._ws.send(JSON.stringify(data));
@@ -1740,6 +2174,33 @@ var HANG_UP_TOOL2 = {
1740
2174
  description: "End the phone call. Use when the conversation is finished or the caller says goodbye.",
1741
2175
  parameters: { type: "object", properties: {} }
1742
2176
  };
2177
+ var COLLECT_DTMF_TOOL2 = {
2178
+ name: "collect_dtmf",
2179
+ 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.",
2180
+ parameters: {
2181
+ type: "object",
2182
+ properties: {
2183
+ max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
2184
+ finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
2185
+ timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
2186
+ },
2187
+ required: ["max_digits"]
2188
+ }
2189
+ };
2190
+ var SEND_DTMF_TOOL2 = {
2191
+ name: "send_dtmf",
2192
+ 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.",
2193
+ parameters: {
2194
+ type: "object",
2195
+ properties: {
2196
+ digits: {
2197
+ type: "string",
2198
+ description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30."
2199
+ }
2200
+ },
2201
+ required: ["digits"]
2202
+ }
2203
+ };
1743
2204
  function resolveRef(ref, defs) {
1744
2205
  const parts = ref.replace(/^#\//, "").split("/");
1745
2206
  let result = defs;
@@ -1802,7 +2263,11 @@ function sanitizeSchemaForGemini(schema, defs, depth = 0) {
1802
2263
  result["properties"] = props;
1803
2264
  }
1804
2265
  if (schema["items"] && typeof schema["items"] === "object" && !Array.isArray(schema["items"])) {
1805
- result["items"] = sanitizeSchemaForGemini(schema["items"], defs, depth + 1);
2266
+ result["items"] = sanitizeSchemaForGemini(
2267
+ schema["items"],
2268
+ defs,
2269
+ depth + 1
2270
+ );
1806
2271
  }
1807
2272
  if (!result["type"] && result["properties"]) result["type"] = "object";
1808
2273
  if (result["type"] === "object" && !result["properties"]) result["properties"] = {};
@@ -1823,6 +2288,9 @@ var GeminiRealtime = class {
1823
2288
  _closed = false;
1824
2289
  _sentAudioChunks = 0;
1825
2290
  _audioRemainder = Buffer.alloc(0);
2291
+ _builtinTools = null;
2292
+ _toolCallInProgress = false;
2293
+ _log = NOOP_LOGGER;
1826
2294
  constructor(options = {}) {
1827
2295
  this._apiKey = options.apiKey ?? process.env["GOOGLE_API_KEY"] ?? "";
1828
2296
  this._systemPrompt = options.systemPrompt ?? "";
@@ -1839,6 +2307,12 @@ var GeminiRealtime = class {
1839
2307
  setRecorder(recorder) {
1840
2308
  this._recorder = recorder;
1841
2309
  }
2310
+ setBuiltinTools(tools) {
2311
+ this._builtinTools = tools;
2312
+ }
2313
+ setLogger(logger) {
2314
+ this._log = logger;
2315
+ }
1842
2316
  async start(callSession, tools) {
1843
2317
  this._call = callSession;
1844
2318
  if (tools) this._tools = tools;
@@ -1875,9 +2349,10 @@ var GeminiRealtime = class {
1875
2349
  callbacks: {
1876
2350
  onmessage: (msg) => this._handleMessage(msg),
1877
2351
  onerror: (err) => {
1878
- console.error("[GeminiRealtime] SDK error:", err);
2352
+ this._log.error({ err }, "Gemini SDK error");
1879
2353
  },
1880
- onclose: () => {
2354
+ onclose: (ev) => {
2355
+ this._log.info({ code: ev?.code ?? "unknown" }, "Gemini connection closed");
1881
2356
  this._closed = true;
1882
2357
  }
1883
2358
  }
@@ -1895,7 +2370,7 @@ var GeminiRealtime = class {
1895
2370
  }
1896
2371
  }
1897
2372
  feedAudio(audio) {
1898
- if (this._session && !this._closed) {
2373
+ if (this._session && !this._closed && !this._toolCallInProgress) {
1899
2374
  const pcm8k = ulawToPcm16(audio);
1900
2375
  if (this._recorder) {
1901
2376
  this._recorder.writeInbound(pcm8k);
@@ -1909,6 +2384,14 @@ var GeminiRealtime = class {
1909
2384
  });
1910
2385
  }
1911
2386
  }
2387
+ async feedDtmf(digits) {
2388
+ if (this._session) {
2389
+ this._session.sendClientContent({
2390
+ turns: [{ role: "user", parts: [{ text: `[DTMF \uC785\uB825: ${digits}]` }] }],
2391
+ turnComplete: true
2392
+ });
2393
+ }
2394
+ }
1912
2395
  async stop() {
1913
2396
  this._closed = true;
1914
2397
  if (this._session) {
@@ -1927,7 +2410,9 @@ var GeminiRealtime = class {
1927
2410
  t.function.parameters ?? { type: "object", properties: {} }
1928
2411
  )
1929
2412
  })) : [];
1930
- toolDefs.push(HANG_UP_TOOL2);
2413
+ if (!this._builtinTools || this._builtinTools.has("hang_up" /* HANG_UP */)) toolDefs.push(HANG_UP_TOOL2);
2414
+ if (!this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */)) toolDefs.push(COLLECT_DTMF_TOOL2);
2415
+ if (!this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */)) toolDefs.push(SEND_DTMF_TOOL2);
1931
2416
  return toolDefs;
1932
2417
  }
1933
2418
  _handleMessage(msg) {
@@ -1947,9 +2432,11 @@ var GeminiRealtime = class {
1947
2432
  }
1948
2433
  }
1949
2434
  if (serverContent.turnComplete) {
2435
+ this._log.debug("Turn complete");
1950
2436
  this._flushAudioRemainder();
1951
2437
  }
1952
2438
  if (serverContent.interrupted) {
2439
+ this._log.info("Barge-in detected");
1953
2440
  if (this._call) {
1954
2441
  this._call.clearAudio();
1955
2442
  }
@@ -1958,16 +2445,22 @@ var GeminiRealtime = class {
1958
2445
  }
1959
2446
  const inputText = serverContent.inputTranscription?.text;
1960
2447
  if (inputText && this._call) {
2448
+ this._log.info("User: %s", inputText);
1961
2449
  this._call._emit("transcript", "user", inputText);
1962
2450
  }
1963
2451
  const outputText = serverContent.outputTranscription?.text;
1964
2452
  if (outputText && this._call) {
2453
+ this._log.info("Assistant: %s", outputText);
1965
2454
  this._call._emit("transcript", "assistant", outputText);
1966
2455
  }
1967
2456
  }
1968
2457
  if (msg.toolCall) {
1969
2458
  this._handleToolCall(msg.toolCall);
1970
2459
  }
2460
+ const toolCancellation = msg["toolCallCancellation"];
2461
+ if (toolCancellation) {
2462
+ this._log.info({ ids: toolCancellation.ids }, "Tool call cancelled");
2463
+ }
1971
2464
  }
1972
2465
  _handleAudioData(b64Data) {
1973
2466
  if (!this._call) return;
@@ -1980,9 +2473,9 @@ var GeminiRealtime = class {
1980
2473
  const combined = Buffer.concat([this._audioRemainder, ulaw]);
1981
2474
  const chunkSize = 160;
1982
2475
  const fullEnd = Math.floor(combined.length / chunkSize) * chunkSize;
1983
- for (let off = 0; off < fullEnd; off += chunkSize) {
1984
- this._call.sendAudio(combined.subarray(off, off + chunkSize));
1985
- this._sentAudioChunks++;
2476
+ if (fullEnd > 0) {
2477
+ this._call.sendAudio(combined.subarray(0, fullEnd));
2478
+ this._sentAudioChunks += fullEnd / chunkSize;
1986
2479
  }
1987
2480
  this._audioRemainder = combined.subarray(fullEnd);
1988
2481
  }
@@ -2000,31 +2493,75 @@ var GeminiRealtime = class {
2000
2493
  async _handleToolCall(toolCall) {
2001
2494
  const functionCalls = toolCall.functionCalls;
2002
2495
  if (!functionCalls) return;
2496
+ this._toolCallInProgress = true;
2003
2497
  const responses = [];
2004
2498
  for (const fc of functionCalls) {
2005
2499
  const name = fc.name ?? "";
2006
2500
  const fcId = fc.id ?? "";
2007
2501
  const args = fc.args ?? {};
2502
+ this._log.info({ tool: name, args }, "Tool call: %s", name);
2008
2503
  if (name === "hang_up") {
2504
+ this._log.info("hang_up: ending call");
2009
2505
  if (this._call) {
2010
- this._call.hangup();
2506
+ await this._call.hangup();
2011
2507
  }
2012
2508
  return;
2013
2509
  }
2510
+ if (name === "collect_dtmf") {
2511
+ if (this._call) {
2512
+ let result;
2513
+ try {
2514
+ this._log.info({ maxDigits: args["max_digits"] ?? 4, timeout: args["timeout"] ?? 5 }, "collect_dtmf: waiting for digits");
2515
+ result = await this._call.collectDtmf({
2516
+ maxDigits: args["max_digits"] ?? 4,
2517
+ finishOnKey: args["finish_on_key"] ?? "#",
2518
+ timeout: args["timeout"] ?? 5
2519
+ });
2520
+ this._log.info("DTMF collected: %s", result || "(empty)");
2521
+ } catch (err) {
2522
+ this._log.error({ err }, "collect_dtmf error");
2523
+ result = `Error: ${err}`;
2524
+ }
2525
+ responses.push({
2526
+ id: fcId,
2527
+ name,
2528
+ response: { result: result || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)" }
2529
+ });
2530
+ }
2531
+ continue;
2532
+ }
2533
+ if (name === "send_dtmf") {
2534
+ if (this._call) {
2535
+ let result;
2536
+ try {
2537
+ this._log.info('send_dtmf: digits="%s"', args["digits"] ?? "");
2538
+ await this._call.sendDtmfSequence(args["digits"] ?? "");
2539
+ result = "sent";
2540
+ this._log.info("send_dtmf: sent");
2541
+ } catch (err) {
2542
+ this._log.error({ err }, "send_dtmf error");
2543
+ result = `Error: ${err}`;
2544
+ }
2545
+ responses.push({ id: fcId, name, response: { result } });
2546
+ }
2547
+ continue;
2548
+ }
2014
2549
  if (!this._tools || !this._tools.has(name)) {
2015
- console.error(`[GeminiRealtime] Unknown tool: ${name}`);
2550
+ this._log.error("Unknown tool: %s", name);
2016
2551
  responses.push({ id: fcId, name, response: { error: `Unknown tool: ${name}` } });
2017
2552
  continue;
2018
2553
  }
2019
2554
  try {
2020
2555
  const result = await this._tools.call(name, args);
2556
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result);
2557
+ this._log.info("Tool result: %s -> %s", name, resultStr.substring(0, 200));
2021
2558
  responses.push({
2022
2559
  id: fcId,
2023
2560
  name,
2024
- response: { result: typeof result === "string" ? result : JSON.stringify(result) }
2561
+ response: { result: resultStr }
2025
2562
  });
2026
2563
  } catch (err) {
2027
- console.error(`[GeminiRealtime] Tool call error for ${name}:`, err);
2564
+ this._log.error({ err }, "Tool call failed: %s", name);
2028
2565
  responses.push({
2029
2566
  id: fcId,
2030
2567
  name,
@@ -2032,15 +2569,41 @@ var GeminiRealtime = class {
2032
2569
  });
2033
2570
  }
2034
2571
  }
2035
- if (this._session) {
2572
+ if (responses.length > 0 && this._session) {
2573
+ this._log.debug("Sending %d tool response(s)", responses.length);
2036
2574
  this._session.sendToolResponse({
2037
2575
  functionResponses: responses
2038
2576
  });
2039
2577
  }
2578
+ this._toolCallInProgress = false;
2040
2579
  }
2041
2580
  };
2042
2581
 
2043
2582
  // src/agent/pipeline/pipeline-session.ts
2583
+ var COLLECT_DTMF_TOOL3 = {
2584
+ function: {
2585
+ description: "\uC0AC\uC6A9\uC790\uB85C\uBD80\uD130 DTMF(\uC804\uD654 \uD0A4\uD328\uB4DC) \uC785\uB825\uC744 \uC218\uC9D1\uD569\uB2C8\uB2E4.",
2586
+ parameters: {
2587
+ properties: {
2588
+ max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
2589
+ finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
2590
+ timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
2591
+ },
2592
+ required: ["max_digits"]
2593
+ }
2594
+ }
2595
+ };
2596
+ var SEND_DTMF_TOOL3 = {
2597
+ function: {
2598
+ 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.",
2599
+ parameters: {
2600
+ properties: {
2601
+ digits: { type: "string", description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30." }
2602
+ },
2603
+ required: ["digits"]
2604
+ }
2605
+ }
2606
+ };
2044
2607
  var PipelineSession = class {
2045
2608
  _stt;
2046
2609
  _llm;
@@ -2059,6 +2622,8 @@ var PipelineSession = class {
2059
2622
  _audioBuffer = [];
2060
2623
  _running = false;
2061
2624
  _speaking = false;
2625
+ _builtinTools = null;
2626
+ _log = NOOP_LOGGER;
2062
2627
  constructor(options) {
2063
2628
  this._stt = options.stt;
2064
2629
  this._llm = options.llm;
@@ -2079,10 +2644,23 @@ var PipelineSession = class {
2079
2644
  setRecorder(recorder) {
2080
2645
  this._recorder = recorder;
2081
2646
  }
2647
+ setBuiltinTools(tools) {
2648
+ this._builtinTools = tools;
2649
+ }
2650
+ setLogger(logger) {
2651
+ this._log = logger;
2652
+ if ("setLogger" in this._stt && typeof this._stt.setLogger === "function") {
2653
+ this._stt.setLogger(logger);
2654
+ }
2655
+ if ("setLogger" in this._tts && typeof this._tts.setLogger === "function") {
2656
+ this._tts.setLogger(logger);
2657
+ }
2658
+ }
2082
2659
  async start(callSession, tools) {
2083
2660
  this._callSession = callSession;
2084
2661
  this._tools = tools ?? null;
2085
2662
  this._running = true;
2663
+ this._log.info("PipelineSession started");
2086
2664
  this._conversation = [];
2087
2665
  if (this._systemPrompt) {
2088
2666
  this._conversation.push({
@@ -2092,11 +2670,11 @@ var PipelineSession = class {
2092
2670
  }
2093
2671
  if (this._greeting) {
2094
2672
  this._generateGreeting().catch((err) => {
2095
- console.error("[PipelineSession] Greeting error:", err);
2673
+ this._log.error({ err }, "Greeting error");
2096
2674
  });
2097
2675
  }
2098
2676
  this._runSttLoop().catch((err) => {
2099
- console.error("[PipelineSession] STT loop error:", err);
2677
+ this._log.error({ err }, "STT loop error");
2100
2678
  });
2101
2679
  }
2102
2680
  feedAudio(audio) {
@@ -2104,8 +2682,16 @@ var PipelineSession = class {
2104
2682
  this._audioBuffer.push(audio);
2105
2683
  }
2106
2684
  }
2685
+ async feedDtmf(digits) {
2686
+ this._conversation.push({
2687
+ role: "user",
2688
+ content: `[DTMF \uC785\uB825: ${digits}]`
2689
+ });
2690
+ await this._respond();
2691
+ }
2107
2692
  async stop() {
2108
2693
  this._running = false;
2694
+ this._log.info("PipelineSession stopped");
2109
2695
  this._audioBuffer = [];
2110
2696
  }
2111
2697
  async _runSttLoop() {
@@ -2119,8 +2705,10 @@ var PipelineSession = class {
2119
2705
  if (this._callSession) {
2120
2706
  this._callSession.clearAudio();
2121
2707
  }
2708
+ this._log.info('Barge-in: "%s"', event.transcript.substring(0, 30));
2122
2709
  }
2123
2710
  if (event.type === "final" && event.transcript.trim()) {
2711
+ this._log.info("STT: %s", event.transcript);
2124
2712
  await this._handleUserSpeech(event.transcript);
2125
2713
  }
2126
2714
  }
@@ -2145,11 +2733,37 @@ var PipelineSession = class {
2145
2733
  this._conversation.push({ role: "user", content: transcript });
2146
2734
  await this._respond();
2147
2735
  }
2736
+ _buildEffectiveTools() {
2737
+ const includeCollectDtmf = !this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */);
2738
+ const includeSendDtmf = !this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */);
2739
+ if (!includeCollectDtmf && !includeSendDtmf) return this._tools ?? void 0;
2740
+ const base = this._tools ? this._tools.fork() : new ToolRegistry();
2741
+ if (includeCollectDtmf) {
2742
+ base.register({
2743
+ name: "collect_dtmf",
2744
+ description: COLLECT_DTMF_TOOL3.function.description,
2745
+ parameters: COLLECT_DTMF_TOOL3.function.parameters.properties,
2746
+ required: COLLECT_DTMF_TOOL3.function.parameters.required,
2747
+ handler: async () => ""
2748
+ });
2749
+ }
2750
+ if (includeSendDtmf) {
2751
+ base.register({
2752
+ name: "send_dtmf",
2753
+ description: SEND_DTMF_TOOL3.function.description,
2754
+ parameters: SEND_DTMF_TOOL3.function.parameters.properties,
2755
+ required: SEND_DTMF_TOOL3.function.parameters.required,
2756
+ handler: async () => ""
2757
+ });
2758
+ }
2759
+ return base;
2760
+ }
2148
2761
  async _respond() {
2149
2762
  let fullResponse = "";
2150
2763
  const textChunks = [];
2764
+ const effectiveTools = this._buildEffectiveTools();
2151
2765
  const llmStream = this._llm.generate(this._conversation, {
2152
- tools: this._tools ?? void 0,
2766
+ tools: effectiveTools,
2153
2767
  temperature: this._temperature,
2154
2768
  maxTokens: this._maxTokens
2155
2769
  });
@@ -2163,15 +2777,44 @@ var PipelineSession = class {
2163
2777
  }
2164
2778
  }
2165
2779
  if (fullResponse.trim()) {
2780
+ this._log.info("Assistant: %s", fullResponse.substring(0, 100));
2166
2781
  this._conversation.push({ role: "assistant", content: fullResponse });
2167
2782
  await this._synthesizeAndSend(fullResponse);
2168
2783
  }
2169
2784
  }
2170
2785
  async _handleToolCall(chunk) {
2171
- if (!chunk.toolCall || !this._tools) return;
2786
+ if (!chunk.toolCall) return;
2172
2787
  const { id, name, arguments: argsStr } = chunk.toolCall;
2173
2788
  try {
2174
2789
  const args = JSON.parse(argsStr);
2790
+ if (name === "collect_dtmf" && this._callSession) {
2791
+ let result2;
2792
+ try {
2793
+ result2 = await this._callSession.collectDtmf({
2794
+ maxDigits: args["max_digits"] ?? 4,
2795
+ finishOnKey: args["finish_on_key"] ?? "#",
2796
+ timeout: args["timeout"] ?? 5
2797
+ });
2798
+ } catch (err) {
2799
+ result2 = `Error: ${err}`;
2800
+ }
2801
+ this._conversation.push({ role: "tool", content: result2 || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)", tool_call_id: id, name });
2802
+ await this._respond();
2803
+ return;
2804
+ }
2805
+ if (name === "send_dtmf" && this._callSession) {
2806
+ let result2;
2807
+ try {
2808
+ await this._callSession.sendDtmfSequence(args["digits"] ?? "");
2809
+ result2 = "sent";
2810
+ } catch (err) {
2811
+ result2 = `Error: ${err}`;
2812
+ }
2813
+ this._conversation.push({ role: "tool", content: result2, tool_call_id: id, name });
2814
+ await this._respond();
2815
+ return;
2816
+ }
2817
+ if (!this._tools) return;
2175
2818
  const result = await this._tools.call(name, args);
2176
2819
  this._conversation.push({
2177
2820
  role: "assistant",
@@ -2184,9 +2827,10 @@ var PipelineSession = class {
2184
2827
  tool_call_id: id,
2185
2828
  name
2186
2829
  });
2830
+ const effectiveTools = this._buildEffectiveTools();
2187
2831
  let followUpText = "";
2188
2832
  const followUpStream = this._llm.generate(this._conversation, {
2189
- tools: this._tools ?? void 0,
2833
+ tools: effectiveTools,
2190
2834
  temperature: this._temperature,
2191
2835
  maxTokens: this._maxTokens
2192
2836
  });
@@ -2201,7 +2845,7 @@ var PipelineSession = class {
2201
2845
  await this._synthesizeAndSend(followUpText);
2202
2846
  }
2203
2847
  } catch (err) {
2204
- console.error(`[PipelineSession] Tool call error for ${name}:`, err);
2848
+ this._log.error({ err }, "Tool call failed: %s", name);
2205
2849
  }
2206
2850
  }
2207
2851
  async _synthesizeAndSend(text) {
@@ -2227,7 +2871,7 @@ var PipelineSession = class {
2227
2871
  }
2228
2872
  }
2229
2873
  } catch (err) {
2230
- console.error("[PipelineSession] TTS error:", err);
2874
+ this._log.error({ err }, "TTS error");
2231
2875
  } finally {
2232
2876
  this._speaking = false;
2233
2877
  }
@@ -2237,6 +2881,10 @@ var PipelineSession = class {
2237
2881
  // src/agent/pipeline/deepgram-stt.ts
2238
2882
  var DeepgramSTT = class {
2239
2883
  _options;
2884
+ _log = NOOP_LOGGER;
2885
+ setLogger(logger) {
2886
+ this._log = logger;
2887
+ }
2240
2888
  constructor(options = {}) {
2241
2889
  this._options = {
2242
2890
  model: "nova-3",
@@ -2306,7 +2954,7 @@ var DeepgramSTT = class {
2306
2954
  }
2307
2955
  });
2308
2956
  ws.on("error", (err) => {
2309
- console.error("[DeepgramSTT] WebSocket error:", err.message);
2957
+ this._log.error({ err }, "Deepgram STT error");
2310
2958
  done = true;
2311
2959
  if (resolveWait) {
2312
2960
  resolveWait();
@@ -2317,6 +2965,7 @@ var DeepgramSTT = class {
2317
2965
  ws.on("open", resolve);
2318
2966
  ws.on("error", reject);
2319
2967
  });
2968
+ this._log.info("Deepgram STT connected");
2320
2969
  const feedPromise = (async () => {
2321
2970
  try {
2322
2971
  for await (const chunk of audioStream) {
@@ -2353,6 +3002,10 @@ var DeepgramSTT = class {
2353
3002
  // src/agent/pipeline/elevenlabs-tts.ts
2354
3003
  var ElevenLabsTTS = class {
2355
3004
  _options;
3005
+ _log = NOOP_LOGGER;
3006
+ setLogger(logger) {
3007
+ this._log = logger;
3008
+ }
2356
3009
  constructor(options = {}) {
2357
3010
  this._options = {
2358
3011
  voiceId: "EXAVITQu4vr4xnSDxMaL",
@@ -2452,7 +3105,7 @@ var ElevenLabsTTS = class {
2452
3105
  }
2453
3106
  });
2454
3107
  ws.on("error", (err) => {
2455
- console.error("[ElevenLabsTTS] WebSocket error:", err.message);
3108
+ this._log.error({ err }, "ElevenLabs TTS error");
2456
3109
  done = true;
2457
3110
  if (resolveWait) {
2458
3111
  resolveWait();
@@ -2463,16 +3116,19 @@ var ElevenLabsTTS = class {
2463
3116
  ws.on("open", resolve);
2464
3117
  ws.on("error", reject);
2465
3118
  });
3119
+ this._log.info("ElevenLabs TTS connected");
2466
3120
  const feedPromise = (async () => {
2467
3121
  try {
2468
3122
  for await (const chunk of textStream) {
2469
3123
  if (done) break;
2470
3124
  if (ws.readyState === 1) {
3125
+ this._log.debug("ElevenLabs sending text: %s", chunk.substring(0, 60));
2471
3126
  ws.send(JSON.stringify({ text: chunk }));
2472
3127
  }
2473
3128
  }
2474
3129
  } finally {
2475
3130
  if (ws.readyState === 1) {
3131
+ this._log.debug("ElevenLabs sending EOS");
2476
3132
  ws.send(JSON.stringify({ text: "" }));
2477
3133
  }
2478
3134
  }
@@ -3012,6 +3668,7 @@ function mcpServerHTTP(options) {
3012
3668
 
3013
3669
  exports.AnthropicLLM = AnthropicLLM;
3014
3670
  exports.AudioRecorder = AudioRecorder;
3671
+ exports.BuiltinTool = BuiltinTool;
3015
3672
  exports.CallSession = CallSession;
3016
3673
  exports.ClawOpsAgent = ClawOpsAgent;
3017
3674
  exports.DECODE_TABLE = DECODE_TABLE;
@@ -3033,6 +3690,8 @@ exports.PipelineSession = PipelineSession;
3033
3690
  exports.TogetherLLM = TogetherLLM;
3034
3691
  exports.ToolRegistry = ToolRegistry;
3035
3692
  exports.XaiLLM = XaiLLM;
3693
+ exports.createAgentLogger = createAgentLogger;
3694
+ exports.createPipelineLogger = createPipelineLogger;
3036
3695
  exports.functionTool = functionTool;
3037
3696
  exports.getTracingConfig = getTracingConfig;
3038
3697
  exports.mcpServerHTTP = mcpServerHTTP;