@teamlearners/clawops 0.16.0 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.cjs +119 -55
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +29 -7
- package/dist/agent/index.d.ts +29 -7
- package/dist/agent/index.js +112 -48
- package/dist/agent/index.js.map +1 -1
- package/dist/{chunk-N5EKKQVO.js → chunk-B3SD2KKR.js} +3 -3
- package/dist/{chunk-N5EKKQVO.js.map → chunk-B3SD2KKR.js.map} +1 -1
- package/dist/{chunk-D25IRY5V.cjs → chunk-GTXJFMHN.cjs} +3 -3
- package/dist/{chunk-D25IRY5V.cjs.map → chunk-GTXJFMHN.cjs.map} +1 -1
- package/dist/index.cjs +35 -35
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/agent/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkGTXJFMHN_cjs = require('../chunk-GTXJFMHN.cjs');
|
|
4
4
|
var pino = require('pino');
|
|
5
5
|
var fs = require('fs');
|
|
6
6
|
var path = require('path');
|
|
@@ -331,6 +331,27 @@ function ulawToPcm16(ulaw) {
|
|
|
331
331
|
}
|
|
332
332
|
return out;
|
|
333
333
|
}
|
|
334
|
+
function applyPcm16Gain(pcm, gain) {
|
|
335
|
+
if (!pcm.length) return Buffer.alloc(0);
|
|
336
|
+
if (gain === 1) return pcm;
|
|
337
|
+
if (!Number.isFinite(gain) || gain < 0) {
|
|
338
|
+
throw new Error("gain must be a finite number >= 0");
|
|
339
|
+
}
|
|
340
|
+
const nSamples = pcm.length >> 1;
|
|
341
|
+
const out = Buffer.alloc(nSamples * 2);
|
|
342
|
+
for (let i = 0; i < nSamples; i++) {
|
|
343
|
+
let value = Math.round(pcm.readInt16LE(i * 2) * gain);
|
|
344
|
+
if (value > 32767) value = 32767;
|
|
345
|
+
else if (value < -32768) value = -32768;
|
|
346
|
+
out.writeInt16LE(value, i * 2);
|
|
347
|
+
}
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
function applyUlawGain(ulaw, gain) {
|
|
351
|
+
if (!ulaw.length) return Buffer.alloc(0);
|
|
352
|
+
if (gain === 1) return ulaw;
|
|
353
|
+
return pcm16ToUlaw(applyPcm16Gain(ulawToPcm16(ulaw), gain));
|
|
354
|
+
}
|
|
334
355
|
function resamplePcm16(pcm, fromRate, toRate) {
|
|
335
356
|
if (fromRate === toRate || !pcm.length) return pcm;
|
|
336
357
|
const nSamples = pcm.length >> 1;
|
|
@@ -919,8 +940,9 @@ var AudioRecorder = class {
|
|
|
919
940
|
_inWritten = 0;
|
|
920
941
|
_outWritten = 0;
|
|
921
942
|
_mixWritten = 0;
|
|
922
|
-
_startTime = 0;
|
|
923
943
|
_started = false;
|
|
944
|
+
_baseTs = null;
|
|
945
|
+
_outCursor = 0;
|
|
924
946
|
_log = NOOP_LOGGER;
|
|
925
947
|
setLogger(logger) {
|
|
926
948
|
this._log = logger;
|
|
@@ -937,17 +959,18 @@ var AudioRecorder = class {
|
|
|
937
959
|
fs__namespace.writeSync(this._fdIn, header);
|
|
938
960
|
fs__namespace.writeSync(this._fdOut, header);
|
|
939
961
|
fs__namespace.writeSync(this._fdMix, header);
|
|
940
|
-
this._startTime = performance.now();
|
|
941
962
|
this._started = true;
|
|
942
963
|
this._log.info("Recording started: %s", this._dir);
|
|
943
964
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
965
|
+
_timestampToBytes(mediaTsMs) {
|
|
966
|
+
if (this._baseTs === null) {
|
|
967
|
+
this._baseTs = mediaTsMs;
|
|
968
|
+
}
|
|
969
|
+
const target = Math.floor((mediaTsMs - this._baseTs) * BYTES_PER_SECOND / 1e3);
|
|
970
|
+
return Math.max(0, target - target % 2);
|
|
947
971
|
}
|
|
948
|
-
_padSilence(fd, written) {
|
|
949
|
-
|
|
950
|
-
let gap = expected - written;
|
|
972
|
+
_padSilence(fd, written, target) {
|
|
973
|
+
let gap = target - written;
|
|
951
974
|
if (gap <= 0) return 0;
|
|
952
975
|
gap = gap - gap % 2;
|
|
953
976
|
if (gap > 0) {
|
|
@@ -957,9 +980,12 @@ var AudioRecorder = class {
|
|
|
957
980
|
}
|
|
958
981
|
_writeToMix(data, trackPos) {
|
|
959
982
|
if (this._fdMix === null) return;
|
|
983
|
+
data = data.subarray(0, data.length - data.length % 2);
|
|
984
|
+
if (data.length === 0) return;
|
|
960
985
|
const filePos = 44 + trackPos;
|
|
961
986
|
if (trackPos < this._mixWritten) {
|
|
962
|
-
|
|
987
|
+
let overlap = Math.min(data.length, this._mixWritten - trackPos);
|
|
988
|
+
overlap = overlap - overlap % 2;
|
|
963
989
|
const existing = Buffer.alloc(overlap);
|
|
964
990
|
fs__namespace.readSync(this._fdMix, existing, 0, overlap, filePos);
|
|
965
991
|
const mixed = mixSamples(existing, data.subarray(0, overlap));
|
|
@@ -982,10 +1008,13 @@ var AudioRecorder = class {
|
|
|
982
1008
|
this._mixWritten += data.length;
|
|
983
1009
|
}
|
|
984
1010
|
}
|
|
985
|
-
writeInbound(pcm16_8k) {
|
|
1011
|
+
writeInbound(pcm16_8k, mediaTsMs = 0) {
|
|
986
1012
|
if (!this._started || this._fdIn === null) return;
|
|
987
1013
|
try {
|
|
988
|
-
|
|
1014
|
+
pcm16_8k = pcm16_8k.subarray(0, pcm16_8k.length - pcm16_8k.length % 2);
|
|
1015
|
+
if (pcm16_8k.length === 0) return;
|
|
1016
|
+
const target = this._timestampToBytes(mediaTsMs);
|
|
1017
|
+
const gap = this._padSilence(this._fdIn, this._inWritten, target);
|
|
989
1018
|
this._inWritten += gap;
|
|
990
1019
|
const posBefore = this._inWritten;
|
|
991
1020
|
fs__namespace.writeSync(this._fdIn, pcm16_8k);
|
|
@@ -995,14 +1024,21 @@ var AudioRecorder = class {
|
|
|
995
1024
|
this._log.error({ err }, "Recording write error (inbound)");
|
|
996
1025
|
}
|
|
997
1026
|
}
|
|
998
|
-
writeOutbound(pcm16_8k) {
|
|
1027
|
+
writeOutbound(pcm16_8k, mediaTsMs) {
|
|
999
1028
|
if (!this._started || this._fdOut === null) return;
|
|
1000
1029
|
try {
|
|
1001
|
-
|
|
1030
|
+
pcm16_8k = pcm16_8k.subarray(0, pcm16_8k.length - pcm16_8k.length % 2);
|
|
1031
|
+
if (pcm16_8k.length === 0) return;
|
|
1032
|
+
let target = this._outCursor;
|
|
1033
|
+
if (mediaTsMs !== void 0) {
|
|
1034
|
+
target = Math.max(target, this._timestampToBytes(mediaTsMs));
|
|
1035
|
+
}
|
|
1036
|
+
const gap = this._padSilence(this._fdOut, this._outWritten, target);
|
|
1002
1037
|
this._outWritten += gap;
|
|
1003
1038
|
const posBefore = this._outWritten;
|
|
1004
1039
|
fs__namespace.writeSync(this._fdOut, pcm16_8k);
|
|
1005
1040
|
this._outWritten += pcm16_8k.length;
|
|
1041
|
+
this._outCursor = this._outWritten;
|
|
1006
1042
|
this._writeToMix(pcm16_8k, posBefore);
|
|
1007
1043
|
} catch (err) {
|
|
1008
1044
|
this._log.error({ err }, "Recording write error (outbound)");
|
|
@@ -1043,7 +1079,7 @@ var MAX_ERROR_MESSAGE_LENGTH = 200;
|
|
|
1043
1079
|
function getSdkInfo() {
|
|
1044
1080
|
return {
|
|
1045
1081
|
name: "clawops-node",
|
|
1046
|
-
version:
|
|
1082
|
+
version: chunkGTXJFMHN_cjs.VERSION,
|
|
1047
1083
|
runtime: `node/${process.versions.node}`,
|
|
1048
1084
|
os: `${process.platform}/${os__default.default.arch()}`
|
|
1049
1085
|
};
|
|
@@ -1698,7 +1734,7 @@ var ATTR_CALL_DIRECTION = "clawops.call.direction";
|
|
|
1698
1734
|
var ATTR_AGENT_ID = "clawops.agent.id";
|
|
1699
1735
|
|
|
1700
1736
|
// src/agent/agent.ts
|
|
1701
|
-
var ClawOpsAgent = class {
|
|
1737
|
+
var ClawOpsAgent = class _ClawOpsAgent {
|
|
1702
1738
|
_apiKey;
|
|
1703
1739
|
_accountId;
|
|
1704
1740
|
_baseUrl;
|
|
@@ -1721,10 +1757,12 @@ var ClawOpsAgent = class {
|
|
|
1721
1757
|
_pipelineLog;
|
|
1722
1758
|
_isPipelineSession = false;
|
|
1723
1759
|
_holdAudioChunks = null;
|
|
1760
|
+
_rxGain;
|
|
1761
|
+
_txGain;
|
|
1724
1762
|
constructor(options) {
|
|
1725
1763
|
this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
|
|
1726
1764
|
this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
|
|
1727
|
-
this._baseUrl = options.baseUrl ??
|
|
1765
|
+
this._baseUrl = options.baseUrl ?? chunkGTXJFMHN_cjs.DEFAULT_BASE_URL;
|
|
1728
1766
|
this._fromNumber = options.from;
|
|
1729
1767
|
this._session = options.session;
|
|
1730
1768
|
this._recording = options.recording ?? false;
|
|
@@ -1732,6 +1770,8 @@ var ClawOpsAgent = class {
|
|
|
1732
1770
|
this._mcpServers = options.mcpServers ?? [];
|
|
1733
1771
|
this._builtinTools = resolveBuiltinTools(options.builtinTools ?? "all" /* ALL */);
|
|
1734
1772
|
this._passiveDtmfDebounceMs = options.passiveDtmfDebounceMs ?? 500;
|
|
1773
|
+
this._rxGain = _ClawOpsAgent._validateGain("rxGain", options.rxGain ?? 1);
|
|
1774
|
+
this._txGain = _ClawOpsAgent._validateGain("txGain", options.txGain ?? 1);
|
|
1735
1775
|
if (options.tracing) {
|
|
1736
1776
|
setTracingConfig(options.tracing);
|
|
1737
1777
|
}
|
|
@@ -1742,6 +1782,12 @@ var ClawOpsAgent = class {
|
|
|
1742
1782
|
this._holdAudioChunks = loadHoldAudio(options.toolConfig.holdAudio);
|
|
1743
1783
|
}
|
|
1744
1784
|
}
|
|
1785
|
+
static _validateGain(name, gain) {
|
|
1786
|
+
if (typeof gain !== "number" || !Number.isFinite(gain) || gain < 0) {
|
|
1787
|
+
throw new chunkGTXJFMHN_cjs.AgentError(`${name}=${gain} must be a finite number >= 0`);
|
|
1788
|
+
}
|
|
1789
|
+
return gain;
|
|
1790
|
+
}
|
|
1745
1791
|
/**
|
|
1746
1792
|
* Register a function tool.
|
|
1747
1793
|
*
|
|
@@ -1752,7 +1798,7 @@ var ClawOpsAgent = class {
|
|
|
1752
1798
|
tool(nameOrTool, description, parameters, handler) {
|
|
1753
1799
|
if (typeof nameOrTool === "string") {
|
|
1754
1800
|
if (!description || !parameters || !handler) {
|
|
1755
|
-
throw new
|
|
1801
|
+
throw new chunkGTXJFMHN_cjs.AgentError(
|
|
1756
1802
|
"tool(name, description, parameters, handler) requires all arguments."
|
|
1757
1803
|
);
|
|
1758
1804
|
}
|
|
@@ -1788,10 +1834,10 @@ var ClawOpsAgent = class {
|
|
|
1788
1834
|
async connect() {
|
|
1789
1835
|
if (this._controlWs) return;
|
|
1790
1836
|
if (!this._apiKey) {
|
|
1791
|
-
throw new
|
|
1837
|
+
throw new chunkGTXJFMHN_cjs.AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
|
|
1792
1838
|
}
|
|
1793
1839
|
if (!this._accountId) {
|
|
1794
|
-
throw new
|
|
1840
|
+
throw new chunkGTXJFMHN_cjs.AgentError(
|
|
1795
1841
|
"Account ID is required. Set CLAWOPS_ACCOUNT_ID or pass accountId option."
|
|
1796
1842
|
);
|
|
1797
1843
|
}
|
|
@@ -1815,7 +1861,7 @@ var ClawOpsAgent = class {
|
|
|
1815
1861
|
} catch {
|
|
1816
1862
|
}
|
|
1817
1863
|
} catch (err) {
|
|
1818
|
-
throw new
|
|
1864
|
+
throw new chunkGTXJFMHN_cjs.AgentConnectionError(
|
|
1819
1865
|
`Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
|
|
1820
1866
|
);
|
|
1821
1867
|
}
|
|
@@ -1866,7 +1912,7 @@ var ClawOpsAgent = class {
|
|
|
1866
1912
|
});
|
|
1867
1913
|
if (resp.status !== 201) {
|
|
1868
1914
|
const error = await resp.json();
|
|
1869
|
-
throw new
|
|
1915
|
+
throw new chunkGTXJFMHN_cjs.AgentError(`\uBC1C\uC2E0 \uC2E4\uD328 (${resp.status}): ${error["error"] ?? ""}`);
|
|
1870
1916
|
}
|
|
1871
1917
|
const data = await resp.json();
|
|
1872
1918
|
const callSession = new CallSession({
|
|
@@ -1909,9 +1955,7 @@ var ClawOpsAgent = class {
|
|
|
1909
1955
|
this._controlWs.send({ event: "call.accept", callId });
|
|
1910
1956
|
}
|
|
1911
1957
|
if (mediaUrl) {
|
|
1912
|
-
this.
|
|
1913
|
-
this._log.error({ err }, "Call session error: %s", callId);
|
|
1914
|
-
});
|
|
1958
|
+
this._safeStartCallSession(session, mediaUrl, callId);
|
|
1915
1959
|
}
|
|
1916
1960
|
}
|
|
1917
1961
|
_handleEnded(event) {
|
|
@@ -1945,9 +1989,7 @@ var ClawOpsAgent = class {
|
|
|
1945
1989
|
}
|
|
1946
1990
|
if (mediaUrl) {
|
|
1947
1991
|
this._log.info("Outbound call answered: %s -> %s (%s)", this._fromNumber, session.toNumber, callId);
|
|
1948
|
-
this.
|
|
1949
|
-
this._log.error({ err }, "Call session error: %s", callId);
|
|
1950
|
-
});
|
|
1992
|
+
this._safeStartCallSession(session, mediaUrl, callId);
|
|
1951
1993
|
}
|
|
1952
1994
|
}
|
|
1953
1995
|
_handleRinging(event) {
|
|
@@ -1991,6 +2033,32 @@ var ClawOpsAgent = class {
|
|
|
1991
2033
|
}
|
|
1992
2034
|
}, this._passiveDtmfDebounceMs);
|
|
1993
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
|
+
}
|
|
1994
2062
|
async _startCallSession(session, mediaWsUrl) {
|
|
1995
2063
|
await withSpan(
|
|
1996
2064
|
"clawops.call_session",
|
|
@@ -2024,9 +2092,14 @@ var ClawOpsAgent = class {
|
|
|
2024
2092
|
}
|
|
2025
2093
|
const mediaWs = new MediaWebSocket();
|
|
2026
2094
|
mediaWs.setLogger(this._log);
|
|
2095
|
+
let latestMediaTs = 0;
|
|
2027
2096
|
session._bindTransport(
|
|
2028
2097
|
(audio) => {
|
|
2029
|
-
|
|
2098
|
+
const gained = applyUlawGain(audio, this._txGain);
|
|
2099
|
+
if (recorder) {
|
|
2100
|
+
recorder.writeOutbound(ulawToPcm16(gained), latestMediaTs);
|
|
2101
|
+
}
|
|
2102
|
+
mediaWs.sendAudio(gained.toString("base64"));
|
|
2030
2103
|
session.recordFirstResponse();
|
|
2031
2104
|
},
|
|
2032
2105
|
() => {
|
|
@@ -2063,12 +2136,14 @@ var ClawOpsAgent = class {
|
|
|
2063
2136
|
sessionHandler.setHoldAudio(this._holdAudioChunks);
|
|
2064
2137
|
}
|
|
2065
2138
|
this._callSessions.set(session.callId, sessionHandler);
|
|
2066
|
-
mediaWs.onAudio((ulawAudio,
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
}
|
|
2139
|
+
mediaWs.onAudio((ulawAudio, timestamp) => {
|
|
2140
|
+
latestMediaTs = timestamp;
|
|
2141
|
+
const gained = applyUlawGain(ulawAudio, this._rxGain);
|
|
2070
2142
|
if (recorder) {
|
|
2071
|
-
recorder.writeInbound(ulawToPcm16(
|
|
2143
|
+
recorder.writeInbound(ulawToPcm16(gained), timestamp);
|
|
2144
|
+
}
|
|
2145
|
+
if (sessionHandler) {
|
|
2146
|
+
sessionHandler.feedAudio(gained, timestamp);
|
|
2072
2147
|
}
|
|
2073
2148
|
});
|
|
2074
2149
|
mediaWs.onDtmf((digit) => {
|
|
@@ -2539,10 +2614,6 @@ var PipelineSession = class {
|
|
|
2539
2614
|
sampleRate: this._sampleRate
|
|
2540
2615
|
})) {
|
|
2541
2616
|
if (!this._running || !this._speaking) break;
|
|
2542
|
-
if (this._recorder) {
|
|
2543
|
-
const pcm8k2 = this._sampleRate !== 8e3 ? resamplePcm16(audioChunk, this._sampleRate, 8e3) : audioChunk;
|
|
2544
|
-
this._recorder.writeOutbound(pcm8k2);
|
|
2545
|
-
}
|
|
2546
2617
|
const pcm8k = resamplePcm16(audioChunk, this._sampleRate, 8e3);
|
|
2547
2618
|
const ulaw = pcm16ToUlaw(pcm8k);
|
|
2548
2619
|
for (let off = 0; off < ulaw.length; off += 160) {
|
|
@@ -2611,8 +2682,13 @@ var OpenAIRealtime = class {
|
|
|
2611
2682
|
_holdAudioChunks = null;
|
|
2612
2683
|
constructor(options = {}) {
|
|
2613
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
|
+
}
|
|
2614
2690
|
this._systemPrompt = options.systemPrompt ?? "";
|
|
2615
|
-
this._model = options.model ?? "gpt-realtime-
|
|
2691
|
+
this._model = options.model ?? "gpt-realtime-2";
|
|
2616
2692
|
this._voice = options.voice ?? "marin";
|
|
2617
2693
|
this._language = options.language ?? "ko";
|
|
2618
2694
|
this._turnDetection = options.turnDetection !== void 0 ? options.turnDetection : { type: "semantic_vad", eagerness: "medium", interrupt_response: true };
|
|
@@ -2636,9 +2712,6 @@ var OpenAIRealtime = class {
|
|
|
2636
2712
|
this._closed = false;
|
|
2637
2713
|
this._playback = null;
|
|
2638
2714
|
this._latestMediaTs = 0;
|
|
2639
|
-
if (!this._apiKey) {
|
|
2640
|
-
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey option.");
|
|
2641
|
-
}
|
|
2642
2715
|
const { WebSocket } = await import('ws');
|
|
2643
2716
|
const url = `${OPENAI_REALTIME_URL}${this._model}`;
|
|
2644
2717
|
this._ws = new WebSocket(url, {
|
|
@@ -2687,8 +2760,8 @@ var OpenAIRealtime = class {
|
|
|
2687
2760
|
});
|
|
2688
2761
|
this._send({ type: "response.create" });
|
|
2689
2762
|
}
|
|
2690
|
-
feedAudio(audio) {
|
|
2691
|
-
this._latestMediaTs =
|
|
2763
|
+
feedAudio(audio, timestamp) {
|
|
2764
|
+
this._latestMediaTs = timestamp ?? this._latestMediaTs;
|
|
2692
2765
|
if (this._ws && this._ws.readyState === 1 && !this._closed) {
|
|
2693
2766
|
this._send({
|
|
2694
2767
|
type: "input_audio_buffer.append",
|
|
@@ -2799,7 +2872,7 @@ var OpenAIRealtime = class {
|
|
|
2799
2872
|
if (this._playback === null) {
|
|
2800
2873
|
this._playback = {
|
|
2801
2874
|
itemId: msg["item_id"] || "",
|
|
2802
|
-
startTs: this._latestMediaTs
|
|
2875
|
+
startTs: this._latestMediaTs,
|
|
2803
2876
|
sentChunks: 0,
|
|
2804
2877
|
generating: true,
|
|
2805
2878
|
audioRemainder: Buffer.alloc(0)
|
|
@@ -2809,9 +2882,6 @@ var OpenAIRealtime = class {
|
|
|
2809
2882
|
}
|
|
2810
2883
|
const pb = this._playback;
|
|
2811
2884
|
const ulaw = Buffer.from(msg["delta"], "base64");
|
|
2812
|
-
if (this._recorder) {
|
|
2813
|
-
this._recorder.writeOutbound(ulawToPcm16(ulaw));
|
|
2814
|
-
}
|
|
2815
2885
|
const combined = Buffer.concat([pb.audioRemainder, ulaw]);
|
|
2816
2886
|
const chunkSize = 160;
|
|
2817
2887
|
const fullEnd = Math.floor(combined.length / chunkSize) * chunkSize;
|
|
@@ -2835,7 +2905,7 @@ var OpenAIRealtime = class {
|
|
|
2835
2905
|
if (pb === null) {
|
|
2836
2906
|
return;
|
|
2837
2907
|
}
|
|
2838
|
-
const playedMs = Math.max(0,
|
|
2908
|
+
const playedMs = Math.max(0, this._latestMediaTs - pb.startTs);
|
|
2839
2909
|
this._log.info(
|
|
2840
2910
|
"[Interrupt] item=%s played=%dms total=%dms",
|
|
2841
2911
|
pb.itemId,
|
|
@@ -3127,9 +3197,6 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3127
3197
|
feedAudio(audio) {
|
|
3128
3198
|
if (this._session && !this._closed) {
|
|
3129
3199
|
const pcm8k = ulawToPcm16(audio);
|
|
3130
|
-
if (this._recorder) {
|
|
3131
|
-
this._recorder.writeInbound(pcm8k);
|
|
3132
|
-
}
|
|
3133
3200
|
const pcm16k = resamplePcm16(pcm8k, 8e3, 16e3);
|
|
3134
3201
|
this._session.sendRealtimeInput({
|
|
3135
3202
|
audio: {
|
|
@@ -3227,9 +3294,6 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3227
3294
|
_handleAudioData(b64Data) {
|
|
3228
3295
|
if (!this._call) return;
|
|
3229
3296
|
const pcm24k = Buffer.from(b64Data, "base64");
|
|
3230
|
-
if (this._recorder) {
|
|
3231
|
-
this._recorder.writeOutbound(resamplePcm16(pcm24k, 24e3, 8e3));
|
|
3232
|
-
}
|
|
3233
3297
|
const pcm8k = resamplePcm16(pcm24k, 24e3, 8e3);
|
|
3234
3298
|
const ulaw = pcm16ToUlaw(pcm8k);
|
|
3235
3299
|
const combined = Buffer.concat([this._audioRemainder, ulaw]);
|