@teamlearners/clawops 0.16.3 → 0.16.5

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,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk4QQXMZ76_cjs = require('../chunk-4QQXMZ76.cjs');
3
+ var chunkTFYE5DWP_cjs = require('../chunk-TFYE5DWP.cjs');
4
4
  var pino = require('pino');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
@@ -1079,7 +1079,7 @@ var MAX_ERROR_MESSAGE_LENGTH = 200;
1079
1079
  function getSdkInfo() {
1080
1080
  return {
1081
1081
  name: "clawops-node",
1082
- version: chunk4QQXMZ76_cjs.VERSION,
1082
+ version: chunkTFYE5DWP_cjs.VERSION,
1083
1083
  runtime: `node/${process.versions.node}`,
1084
1084
  os: `${process.platform}/${os__default.default.arch()}`
1085
1085
  };
@@ -1762,7 +1762,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1762
1762
  constructor(options) {
1763
1763
  this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
1764
1764
  this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
1765
- this._baseUrl = options.baseUrl ?? chunk4QQXMZ76_cjs.DEFAULT_BASE_URL;
1765
+ this._baseUrl = options.baseUrl ?? chunkTFYE5DWP_cjs.DEFAULT_BASE_URL;
1766
1766
  this._fromNumber = options.from;
1767
1767
  this._session = options.session;
1768
1768
  this._recording = options.recording ?? false;
@@ -1784,7 +1784,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1784
1784
  }
1785
1785
  static _validateGain(name, gain) {
1786
1786
  if (typeof gain !== "number" || !Number.isFinite(gain) || gain < 0) {
1787
- throw new chunk4QQXMZ76_cjs.AgentError(`${name}=${gain} must be a finite number >= 0`);
1787
+ throw new chunkTFYE5DWP_cjs.AgentError(`${name}=${gain} must be a finite number >= 0`);
1788
1788
  }
1789
1789
  return gain;
1790
1790
  }
@@ -1798,7 +1798,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1798
1798
  tool(nameOrTool, description, parameters, handler) {
1799
1799
  if (typeof nameOrTool === "string") {
1800
1800
  if (!description || !parameters || !handler) {
1801
- throw new chunk4QQXMZ76_cjs.AgentError(
1801
+ throw new chunkTFYE5DWP_cjs.AgentError(
1802
1802
  "tool(name, description, parameters, handler) requires all arguments."
1803
1803
  );
1804
1804
  }
@@ -1834,10 +1834,10 @@ var ClawOpsAgent = class _ClawOpsAgent {
1834
1834
  async connect() {
1835
1835
  if (this._controlWs) return;
1836
1836
  if (!this._apiKey) {
1837
- throw new chunk4QQXMZ76_cjs.AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1837
+ throw new chunkTFYE5DWP_cjs.AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
1838
1838
  }
1839
1839
  if (!this._accountId) {
1840
- throw new chunk4QQXMZ76_cjs.AgentError(
1840
+ throw new chunkTFYE5DWP_cjs.AgentError(
1841
1841
  "Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option."
1842
1842
  );
1843
1843
  }
@@ -1861,7 +1861,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1861
1861
  } catch {
1862
1862
  }
1863
1863
  } catch (err) {
1864
- throw new chunk4QQXMZ76_cjs.AgentConnectionError(
1864
+ throw new chunkTFYE5DWP_cjs.AgentConnectionError(
1865
1865
  `Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
1866
1866
  );
1867
1867
  }
@@ -1912,7 +1912,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1912
1912
  });
1913
1913
  if (resp.status !== 201) {
1914
1914
  const error = await resp.json();
1915
- throw new chunk4QQXMZ76_cjs.AgentError(`\uBC1C\uC2E0 \uC2E4\uD328 (${resp.status}): ${error["error"] ?? ""}`);
1915
+ throw new chunkTFYE5DWP_cjs.AgentError(`\uBC1C\uC2E0 \uC2E4\uD328 (${resp.status}): ${error["error"] ?? ""}`);
1916
1916
  }
1917
1917
  const data = await resp.json();
1918
1918
  const callSession = new CallSession({
@@ -1955,9 +1955,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1955
1955
  this._controlWs.send({ event: "call.accept", callId });
1956
1956
  }
1957
1957
  if (mediaUrl) {
1958
- this._startCallSession(session, mediaUrl).catch((err) => {
1959
- this._log.error({ err }, "Call session error: %s", callId);
1960
- });
1958
+ this._safeStartCallSession(session, mediaUrl, callId);
1961
1959
  }
1962
1960
  }
1963
1961
  _handleEnded(event) {
@@ -1991,9 +1989,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
1991
1989
  }
1992
1990
  if (mediaUrl) {
1993
1991
  this._log.info("Outbound call answered: %s -> %s (%s)", this._fromNumber, session.toNumber, callId);
1994
- this._startCallSession(session, mediaUrl).catch((err) => {
1995
- this._log.error({ err }, "Call session error: %s", callId);
1996
- });
1992
+ this._safeStartCallSession(session, mediaUrl, callId);
1997
1993
  }
1998
1994
  }
1999
1995
  _handleRinging(event) {
@@ -2037,6 +2033,32 @@ var ClawOpsAgent = class _ClawOpsAgent {
2037
2033
  }
2038
2034
  }, this._passiveDtmfDebounceMs);
2039
2035
  }
2036
+ /**
2037
+ * _startCallSession 의 예외를 잡아 control WS 로 call.session_failed 전송한다.
2038
+ *
2039
+ * OpenAI/Gemini API 키 누락 등 session.start() 단계 실패는 media WS connect 에
2040
+ * 도달하지 못해 call-engine 이 30 초간 무음 통화를 유지하게 만든다. 서버에 즉시
2041
+ * 알려서 fail-fast 시키고 _activeSessions 에서 정리한다.
2042
+ */
2043
+ _safeStartCallSession(session, mediaWsUrl, callId) {
2044
+ this._startCallSession(session, mediaWsUrl).catch((err) => {
2045
+ const error = err;
2046
+ this._log.error({ err }, "Session start failed for %s", callId);
2047
+ if (this._controlWs) {
2048
+ try {
2049
+ this._controlWs.send({
2050
+ event: "call.session_failed",
2051
+ callId,
2052
+ reason: error?.name ?? "Error",
2053
+ message: error?.message ?? String(err)
2054
+ });
2055
+ } catch {
2056
+ }
2057
+ }
2058
+ this._activeSessions.delete(callId);
2059
+ this._callSessions.delete(callId);
2060
+ });
2061
+ }
2040
2062
  async _startCallSession(session, mediaWsUrl) {
2041
2063
  await withSpan(
2042
2064
  "clawops.call_session",
@@ -2660,6 +2682,11 @@ var OpenAIRealtime = class {
2660
2682
  _holdAudioChunks = null;
2661
2683
  constructor(options = {}) {
2662
2684
  this._apiKey = options.apiKey ?? process.env["OPENAI_API_KEY"] ?? "";
2685
+ if (!this._apiKey) {
2686
+ throw new Error(
2687
+ "OpenAI API key is required. Set OPENAI_API_KEY env var or pass apiKey option."
2688
+ );
2689
+ }
2663
2690
  this._systemPrompt = options.systemPrompt ?? "";
2664
2691
  this._model = options.model ?? "gpt-realtime-2";
2665
2692
  this._voice = options.voice ?? "marin";
@@ -2685,15 +2712,11 @@ var OpenAIRealtime = class {
2685
2712
  this._closed = false;
2686
2713
  this._playback = null;
2687
2714
  this._latestMediaTs = 0;
2688
- if (!this._apiKey) {
2689
- throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey option.");
2690
- }
2691
2715
  const { WebSocket } = await import('ws');
2692
2716
  const url = `${OPENAI_REALTIME_URL}${this._model}`;
2693
2717
  this._ws = new WebSocket(url, {
2694
2718
  headers: {
2695
- Authorization: `Bearer ${this._apiKey}`,
2696
- "OpenAI-Beta": "realtime=v1"
2719
+ Authorization: `Bearer ${this._apiKey}`
2697
2720
  }
2698
2721
  });
2699
2722
  return new Promise((resolve, reject) => {
@@ -2763,17 +2786,24 @@ var OpenAIRealtime = class {
2763
2786
  this._send({
2764
2787
  type: "session.update",
2765
2788
  session: {
2766
- modalities: ["text", "audio"],
2767
- voice: this._voice,
2789
+ type: "realtime",
2790
+ output_modalities: ["audio"],
2768
2791
  instructions: this._systemPrompt,
2769
- input_audio_format: "g711_ulaw",
2770
- output_audio_format: "g711_ulaw",
2771
- input_audio_transcription: {
2772
- model: "whisper-1",
2773
- language: this._language
2792
+ audio: {
2793
+ input: {
2794
+ format: { type: "audio/pcmu" },
2795
+ noise_reduction: { type: "far_field" },
2796
+ transcription: {
2797
+ model: "gpt-realtime-whisper",
2798
+ language: this._language
2799
+ },
2800
+ turn_detection: this._turnDetection
2801
+ },
2802
+ output: {
2803
+ format: { type: "audio/pcmu" },
2804
+ voice: this._voice
2805
+ }
2774
2806
  },
2775
- input_audio_noise_reduction: { type: "far_field" },
2776
- turn_detection: this._turnDetection,
2777
2807
  tools: toolSchemas
2778
2808
  }
2779
2809
  });
@@ -2781,11 +2811,11 @@ var OpenAIRealtime = class {
2781
2811
  _handleMessage(msg) {
2782
2812
  const type = msg["type"];
2783
2813
  switch (type) {
2784
- case "response.audio.delta": {
2814
+ case "response.output_audio.delta": {
2785
2815
  this._handleAudioDelta(msg);
2786
2816
  break;
2787
2817
  }
2788
- case "response.audio.done": {
2818
+ case "response.output_audio.done": {
2789
2819
  if (this._playback) {
2790
2820
  this._playback.generating = false;
2791
2821
  if (this._playback.audioRemainder.length > 0) {
@@ -2819,7 +2849,7 @@ var OpenAIRealtime = class {
2819
2849
  }
2820
2850
  break;
2821
2851
  }
2822
- case "response.audio_transcript.done": {
2852
+ case "response.output_audio_transcript.done": {
2823
2853
  if (this._call) {
2824
2854
  this._call._emit("transcript", "assistant", msg["transcript"] ?? "");
2825
2855
  }