@teamlearners/clawops 0.5.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.cjs +1103 -919
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +172 -74
- package/dist/agent/index.d.ts +172 -74
- package/dist/agent/index.js +1093 -914
- package/dist/agent/index.js.map +1 -1
- package/dist/{chunk-7S2OEBS6.js → chunk-2B74QZZJ.js} +6 -3
- package/dist/chunk-2B74QZZJ.js.map +1 -0
- package/dist/{chunk-6IQN5RQD.cjs → chunk-R742GPVL.cjs} +6 -2
- package/dist/chunk-R742GPVL.cjs.map +1 -0
- package/dist/index.cjs +38 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-6IQN5RQD.cjs.map +0 -1
- package/dist/chunk-7S2OEBS6.js.map +0 -1
package/dist/agent/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { DEFAULT_BASE_URL, AgentError, AgentConnectionError } from '../chunk-
|
|
1
|
+
import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-2B74QZZJ.js';
|
|
2
2
|
import pino from 'pino';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
+
import os from 'os';
|
|
5
6
|
|
|
6
7
|
// src/agent/audio.ts
|
|
7
8
|
var _BIAS = 132;
|
|
@@ -972,6 +973,36 @@ var AudioRecorder = class {
|
|
|
972
973
|
}
|
|
973
974
|
}
|
|
974
975
|
};
|
|
976
|
+
var MAX_ERRORS = 20;
|
|
977
|
+
var MAX_ERROR_MESSAGE_LENGTH = 200;
|
|
978
|
+
function getSdkInfo() {
|
|
979
|
+
return {
|
|
980
|
+
name: "clawops-node",
|
|
981
|
+
version: VERSION,
|
|
982
|
+
runtime: `node/${process.versions.node}`,
|
|
983
|
+
os: `${process.platform}/${os.arch()}`
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function createCallMetrics() {
|
|
987
|
+
return {
|
|
988
|
+
firstResponseMs: null,
|
|
989
|
+
turnCount: 0,
|
|
990
|
+
toolCallCount: 0,
|
|
991
|
+
toolErrorCount: 0,
|
|
992
|
+
bargeInCount: 0,
|
|
993
|
+
endReason: null,
|
|
994
|
+
errors: []
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
function addMetricError(metrics, err) {
|
|
998
|
+
metrics.toolErrorCount++;
|
|
999
|
+
if (metrics.errors.length < MAX_ERRORS) {
|
|
1000
|
+
metrics.errors.push({
|
|
1001
|
+
type: err.name || "Error",
|
|
1002
|
+
message: (err.message || "").slice(0, MAX_ERROR_MESSAGE_LENGTH)
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
975
1006
|
|
|
976
1007
|
// src/agent/session.ts
|
|
977
1008
|
var CallSession = class {
|
|
@@ -997,6 +1028,8 @@ var CallSession = class {
|
|
|
997
1028
|
_handlers = /* @__PURE__ */ new Map();
|
|
998
1029
|
_endedPromise;
|
|
999
1030
|
_resolveEnded;
|
|
1031
|
+
_metrics = createCallMetrics();
|
|
1032
|
+
_firstResponseSent = false;
|
|
1000
1033
|
constructor(options) {
|
|
1001
1034
|
this.callId = options.callId;
|
|
1002
1035
|
this.fromNumber = options.fromNumber;
|
|
@@ -1019,6 +1052,30 @@ var CallSession = class {
|
|
|
1019
1052
|
get duration() {
|
|
1020
1053
|
return (Date.now() - this.startTime.getTime()) / 1e3;
|
|
1021
1054
|
}
|
|
1055
|
+
get metrics() {
|
|
1056
|
+
return this._metrics;
|
|
1057
|
+
}
|
|
1058
|
+
recordFirstResponse() {
|
|
1059
|
+
if (!this._firstResponseSent) {
|
|
1060
|
+
this._firstResponseSent = true;
|
|
1061
|
+
this._metrics.firstResponseMs = Date.now() - this.startTime.getTime();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
recordTurn() {
|
|
1065
|
+
this._metrics.turnCount++;
|
|
1066
|
+
}
|
|
1067
|
+
recordToolCall() {
|
|
1068
|
+
this._metrics.toolCallCount++;
|
|
1069
|
+
}
|
|
1070
|
+
recordToolError(err) {
|
|
1071
|
+
addMetricError(this._metrics, err);
|
|
1072
|
+
}
|
|
1073
|
+
recordBargeIn() {
|
|
1074
|
+
this._metrics.bargeInCount++;
|
|
1075
|
+
}
|
|
1076
|
+
recordEndReason(reason) {
|
|
1077
|
+
this._metrics.endReason = reason;
|
|
1078
|
+
}
|
|
1022
1079
|
/** Bind transport functions (called internally by the agent). */
|
|
1023
1080
|
_bindTransport(send, clear, hangup, sendDtmf, isConnected) {
|
|
1024
1081
|
this._sendAudioFn = send;
|
|
@@ -1459,6 +1516,7 @@ var ClawOpsAgent = class {
|
|
|
1459
1516
|
}
|
|
1460
1517
|
/** Connect to the ClawOps platform and start listening for calls. */
|
|
1461
1518
|
async connect() {
|
|
1519
|
+
if (this._controlWs) return;
|
|
1462
1520
|
if (!this._apiKey) {
|
|
1463
1521
|
throw new AgentError("API key is required. Set CLAWOPS_API_KEY or pass apiKey option.");
|
|
1464
1522
|
}
|
|
@@ -1482,6 +1540,10 @@ var ClawOpsAgent = class {
|
|
|
1482
1540
|
try {
|
|
1483
1541
|
await this._controlWs.connect();
|
|
1484
1542
|
await this._controlWs.waitConnected();
|
|
1543
|
+
try {
|
|
1544
|
+
this._controlWs.send({ event: "agent.hello", sdk: getSdkInfo() });
|
|
1545
|
+
} catch {
|
|
1546
|
+
}
|
|
1485
1547
|
} catch (err) {
|
|
1486
1548
|
throw new AgentConnectionError(
|
|
1487
1549
|
`Failed to connect to ClawOps: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1695,9 +1757,11 @@ var ClawOpsAgent = class {
|
|
|
1695
1757
|
session._bindTransport(
|
|
1696
1758
|
(audio) => {
|
|
1697
1759
|
mediaWs.sendAudio(audio.toString("base64"));
|
|
1760
|
+
session.recordFirstResponse();
|
|
1698
1761
|
},
|
|
1699
1762
|
() => {
|
|
1700
1763
|
mediaWs.sendClear();
|
|
1764
|
+
session.recordBargeIn();
|
|
1701
1765
|
},
|
|
1702
1766
|
async () => {
|
|
1703
1767
|
await mediaWs.flush();
|
|
@@ -1748,10 +1812,22 @@ var ClawOpsAgent = class {
|
|
|
1748
1812
|
await mediaWs.connect(mediaWsUrl, this._apiKey);
|
|
1749
1813
|
this._log.info("Media stream started: %s", session.callId);
|
|
1750
1814
|
await sessionHandler.start(session, sessionTools);
|
|
1815
|
+
const telemetry = sessionHandler.getTelemetry?.() ?? null;
|
|
1816
|
+
if (telemetry) {
|
|
1817
|
+
telemetry.toolCount = sessionTools?.size ?? 0;
|
|
1818
|
+
telemetry.mcpServerCount = this._mcpServers?.length ?? 0;
|
|
1819
|
+
telemetry.builtinTools = this._builtinTools ? [...this._builtinTools].map((t) => t.toString()) : [];
|
|
1820
|
+
telemetry.recordingEnabled = this._recording;
|
|
1821
|
+
try {
|
|
1822
|
+
this._controlWs.send({ event: "call.telemetry", callId: session.callId, telemetry });
|
|
1823
|
+
} catch {
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1751
1826
|
await session.wait();
|
|
1752
1827
|
await sessionHandler.stop();
|
|
1753
1828
|
} catch (err) {
|
|
1754
1829
|
this._log.error({ err }, "Call session error: %s", session.callId);
|
|
1830
|
+
session.recordEndReason("error");
|
|
1755
1831
|
} finally {
|
|
1756
1832
|
if (mcpClients.length > 0) {
|
|
1757
1833
|
sessionTools.clearMcpTools();
|
|
@@ -1763,6 +1839,13 @@ var ClawOpsAgent = class {
|
|
|
1763
1839
|
if (recorder) {
|
|
1764
1840
|
recorder.stop();
|
|
1765
1841
|
}
|
|
1842
|
+
if (!session.metrics.endReason) {
|
|
1843
|
+
session.recordEndReason(session.status === "ended" ? "user_hangup" : "agent_hangup");
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
this._controlWs?.send({ event: "call.metrics", callId: session.callId, metrics: session.metrics });
|
|
1847
|
+
} catch {
|
|
1848
|
+
}
|
|
1766
1849
|
session._emit("call_end");
|
|
1767
1850
|
session._markEnded();
|
|
1768
1851
|
this._activeSessions.delete(session.callId);
|
|
@@ -1773,20 +1856,13 @@ var ClawOpsAgent = class {
|
|
|
1773
1856
|
}
|
|
1774
1857
|
};
|
|
1775
1858
|
|
|
1776
|
-
// src/agent/pipeline/
|
|
1777
|
-
var
|
|
1778
|
-
var HANG_UP_TOOL = {
|
|
1779
|
-
type: "function",
|
|
1859
|
+
// src/agent/pipeline/builtin-tool-schemas.ts
|
|
1860
|
+
var HANG_UP = {
|
|
1780
1861
|
name: "hang_up",
|
|
1781
1862
|
description: "End the phone call. Use when the conversation is finished or the caller says goodbye.",
|
|
1782
|
-
parameters: {
|
|
1783
|
-
type: "object",
|
|
1784
|
-
properties: {},
|
|
1785
|
-
required: []
|
|
1786
|
-
}
|
|
1863
|
+
parameters: { type: "object", properties: {} }
|
|
1787
1864
|
};
|
|
1788
|
-
var
|
|
1789
|
-
type: "function",
|
|
1865
|
+
var COLLECT_DTMF = {
|
|
1790
1866
|
name: "collect_dtmf",
|
|
1791
1867
|
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.",
|
|
1792
1868
|
parameters: {
|
|
@@ -1799,8 +1875,7 @@ var COLLECT_DTMF_TOOL = {
|
|
|
1799
1875
|
required: ["max_digits"]
|
|
1800
1876
|
}
|
|
1801
1877
|
};
|
|
1802
|
-
var
|
|
1803
|
-
type: "function",
|
|
1878
|
+
var SEND_DTMF = {
|
|
1804
1879
|
name: "send_dtmf",
|
|
1805
1880
|
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.",
|
|
1806
1881
|
parameters: {
|
|
@@ -1808,492 +1883,419 @@ var SEND_DTMF_TOOL = {
|
|
|
1808
1883
|
properties: {
|
|
1809
1884
|
digits: {
|
|
1810
1885
|
type: "string",
|
|
1811
|
-
description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30."
|
|
1886
|
+
description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30. \uC608: '1', '1234#', '1w2'"
|
|
1812
1887
|
}
|
|
1813
1888
|
},
|
|
1814
1889
|
required: ["digits"]
|
|
1815
1890
|
}
|
|
1816
1891
|
};
|
|
1817
|
-
var
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1892
|
+
var TOOL_MAP = /* @__PURE__ */ new Map([
|
|
1893
|
+
["hang_up" /* HANG_UP */, HANG_UP],
|
|
1894
|
+
["collect_dtmf" /* COLLECT_DTMF */, COLLECT_DTMF],
|
|
1895
|
+
["send_dtmf" /* SEND_DTMF */, SEND_DTMF]
|
|
1896
|
+
]);
|
|
1897
|
+
var BUILTIN_TOOL_NAMES = new Set(
|
|
1898
|
+
Array.from(TOOL_MAP.values()).map((s) => s.name)
|
|
1899
|
+
);
|
|
1900
|
+
function toChatCompletions(schema) {
|
|
1901
|
+
return {
|
|
1902
|
+
type: "function",
|
|
1903
|
+
function: {
|
|
1904
|
+
name: schema.name,
|
|
1905
|
+
description: schema.description,
|
|
1906
|
+
parameters: schema.parameters
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
function toRealtime(schema) {
|
|
1911
|
+
return {
|
|
1912
|
+
type: "function",
|
|
1913
|
+
name: schema.name,
|
|
1914
|
+
description: schema.description,
|
|
1915
|
+
parameters: schema.parameters
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
function toGemini(schema) {
|
|
1919
|
+
return {
|
|
1920
|
+
name: schema.name,
|
|
1921
|
+
description: schema.description,
|
|
1922
|
+
parameters: schema.parameters
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
var CONVERTERS = {
|
|
1926
|
+
chat: toChatCompletions,
|
|
1927
|
+
realtime: toRealtime,
|
|
1928
|
+
gemini: toGemini
|
|
1929
|
+
};
|
|
1930
|
+
function getBuiltinToolSchemas(builtinTools, fmt) {
|
|
1931
|
+
const converter = CONVERTERS[fmt];
|
|
1932
|
+
const result = [];
|
|
1933
|
+
for (const [toolEnum, schema] of TOOL_MAP) {
|
|
1934
|
+
if (builtinTools === null || builtinTools.has(toolEnum)) {
|
|
1935
|
+
result.push(converter(schema));
|
|
1936
|
+
}
|
|
1829
1937
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1938
|
+
return result;
|
|
1939
|
+
}
|
|
1940
|
+
function isBuiltinTool(name) {
|
|
1941
|
+
return BUILTIN_TOOL_NAMES.has(name);
|
|
1942
|
+
}
|
|
1943
|
+
async function executeBuiltinTool(funcName, args, call) {
|
|
1944
|
+
if (funcName === "hang_up") {
|
|
1945
|
+
await call.hangup();
|
|
1946
|
+
return "";
|
|
1832
1947
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1948
|
+
if (funcName === "collect_dtmf") {
|
|
1949
|
+
try {
|
|
1950
|
+
const result = await call.collectDtmf({
|
|
1951
|
+
maxDigits: args["max_digits"] ?? 4,
|
|
1952
|
+
finishOnKey: args["finish_on_key"] ?? "#",
|
|
1953
|
+
timeout: args["timeout"] ?? 5
|
|
1954
|
+
});
|
|
1955
|
+
return result || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)";
|
|
1956
|
+
} catch (e) {
|
|
1957
|
+
return `Error: ${e}`;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
if (funcName === "send_dtmf") {
|
|
1961
|
+
try {
|
|
1962
|
+
await call.sendDtmfSequence(args["digits"] ?? "");
|
|
1963
|
+
return "sent";
|
|
1964
|
+
} catch (e) {
|
|
1965
|
+
return `Error: ${e}`;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
return null;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// src/agent/pipeline/pipeline-session.ts
|
|
1972
|
+
var PipelineSession = class {
|
|
1973
|
+
_stt;
|
|
1974
|
+
_llm;
|
|
1975
|
+
_tts;
|
|
1976
|
+
_systemPrompt;
|
|
1977
|
+
_greeting;
|
|
1978
|
+
_language;
|
|
1979
|
+
_temperature;
|
|
1980
|
+
_maxTokens;
|
|
1981
|
+
_sampleRate;
|
|
1982
|
+
_interruptOnSpeech;
|
|
1983
|
+
_callSession = null;
|
|
1835
1984
|
_tools = null;
|
|
1836
1985
|
_recorder = null;
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
this.
|
|
1848
|
-
this._systemPrompt = options.systemPrompt ?? "";
|
|
1849
|
-
this._model = options.model ?? "gpt-realtime-1.5";
|
|
1850
|
-
this._voice = options.voice ?? "marin";
|
|
1851
|
-
this._language = options.language ?? "ko";
|
|
1852
|
-
this._eagerness = options.eagerness ?? "high";
|
|
1986
|
+
_conversation = [];
|
|
1987
|
+
_audioBuffer = [];
|
|
1988
|
+
_running = false;
|
|
1989
|
+
_speaking = false;
|
|
1990
|
+
_builtinTools = null;
|
|
1991
|
+
_log = NOOP_LOGGER;
|
|
1992
|
+
constructor(options) {
|
|
1993
|
+
this._stt = options.stt;
|
|
1994
|
+
this._llm = options.llm;
|
|
1995
|
+
this._tts = options.tts;
|
|
1996
|
+
this._systemPrompt = options.systemPrompt;
|
|
1853
1997
|
this._greeting = options.greeting ?? true;
|
|
1998
|
+
this._language = options.language ?? "ko";
|
|
1999
|
+
this._temperature = options.temperature;
|
|
2000
|
+
this._maxTokens = options.maxTokens;
|
|
2001
|
+
this._sampleRate = options.sampleRate ?? 8e3;
|
|
2002
|
+
this._interruptOnSpeech = options.interruptOnSpeech ?? true;
|
|
2003
|
+
if (options.toolRegistry) this._tools = options.toolRegistry;
|
|
2004
|
+
if (options.recorder) this._recorder = options.recorder;
|
|
1854
2005
|
}
|
|
1855
|
-
/** Inject per-call ToolRegistry. */
|
|
1856
2006
|
setToolRegistry(registry) {
|
|
1857
2007
|
this._tools = registry;
|
|
1858
2008
|
}
|
|
1859
|
-
/** Inject per-call AudioRecorder. */
|
|
1860
2009
|
setRecorder(recorder) {
|
|
1861
2010
|
this._recorder = recorder;
|
|
1862
2011
|
}
|
|
1863
|
-
|
|
1864
|
-
this.
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2012
|
+
setBuiltinTools(tools) {
|
|
2013
|
+
this._builtinTools = tools;
|
|
2014
|
+
}
|
|
2015
|
+
getTelemetry() {
|
|
2016
|
+
const llm = this._llm;
|
|
2017
|
+
const stt = this._stt;
|
|
2018
|
+
const tts = this._tts;
|
|
2019
|
+
return {
|
|
2020
|
+
sessionType: "pipeline",
|
|
2021
|
+
llm: llm.provider && llm.model ? { provider: llm.provider, model: llm.model } : null,
|
|
2022
|
+
stt: stt.provider && stt.model ? { provider: stt.provider, model: stt.model } : null,
|
|
2023
|
+
tts: tts.provider && tts.model ? { provider: tts.provider, model: tts.model } : null,
|
|
2024
|
+
voice: tts.voiceId ?? null,
|
|
2025
|
+
language: this._language,
|
|
2026
|
+
greetingEnabled: this._greeting,
|
|
2027
|
+
recordingEnabled: !!this._recorder,
|
|
2028
|
+
toolCount: this._tools?.size ?? 0,
|
|
2029
|
+
mcpServerCount: 0,
|
|
2030
|
+
builtinTools: []
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
setLogger(logger) {
|
|
2034
|
+
this._log = logger;
|
|
2035
|
+
if ("setLogger" in this._stt && typeof this._stt.setLogger === "function") {
|
|
2036
|
+
this._stt.setLogger(logger);
|
|
1873
2037
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
this._send({ type: "response.create" });
|
|
1889
|
-
}
|
|
1890
|
-
resolve();
|
|
1891
|
-
});
|
|
1892
|
-
ws.on("message", (data) => {
|
|
1893
|
-
try {
|
|
1894
|
-
const msg = JSON.parse(data.toString());
|
|
1895
|
-
this._handleMessage(msg);
|
|
1896
|
-
} catch {
|
|
1897
|
-
}
|
|
1898
|
-
});
|
|
1899
|
-
ws.on("close", () => {
|
|
1900
|
-
this._closed = true;
|
|
2038
|
+
if ("setLogger" in this._tts && typeof this._tts.setLogger === "function") {
|
|
2039
|
+
this._tts.setLogger(logger);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
async start(callSession, tools) {
|
|
2043
|
+
this._callSession = callSession;
|
|
2044
|
+
this._tools = tools ?? null;
|
|
2045
|
+
this._running = true;
|
|
2046
|
+
this._log.info("PipelineSession started");
|
|
2047
|
+
this._conversation = [];
|
|
2048
|
+
if (this._systemPrompt) {
|
|
2049
|
+
this._conversation.push({
|
|
2050
|
+
role: "system",
|
|
2051
|
+
content: this._systemPrompt
|
|
1901
2052
|
});
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
}
|
|
1906
|
-
this._log.error({ err }, "OpenAI Realtime WS error");
|
|
2053
|
+
}
|
|
2054
|
+
if (this._greeting) {
|
|
2055
|
+
this._generateGreeting().catch((err) => {
|
|
2056
|
+
this._log.error({ err }, "Greeting error");
|
|
1907
2057
|
});
|
|
2058
|
+
}
|
|
2059
|
+
this._runSttLoop().catch((err) => {
|
|
2060
|
+
this._log.error({ err }, "STT loop error");
|
|
1908
2061
|
});
|
|
1909
2062
|
}
|
|
1910
|
-
async feedDtmf(digits) {
|
|
1911
|
-
await this._waitForResponseDone();
|
|
1912
|
-
this._send({
|
|
1913
|
-
type: "conversation.item.create",
|
|
1914
|
-
item: {
|
|
1915
|
-
type: "message",
|
|
1916
|
-
role: "user",
|
|
1917
|
-
content: [{ type: "input_text", text: `[DTMF \uC785\uB825: ${digits}]` }]
|
|
1918
|
-
}
|
|
1919
|
-
});
|
|
1920
|
-
this._send({ type: "response.create" });
|
|
1921
|
-
}
|
|
1922
2063
|
feedAudio(audio) {
|
|
1923
|
-
if (this.
|
|
1924
|
-
this.
|
|
1925
|
-
type: "input_audio_buffer.append",
|
|
1926
|
-
audio: audio.toString("base64")
|
|
1927
|
-
});
|
|
2064
|
+
if (this._running) {
|
|
2065
|
+
this._audioBuffer.push(audio);
|
|
1928
2066
|
}
|
|
1929
2067
|
}
|
|
2068
|
+
async feedDtmf(digits) {
|
|
2069
|
+
this._conversation.push({
|
|
2070
|
+
role: "user",
|
|
2071
|
+
content: `[DTMF \uC785\uB825: ${digits}]`
|
|
2072
|
+
});
|
|
2073
|
+
await this._respond();
|
|
2074
|
+
}
|
|
1930
2075
|
async stop() {
|
|
1931
|
-
this.
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
this._ws = null;
|
|
1935
|
-
}
|
|
2076
|
+
this._running = false;
|
|
2077
|
+
this._log.info("PipelineSession stopped");
|
|
2078
|
+
this._audioBuffer = [];
|
|
1936
2079
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
modalities: ["text", "audio"],
|
|
1950
|
-
voice: this._voice,
|
|
1951
|
-
instructions: this._systemPrompt,
|
|
1952
|
-
input_audio_format: "g711_ulaw",
|
|
1953
|
-
output_audio_format: "g711_ulaw",
|
|
1954
|
-
input_audio_transcription: {
|
|
1955
|
-
model: "whisper-1",
|
|
1956
|
-
language: this._language
|
|
1957
|
-
},
|
|
1958
|
-
input_audio_noise_reduction: { type: "far_field" },
|
|
1959
|
-
turn_detection: {
|
|
1960
|
-
type: "semantic_vad",
|
|
1961
|
-
interrupt_response: true,
|
|
1962
|
-
eagerness: this._eagerness
|
|
1963
|
-
},
|
|
1964
|
-
tools: toolSchemas
|
|
2080
|
+
async _runSttLoop() {
|
|
2081
|
+
const audioStream = this._createAudioStream();
|
|
2082
|
+
for await (const event of this._stt.transcribe(audioStream, {
|
|
2083
|
+
sampleRate: this._sampleRate
|
|
2084
|
+
})) {
|
|
2085
|
+
if (!this._running) break;
|
|
2086
|
+
if (event.type === "interim" && this._speaking && this._interruptOnSpeech) {
|
|
2087
|
+
this._speaking = false;
|
|
2088
|
+
if (this._callSession) {
|
|
2089
|
+
this._callSession.clearAudio();
|
|
2090
|
+
}
|
|
2091
|
+
this._log.info('Barge-in: "%s"', event.transcript.substring(0, 30));
|
|
1965
2092
|
}
|
|
1966
|
-
|
|
2093
|
+
if (event.type === "final" && event.transcript.trim()) {
|
|
2094
|
+
this._log.info("STT: %s", event.transcript);
|
|
2095
|
+
await this._handleUserSpeech(event.transcript);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
1967
2098
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
const padded = Buffer.concat([
|
|
1978
|
-
this._audioRemainder,
|
|
1979
|
-
Buffer.alloc(160 - this._audioRemainder.length, 255)
|
|
1980
|
-
]);
|
|
1981
|
-
if (this._call) {
|
|
1982
|
-
this._call.sendAudio(padded);
|
|
1983
|
-
}
|
|
1984
|
-
this._sentAudioChunks++;
|
|
1985
|
-
this._audioRemainder = Buffer.alloc(0);
|
|
1986
|
-
}
|
|
1987
|
-
break;
|
|
1988
|
-
}
|
|
1989
|
-
case "input_audio_buffer.speech_started": {
|
|
1990
|
-
this._handleTruncation();
|
|
1991
|
-
break;
|
|
1992
|
-
}
|
|
1993
|
-
case "response.output_item.done": {
|
|
1994
|
-
const item = msg["item"];
|
|
1995
|
-
if (item && item["type"] === "function_call") {
|
|
1996
|
-
this._handleToolCall(item);
|
|
1997
|
-
}
|
|
1998
|
-
break;
|
|
1999
|
-
}
|
|
2000
|
-
case "conversation.item.input_audio_transcription.completed": {
|
|
2001
|
-
if (this._call) {
|
|
2002
|
-
this._call._emit("transcript", "user", msg["transcript"] ?? "");
|
|
2003
|
-
}
|
|
2004
|
-
break;
|
|
2005
|
-
}
|
|
2006
|
-
case "response.audio_transcript.done": {
|
|
2007
|
-
if (this._call) {
|
|
2008
|
-
this._call._emit("transcript", "assistant", msg["transcript"] ?? "");
|
|
2009
|
-
}
|
|
2010
|
-
break;
|
|
2011
|
-
}
|
|
2012
|
-
case "response.created": {
|
|
2013
|
-
this._responseInProgress = true;
|
|
2014
|
-
break;
|
|
2015
|
-
}
|
|
2016
|
-
case "response.done": {
|
|
2017
|
-
this._responseInProgress = false;
|
|
2018
|
-
if (this._onResponseDone) {
|
|
2019
|
-
const cb = this._onResponseDone;
|
|
2020
|
-
this._onResponseDone = null;
|
|
2021
|
-
cb();
|
|
2022
|
-
}
|
|
2023
|
-
break;
|
|
2024
|
-
}
|
|
2025
|
-
case "error": {
|
|
2026
|
-
this._log.error({ apiError: msg["error"] }, "OpenAI error");
|
|
2027
|
-
break;
|
|
2099
|
+
async *_createAudioStream() {
|
|
2100
|
+
while (this._running) {
|
|
2101
|
+
if (this._audioBuffer.length > 0) {
|
|
2102
|
+
const ulaw = this._audioBuffer.shift();
|
|
2103
|
+
const pcm8k = ulawToPcm16(ulaw);
|
|
2104
|
+
const pcm16k = resamplePcm16(pcm8k, 8e3, 16e3);
|
|
2105
|
+
yield pcm16k;
|
|
2106
|
+
} else {
|
|
2107
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
2028
2108
|
}
|
|
2029
2109
|
}
|
|
2030
2110
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
this._sentAudioChunks = 0;
|
|
2035
|
-
}
|
|
2036
|
-
if (msg["item_id"]) {
|
|
2037
|
-
this._lastAssistantItem = msg["item_id"];
|
|
2038
|
-
}
|
|
2039
|
-
const ulaw = Buffer.from(msg["delta"], "base64");
|
|
2040
|
-
if (this._recorder) {
|
|
2041
|
-
this._recorder.writeOutbound(ulawToPcm16(ulaw));
|
|
2042
|
-
}
|
|
2043
|
-
const combined = Buffer.concat([this._audioRemainder, ulaw]);
|
|
2044
|
-
const chunkSize = 160;
|
|
2045
|
-
const fullEnd = Math.floor(combined.length / chunkSize) * chunkSize;
|
|
2046
|
-
for (let off = 0; off < fullEnd; off += chunkSize) {
|
|
2047
|
-
if (this._call) {
|
|
2048
|
-
this._call.sendAudio(combined.subarray(off, off + chunkSize));
|
|
2049
|
-
}
|
|
2050
|
-
this._sentAudioChunks++;
|
|
2051
|
-
}
|
|
2052
|
-
this._audioRemainder = combined.subarray(fullEnd);
|
|
2111
|
+
async _generateGreeting() {
|
|
2112
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2113
|
+
await this._respond();
|
|
2053
2114
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
this.
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
audio_end_ms: audioEndMs
|
|
2115
|
+
async _handleUserSpeech(transcript) {
|
|
2116
|
+
this._conversation.push({ role: "user", content: transcript });
|
|
2117
|
+
await this._respond();
|
|
2118
|
+
}
|
|
2119
|
+
_buildEffectiveTools() {
|
|
2120
|
+
const builtinSchemas = getBuiltinToolSchemas(this._builtinTools, "chat");
|
|
2121
|
+
const dtmfSchemas = builtinSchemas.filter((s) => {
|
|
2122
|
+
const name = s["function"]?.["name"];
|
|
2123
|
+
return name === "collect_dtmf" || name === "send_dtmf";
|
|
2064
2124
|
});
|
|
2065
|
-
if (
|
|
2066
|
-
|
|
2125
|
+
if (dtmfSchemas.length === 0) return this._tools ?? void 0;
|
|
2126
|
+
const base = this._tools ? this._tools.fork() : new ToolRegistry();
|
|
2127
|
+
for (const schema of dtmfSchemas) {
|
|
2128
|
+
const fn = schema["function"];
|
|
2129
|
+
const params = fn["parameters"];
|
|
2130
|
+
base.register({
|
|
2131
|
+
name: fn["name"],
|
|
2132
|
+
description: fn["description"],
|
|
2133
|
+
parameters: params["properties"] ?? {},
|
|
2134
|
+
required: params["required"] ?? [],
|
|
2135
|
+
handler: async () => ""
|
|
2136
|
+
});
|
|
2067
2137
|
}
|
|
2068
|
-
|
|
2069
|
-
this._responseStartTs = null;
|
|
2070
|
-
this._sentAudioChunks = 0;
|
|
2071
|
-
this._audioRemainder = Buffer.alloc(0);
|
|
2138
|
+
return base;
|
|
2072
2139
|
}
|
|
2073
|
-
async
|
|
2074
|
-
|
|
2075
|
-
const
|
|
2076
|
-
this.
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2140
|
+
async _respond() {
|
|
2141
|
+
let fullResponse = "";
|
|
2142
|
+
const textChunks = [];
|
|
2143
|
+
const effectiveTools = this._buildEffectiveTools();
|
|
2144
|
+
const llmStream = this._llm.generate(this._conversation, {
|
|
2145
|
+
tools: effectiveTools,
|
|
2146
|
+
temperature: this._temperature,
|
|
2147
|
+
maxTokens: this._maxTokens
|
|
2148
|
+
});
|
|
2149
|
+
for await (const chunk of llmStream) {
|
|
2150
|
+
if (!this._running) break;
|
|
2151
|
+
if (chunk.type === "text" && chunk.text) {
|
|
2152
|
+
textChunks.push(chunk.text);
|
|
2153
|
+
fullResponse += chunk.text;
|
|
2154
|
+
} else if (chunk.type === "tool_call" && chunk.toolCall) {
|
|
2155
|
+
await this._handleToolCall(chunk);
|
|
2080
2156
|
}
|
|
2081
|
-
return;
|
|
2082
2157
|
}
|
|
2083
|
-
if (
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2158
|
+
if (fullResponse.trim()) {
|
|
2159
|
+
this._log.info("Assistant: %s", fullResponse.substring(0, 100));
|
|
2160
|
+
this._conversation.push({ role: "assistant", content: fullResponse });
|
|
2161
|
+
await this._synthesizeAndSend(fullResponse);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
async _handleToolCall(chunk) {
|
|
2165
|
+
if (!chunk.toolCall) return;
|
|
2166
|
+
const { id, name, arguments: argsStr } = chunk.toolCall;
|
|
2167
|
+
try {
|
|
2168
|
+
const args = JSON.parse(argsStr);
|
|
2169
|
+
if (BUILTIN_TOOL_NAMES.has(name) && this._callSession) {
|
|
2170
|
+
const result2 = await executeBuiltinTool(name, args, this._callSession);
|
|
2171
|
+
if (result2 !== null) {
|
|
2172
|
+
if (name === "hang_up") return;
|
|
2173
|
+
this._conversation.push({ role: "tool", content: result2, tool_call_id: id, name });
|
|
2174
|
+
await this._respond();
|
|
2175
|
+
return;
|
|
2095
2176
|
}
|
|
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 || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)"
|
|
2103
|
-
}
|
|
2104
|
-
});
|
|
2105
|
-
this._send({ type: "response.create" });
|
|
2106
2177
|
}
|
|
2107
|
-
return;
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2178
|
+
if (!this._tools) return;
|
|
2179
|
+
this._callSession?.recordToolCall();
|
|
2180
|
+
const result = await this._tools.call(name, args);
|
|
2181
|
+
this._conversation.push({
|
|
2182
|
+
role: "assistant",
|
|
2183
|
+
content: ""
|
|
2184
|
+
// Tool call info stored in the message flow
|
|
2185
|
+
});
|
|
2186
|
+
this._conversation.push({
|
|
2187
|
+
role: "tool",
|
|
2188
|
+
content: typeof result === "string" ? result : JSON.stringify(result),
|
|
2189
|
+
tool_call_id: id,
|
|
2190
|
+
name
|
|
2191
|
+
});
|
|
2192
|
+
const effectiveTools = this._buildEffectiveTools();
|
|
2193
|
+
let followUpText = "";
|
|
2194
|
+
const followUpStream = this._llm.generate(this._conversation, {
|
|
2195
|
+
tools: effectiveTools,
|
|
2196
|
+
temperature: this._temperature,
|
|
2197
|
+
maxTokens: this._maxTokens
|
|
2198
|
+
});
|
|
2199
|
+
for await (const followChunk of followUpStream) {
|
|
2200
|
+
if (!this._running) break;
|
|
2201
|
+
if (followChunk.type === "text" && followChunk.text) {
|
|
2202
|
+
followUpText += followChunk.text;
|
|
2118
2203
|
}
|
|
2119
|
-
await this._waitForResponseDone();
|
|
2120
|
-
this._send({
|
|
2121
|
-
type: "conversation.item.create",
|
|
2122
|
-
item: {
|
|
2123
|
-
type: "function_call_output",
|
|
2124
|
-
call_id: callId,
|
|
2125
|
-
output: result2
|
|
2126
|
-
}
|
|
2127
|
-
});
|
|
2128
|
-
this._send({ type: "response.create" });
|
|
2129
2204
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
this._log.error("Unknown tool: %s", funcName);
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
let result;
|
|
2137
|
-
try {
|
|
2138
|
-
const args = JSON.parse(item["arguments"] ?? "{}");
|
|
2139
|
-
result = await this._tools.call(funcName, args);
|
|
2140
|
-
} catch (err) {
|
|
2141
|
-
this._log.error({ err }, "Tool call failed: %s", funcName);
|
|
2142
|
-
result = `Error: ${err}`;
|
|
2143
|
-
}
|
|
2144
|
-
await this._waitForResponseDone();
|
|
2145
|
-
this._send({
|
|
2146
|
-
type: "conversation.item.create",
|
|
2147
|
-
item: {
|
|
2148
|
-
type: "function_call_output",
|
|
2149
|
-
call_id: callId,
|
|
2150
|
-
output: typeof result === "string" ? result : JSON.stringify(result)
|
|
2205
|
+
if (followUpText.trim()) {
|
|
2206
|
+
this._conversation.push({ role: "assistant", content: followUpText });
|
|
2207
|
+
await this._synthesizeAndSend(followUpText);
|
|
2151
2208
|
}
|
|
2152
|
-
})
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
if (!this._responseInProgress) return Promise.resolve();
|
|
2157
|
-
return new Promise((resolve) => {
|
|
2158
|
-
this._onResponseDone = resolve;
|
|
2159
|
-
});
|
|
2160
|
-
}
|
|
2161
|
-
_send(data) {
|
|
2162
|
-
if (this._ws && this._ws.readyState === 1 && !this._closed) {
|
|
2163
|
-
this._ws.send(JSON.stringify(data));
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
};
|
|
2167
|
-
|
|
2168
|
-
// src/agent/pipeline/gemini-realtime.ts
|
|
2169
|
-
var HANG_UP_TOOL2 = {
|
|
2170
|
-
name: "hang_up",
|
|
2171
|
-
description: "End the phone call. Use when the conversation is finished or the caller says goodbye.",
|
|
2172
|
-
parameters: { type: "object", properties: {} }
|
|
2173
|
-
};
|
|
2174
|
-
var COLLECT_DTMF_TOOL2 = {
|
|
2175
|
-
name: "collect_dtmf",
|
|
2176
|
-
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.",
|
|
2177
|
-
parameters: {
|
|
2178
|
-
type: "object",
|
|
2179
|
-
properties: {
|
|
2180
|
-
max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
|
|
2181
|
-
finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
|
|
2182
|
-
timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
|
|
2183
|
-
},
|
|
2184
|
-
required: ["max_digits"]
|
|
2185
|
-
}
|
|
2186
|
-
};
|
|
2187
|
-
var SEND_DTMF_TOOL2 = {
|
|
2188
|
-
name: "send_dtmf",
|
|
2189
|
-
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.",
|
|
2190
|
-
parameters: {
|
|
2191
|
-
type: "object",
|
|
2192
|
-
properties: {
|
|
2193
|
-
digits: {
|
|
2194
|
-
type: "string",
|
|
2195
|
-
description: "\uC804\uC1A1\uD560 \uBC88\uD638 (0-9, *, #). 'w'\uB294 500ms \uB300\uAE30, 'W'\uB294 1000ms \uB300\uAE30."
|
|
2209
|
+
} catch (err) {
|
|
2210
|
+
this._log.error({ err }, "Tool call failed: %s", name);
|
|
2211
|
+
if (err instanceof Error) {
|
|
2212
|
+
this._callSession?.recordToolError(err);
|
|
2196
2213
|
}
|
|
2197
|
-
},
|
|
2198
|
-
required: ["digits"]
|
|
2199
|
-
}
|
|
2200
|
-
};
|
|
2201
|
-
function resolveRef(ref, defs) {
|
|
2202
|
-
const parts = ref.replace(/^#\//, "").split("/");
|
|
2203
|
-
let result = defs;
|
|
2204
|
-
for (const part of parts) {
|
|
2205
|
-
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
2206
|
-
result = result[part];
|
|
2207
|
-
} else {
|
|
2208
|
-
return {};
|
|
2209
2214
|
}
|
|
2210
2215
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
for (const v of variants) {
|
|
2230
|
-
if (v && typeof v === "object") {
|
|
2231
|
-
const resolved = sanitizeSchemaForGemini(v, defs, depth + 1);
|
|
2232
|
-
if (resolved["type"] === "object" && resolved["properties"]) {
|
|
2233
|
-
return resolved;
|
|
2216
|
+
async _synthesizeAndSend(text) {
|
|
2217
|
+
if (!this._callSession || !this._running) return;
|
|
2218
|
+
this._speaking = true;
|
|
2219
|
+
try {
|
|
2220
|
+
for await (const audioChunk of this._tts.synthesize(text, {
|
|
2221
|
+
sampleRate: this._sampleRate
|
|
2222
|
+
})) {
|
|
2223
|
+
if (!this._running || !this._speaking) break;
|
|
2224
|
+
if (this._recorder) {
|
|
2225
|
+
const pcm8k2 = this._sampleRate !== 8e3 ? resamplePcm16(audioChunk, this._sampleRate, 8e3) : audioChunk;
|
|
2226
|
+
this._recorder.writeOutbound(pcm8k2);
|
|
2227
|
+
}
|
|
2228
|
+
const pcm8k = resamplePcm16(audioChunk, this._sampleRate, 8e3);
|
|
2229
|
+
const ulaw = pcm16ToUlaw(pcm8k);
|
|
2230
|
+
for (let off = 0; off < ulaw.length; off += 160) {
|
|
2231
|
+
let chunk = ulaw.subarray(off, off + 160);
|
|
2232
|
+
if (chunk.length < 160) {
|
|
2233
|
+
chunk = Buffer.concat([chunk, Buffer.alloc(160 - chunk.length, 255)]);
|
|
2234
2234
|
}
|
|
2235
|
+
this._callSession.sendAudio(chunk);
|
|
2235
2236
|
}
|
|
2236
2237
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
const result = {};
|
|
2244
|
-
let schemaType = schema["type"];
|
|
2245
|
-
if (Array.isArray(schemaType)) {
|
|
2246
|
-
const nonNull = schemaType.filter((t) => t !== "null");
|
|
2247
|
-
schemaType = nonNull[0] ?? "string";
|
|
2248
|
-
}
|
|
2249
|
-
if (schemaType) result["type"] = schemaType;
|
|
2250
|
-
if (schema["description"]) result["description"] = schema["description"];
|
|
2251
|
-
if (schema["enum"]) result["enum"] = schema["enum"];
|
|
2252
|
-
if (schema["required"]) result["required"] = schema["required"];
|
|
2253
|
-
if (schema["properties"] && typeof schema["properties"] === "object") {
|
|
2254
|
-
const props = {};
|
|
2255
|
-
for (const [key, val] of Object.entries(schema["properties"])) {
|
|
2256
|
-
if (val && typeof val === "object") {
|
|
2257
|
-
props[key] = sanitizeSchemaForGemini(val, defs, depth + 1);
|
|
2258
|
-
}
|
|
2238
|
+
} catch (err) {
|
|
2239
|
+
this._log.error({ err }, "TTS error");
|
|
2240
|
+
} finally {
|
|
2241
|
+
this._speaking = false;
|
|
2259
2242
|
}
|
|
2260
|
-
result["properties"] = props;
|
|
2261
|
-
}
|
|
2262
|
-
if (schema["items"] && typeof schema["items"] === "object" && !Array.isArray(schema["items"])) {
|
|
2263
|
-
result["items"] = sanitizeSchemaForGemini(
|
|
2264
|
-
schema["items"],
|
|
2265
|
-
defs,
|
|
2266
|
-
depth + 1
|
|
2267
|
-
);
|
|
2268
2243
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
var
|
|
2244
|
+
};
|
|
2245
|
+
|
|
2246
|
+
// src/agent/pipeline/realtime/openai-realtime.ts
|
|
2247
|
+
var OPENAI_REALTIME_URL = "wss://api.openai.com/v1/realtime?model=";
|
|
2248
|
+
var OpenAIRealtime = class {
|
|
2274
2249
|
_apiKey;
|
|
2275
2250
|
_systemPrompt;
|
|
2276
2251
|
_model;
|
|
2277
2252
|
_voice;
|
|
2278
2253
|
_language;
|
|
2254
|
+
_turnDetection;
|
|
2279
2255
|
_greeting;
|
|
2280
|
-
|
|
2281
|
-
|
|
2256
|
+
_log = NOOP_LOGGER;
|
|
2257
|
+
_builtinTools = null;
|
|
2258
|
+
setLogger(logger) {
|
|
2259
|
+
this._log = logger;
|
|
2260
|
+
}
|
|
2261
|
+
setBuiltinTools(tools) {
|
|
2262
|
+
this._builtinTools = tools;
|
|
2263
|
+
}
|
|
2264
|
+
getTelemetry() {
|
|
2265
|
+
return {
|
|
2266
|
+
sessionType: "openai_realtime",
|
|
2267
|
+
llm: { provider: "openai", model: this._model },
|
|
2268
|
+
stt: null,
|
|
2269
|
+
tts: null,
|
|
2270
|
+
voice: this._voice,
|
|
2271
|
+
language: this._language,
|
|
2272
|
+
greetingEnabled: this._greeting,
|
|
2273
|
+
recordingEnabled: !!this._recorder,
|
|
2274
|
+
toolCount: this._tools?.size ?? 0,
|
|
2275
|
+
mcpServerCount: 0,
|
|
2276
|
+
builtinTools: []
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
_ws = null;
|
|
2282
2280
|
_call = null;
|
|
2283
2281
|
_tools = null;
|
|
2284
2282
|
_recorder = null;
|
|
2285
2283
|
_closed = false;
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2284
|
+
// PlaybackState — 현재 재생 중인 응답 상태
|
|
2285
|
+
_playback = null;
|
|
2286
|
+
_latestMediaTs = 0;
|
|
2287
|
+
// Pending tool call tracking — 인터럽트 시 취소용
|
|
2288
|
+
_pendingToolCalls = /* @__PURE__ */ new Map();
|
|
2289
|
+
// Response state tracking — prevent sending response.create while one is active
|
|
2290
|
+
_responseInProgress = false;
|
|
2291
|
+
_onResponseDone = null;
|
|
2291
2292
|
constructor(options = {}) {
|
|
2292
|
-
this._apiKey = options.apiKey ?? process.env["
|
|
2293
|
+
this._apiKey = options.apiKey ?? process.env["OPENAI_API_KEY"] ?? "";
|
|
2293
2294
|
this._systemPrompt = options.systemPrompt ?? "";
|
|
2294
|
-
this._model = options.model ?? "
|
|
2295
|
-
this._voice = options.voice ?? "
|
|
2295
|
+
this._model = options.model ?? "gpt-realtime-1.5";
|
|
2296
|
+
this._voice = options.voice ?? "marin";
|
|
2296
2297
|
this._language = options.language ?? "ko";
|
|
2298
|
+
this._turnDetection = options.turnDetection !== void 0 ? options.turnDetection : { type: "semantic_vad", eagerness: "medium", interrupt_response: true };
|
|
2297
2299
|
this._greeting = options.greeting ?? true;
|
|
2298
2300
|
}
|
|
2299
2301
|
/** Inject per-call ToolRegistry. */
|
|
@@ -2304,340 +2306,409 @@ var GeminiRealtime = class {
|
|
|
2304
2306
|
setRecorder(recorder) {
|
|
2305
2307
|
this._recorder = recorder;
|
|
2306
2308
|
}
|
|
2307
|
-
setBuiltinTools(tools) {
|
|
2308
|
-
this._builtinTools = tools;
|
|
2309
|
-
}
|
|
2310
|
-
setLogger(logger) {
|
|
2311
|
-
this._log = logger;
|
|
2312
|
-
}
|
|
2313
2309
|
async start(callSession, tools) {
|
|
2314
2310
|
this._call = callSession;
|
|
2315
2311
|
if (tools) this._tools = tools;
|
|
2316
2312
|
this._closed = false;
|
|
2317
|
-
this.
|
|
2318
|
-
this.
|
|
2313
|
+
this._playback = null;
|
|
2314
|
+
this._latestMediaTs = 0;
|
|
2319
2315
|
if (!this._apiKey) {
|
|
2320
|
-
throw new Error("
|
|
2321
|
-
}
|
|
2322
|
-
const { GoogleGenAI } = await import('@google/genai/node');
|
|
2323
|
-
const client = new GoogleGenAI({ apiKey: this._apiKey });
|
|
2324
|
-
const config = {
|
|
2325
|
-
responseModalities: ["AUDIO"],
|
|
2326
|
-
speechConfig: {
|
|
2327
|
-
voiceConfig: {
|
|
2328
|
-
prebuiltVoiceConfig: {
|
|
2329
|
-
voiceName: this._voice
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
},
|
|
2333
|
-
inputAudioTranscription: {},
|
|
2334
|
-
outputAudioTranscription: {}
|
|
2335
|
-
};
|
|
2336
|
-
if (this._systemPrompt) {
|
|
2337
|
-
config["systemInstruction"] = this._systemPrompt;
|
|
2338
|
-
}
|
|
2339
|
-
const toolSchemas = this._buildToolSchemas();
|
|
2340
|
-
if (toolSchemas.length > 0) {
|
|
2341
|
-
config["tools"] = [{ functionDeclarations: toolSchemas }];
|
|
2316
|
+
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey option.");
|
|
2342
2317
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
this._log.error({ err }, "Gemini SDK error");
|
|
2350
|
-
},
|
|
2351
|
-
onclose: (ev) => {
|
|
2352
|
-
this._log.info({ code: ev?.code ?? "unknown" }, "Gemini connection closed");
|
|
2353
|
-
this._closed = true;
|
|
2354
|
-
}
|
|
2318
|
+
const { WebSocket } = await import('ws');
|
|
2319
|
+
const url = `${OPENAI_REALTIME_URL}${this._model}`;
|
|
2320
|
+
this._ws = new WebSocket(url, {
|
|
2321
|
+
headers: {
|
|
2322
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
2323
|
+
"OpenAI-Beta": "realtime=v1"
|
|
2355
2324
|
}
|
|
2356
2325
|
});
|
|
2357
|
-
|
|
2358
|
-
this.
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2326
|
+
return new Promise((resolve, reject) => {
|
|
2327
|
+
const ws = this._ws;
|
|
2328
|
+
ws.on("open", () => {
|
|
2329
|
+
this._sendSessionUpdate();
|
|
2330
|
+
this._log.info("OpenAI Realtime connected");
|
|
2331
|
+
if (this._greeting) {
|
|
2332
|
+
this._send({ type: "response.create" });
|
|
2333
|
+
}
|
|
2334
|
+
resolve();
|
|
2366
2335
|
});
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2336
|
+
ws.on("message", (data) => {
|
|
2337
|
+
try {
|
|
2338
|
+
const msg = JSON.parse(data.toString());
|
|
2339
|
+
this._handleMessage(msg);
|
|
2340
|
+
} catch {
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
ws.on("close", () => {
|
|
2344
|
+
this._closed = true;
|
|
2345
|
+
});
|
|
2346
|
+
ws.on("error", (err) => {
|
|
2347
|
+
if (!this._ws) {
|
|
2348
|
+
reject(err);
|
|
2380
2349
|
}
|
|
2350
|
+
this._log.error({ err }, "OpenAI Realtime WS error");
|
|
2381
2351
|
});
|
|
2382
|
-
}
|
|
2352
|
+
});
|
|
2383
2353
|
}
|
|
2384
2354
|
async feedDtmf(digits) {
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2355
|
+
await this._waitForResponseDone();
|
|
2356
|
+
this._send({
|
|
2357
|
+
type: "conversation.item.create",
|
|
2358
|
+
item: {
|
|
2359
|
+
type: "message",
|
|
2360
|
+
role: "user",
|
|
2361
|
+
content: [{ type: "input_text", text: `[DTMF \uC785\uB825: ${digits}]` }]
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
this._send({ type: "response.create" });
|
|
2365
|
+
}
|
|
2366
|
+
feedAudio(audio) {
|
|
2367
|
+
this._latestMediaTs = Date.now();
|
|
2368
|
+
if (this._ws && this._ws.readyState === 1 && !this._closed) {
|
|
2369
|
+
this._send({
|
|
2370
|
+
type: "input_audio_buffer.append",
|
|
2371
|
+
audio: audio.toString("base64")
|
|
2389
2372
|
});
|
|
2390
2373
|
}
|
|
2391
2374
|
}
|
|
2392
2375
|
async stop() {
|
|
2393
2376
|
this._closed = true;
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
this.
|
|
2377
|
+
for (const [, controller] of this._pendingToolCalls) {
|
|
2378
|
+
controller.abort();
|
|
2379
|
+
}
|
|
2380
|
+
this._pendingToolCalls.clear();
|
|
2381
|
+
if (this._ws) {
|
|
2382
|
+
this._ws.close();
|
|
2383
|
+
this._ws = null;
|
|
2400
2384
|
}
|
|
2401
2385
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2386
|
+
_sendSessionUpdate() {
|
|
2387
|
+
if (!this._ws || this._ws.readyState !== 1) return;
|
|
2388
|
+
const toolSchemas = this._tools ? this._tools.toOpenAITools().map((t) => ({ type: "function", ...t.function })) : [];
|
|
2389
|
+
toolSchemas.push(...getBuiltinToolSchemas(this._builtinTools, "realtime"));
|
|
2390
|
+
this._send({
|
|
2391
|
+
type: "session.update",
|
|
2392
|
+
session: {
|
|
2393
|
+
modalities: ["text", "audio"],
|
|
2394
|
+
voice: this._voice,
|
|
2395
|
+
instructions: this._systemPrompt,
|
|
2396
|
+
input_audio_format: "g711_ulaw",
|
|
2397
|
+
output_audio_format: "g711_ulaw",
|
|
2398
|
+
input_audio_transcription: {
|
|
2399
|
+
model: "whisper-1",
|
|
2400
|
+
language: this._language
|
|
2401
|
+
},
|
|
2402
|
+
input_audio_noise_reduction: { type: "far_field" },
|
|
2403
|
+
turn_detection: this._turnDetection,
|
|
2404
|
+
tools: toolSchemas
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2414
2407
|
}
|
|
2415
2408
|
_handleMessage(msg) {
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2409
|
+
const type = msg["type"];
|
|
2410
|
+
switch (type) {
|
|
2411
|
+
case "response.audio.delta": {
|
|
2412
|
+
this._handleAudioDelta(msg);
|
|
2413
|
+
break;
|
|
2414
|
+
}
|
|
2415
|
+
case "response.audio.done": {
|
|
2416
|
+
if (this._playback) {
|
|
2417
|
+
this._playback.generating = false;
|
|
2418
|
+
if (this._playback.audioRemainder.length > 0) {
|
|
2419
|
+
const padded = Buffer.concat([
|
|
2420
|
+
this._playback.audioRemainder,
|
|
2421
|
+
Buffer.alloc(160 - this._playback.audioRemainder.length, 255)
|
|
2422
|
+
]);
|
|
2423
|
+
if (this._call) {
|
|
2424
|
+
this._call.sendAudio(padded);
|
|
2427
2425
|
}
|
|
2426
|
+
this._playback.sentChunks++;
|
|
2427
|
+
this._playback.audioRemainder = Buffer.alloc(0);
|
|
2428
2428
|
}
|
|
2429
2429
|
}
|
|
2430
|
+
break;
|
|
2430
2431
|
}
|
|
2431
|
-
|
|
2432
|
-
this.
|
|
2433
|
-
|
|
2432
|
+
case "input_audio_buffer.speech_started": {
|
|
2433
|
+
this._handleTruncation();
|
|
2434
|
+
break;
|
|
2434
2435
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2436
|
+
case "response.output_item.done": {
|
|
2437
|
+
const item = msg["item"];
|
|
2438
|
+
if (item && item["type"] === "function_call") {
|
|
2439
|
+
this._handleToolCall(item);
|
|
2440
|
+
}
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
case "conversation.item.input_audio_transcription.completed": {
|
|
2437
2444
|
if (this._call) {
|
|
2438
|
-
this._call.
|
|
2445
|
+
this._call._emit("transcript", "user", msg["transcript"] ?? "");
|
|
2439
2446
|
}
|
|
2440
|
-
|
|
2441
|
-
this._audioRemainder = Buffer.alloc(0);
|
|
2447
|
+
break;
|
|
2442
2448
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2449
|
+
case "response.audio_transcript.done": {
|
|
2450
|
+
if (this._call) {
|
|
2451
|
+
this._call._emit("transcript", "assistant", msg["transcript"] ?? "");
|
|
2452
|
+
}
|
|
2453
|
+
break;
|
|
2447
2454
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2455
|
+
case "response.created": {
|
|
2456
|
+
this._responseInProgress = true;
|
|
2457
|
+
break;
|
|
2458
|
+
}
|
|
2459
|
+
case "response.done": {
|
|
2460
|
+
this._responseInProgress = false;
|
|
2461
|
+
if (this._onResponseDone) {
|
|
2462
|
+
const cb = this._onResponseDone;
|
|
2463
|
+
this._onResponseDone = null;
|
|
2464
|
+
cb();
|
|
2465
|
+
}
|
|
2466
|
+
break;
|
|
2467
|
+
}
|
|
2468
|
+
case "error": {
|
|
2469
|
+
this._log.error({ apiError: msg["error"] }, "OpenAI error");
|
|
2470
|
+
break;
|
|
2452
2471
|
}
|
|
2453
|
-
}
|
|
2454
|
-
if (msg.toolCall) {
|
|
2455
|
-
this._handleToolCall(msg.toolCall);
|
|
2456
|
-
}
|
|
2457
|
-
const toolCancellation = msg["toolCallCancellation"];
|
|
2458
|
-
if (toolCancellation) {
|
|
2459
|
-
this._log.info({ ids: toolCancellation.ids }, "Tool call cancelled");
|
|
2460
2472
|
}
|
|
2461
2473
|
}
|
|
2462
|
-
|
|
2463
|
-
if (
|
|
2464
|
-
|
|
2474
|
+
_handleAudioDelta(msg) {
|
|
2475
|
+
if (this._playback === null) {
|
|
2476
|
+
this._playback = {
|
|
2477
|
+
itemId: msg["item_id"] || "",
|
|
2478
|
+
startTs: this._latestMediaTs || Date.now(),
|
|
2479
|
+
sentChunks: 0,
|
|
2480
|
+
generating: true,
|
|
2481
|
+
audioRemainder: Buffer.alloc(0)
|
|
2482
|
+
};
|
|
2483
|
+
} else if (msg["item_id"]) {
|
|
2484
|
+
this._playback.itemId = msg["item_id"];
|
|
2485
|
+
}
|
|
2486
|
+
const pb = this._playback;
|
|
2487
|
+
const ulaw = Buffer.from(msg["delta"], "base64");
|
|
2465
2488
|
if (this._recorder) {
|
|
2466
|
-
this._recorder.writeOutbound(
|
|
2489
|
+
this._recorder.writeOutbound(ulawToPcm16(ulaw));
|
|
2467
2490
|
}
|
|
2468
|
-
const
|
|
2469
|
-
const ulaw = pcm16ToUlaw(pcm8k);
|
|
2470
|
-
const combined = Buffer.concat([this._audioRemainder, ulaw]);
|
|
2491
|
+
const combined = Buffer.concat([pb.audioRemainder, ulaw]);
|
|
2471
2492
|
const chunkSize = 160;
|
|
2472
2493
|
const fullEnd = Math.floor(combined.length / chunkSize) * chunkSize;
|
|
2473
|
-
|
|
2474
|
-
this._call
|
|
2475
|
-
|
|
2494
|
+
for (let off = 0; off < fullEnd; off += chunkSize) {
|
|
2495
|
+
if (this._call) {
|
|
2496
|
+
this._call.sendAudio(combined.subarray(off, off + chunkSize));
|
|
2497
|
+
}
|
|
2498
|
+
pb.sentChunks++;
|
|
2476
2499
|
}
|
|
2477
|
-
|
|
2500
|
+
pb.audioRemainder = combined.subarray(fullEnd);
|
|
2478
2501
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
this._call.
|
|
2486
|
-
|
|
2487
|
-
|
|
2502
|
+
_handleTruncation() {
|
|
2503
|
+
for (const [, controller] of this._pendingToolCalls) {
|
|
2504
|
+
controller.abort();
|
|
2505
|
+
}
|
|
2506
|
+
this._pendingToolCalls.clear();
|
|
2507
|
+
if (this._call) {
|
|
2508
|
+
this._call.clearAudio();
|
|
2509
|
+
}
|
|
2510
|
+
const pb = this._playback;
|
|
2511
|
+
if (pb === null) {
|
|
2512
|
+
return;
|
|
2488
2513
|
}
|
|
2514
|
+
const playedMs = Math.max(0, (this._latestMediaTs || Date.now()) - pb.startTs);
|
|
2515
|
+
this._log.info(
|
|
2516
|
+
"[Interrupt] item=%s played=%dms total=%dms",
|
|
2517
|
+
pb.itemId,
|
|
2518
|
+
playedMs,
|
|
2519
|
+
pb.sentChunks * 20
|
|
2520
|
+
);
|
|
2521
|
+
this._playback = null;
|
|
2489
2522
|
}
|
|
2490
|
-
async _handleToolCall(
|
|
2491
|
-
const
|
|
2492
|
-
|
|
2493
|
-
this.
|
|
2494
|
-
const
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
await this.
|
|
2523
|
+
async _handleToolCall(item) {
|
|
2524
|
+
const funcName = item["name"];
|
|
2525
|
+
const callId = item["call_id"];
|
|
2526
|
+
this._log.info("Tool call: %s", funcName);
|
|
2527
|
+
const controller = new AbortController();
|
|
2528
|
+
this._pendingToolCalls.set(callId, controller);
|
|
2529
|
+
try {
|
|
2530
|
+
if (BUILTIN_TOOL_NAMES.has(funcName) && this._call) {
|
|
2531
|
+
const args = JSON.parse(item["arguments"] ?? "{}");
|
|
2532
|
+
const result2 = await executeBuiltinTool(funcName, args, this._call);
|
|
2533
|
+
if (result2 !== null) {
|
|
2534
|
+
if (funcName === "hang_up") return;
|
|
2535
|
+
if (controller.signal.aborted) return;
|
|
2536
|
+
await this._waitForResponseDone();
|
|
2537
|
+
this._send({
|
|
2538
|
+
type: "conversation.item.create",
|
|
2539
|
+
item: {
|
|
2540
|
+
type: "function_call_output",
|
|
2541
|
+
call_id: callId,
|
|
2542
|
+
output: result2
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
this._send({ type: "response.create" });
|
|
2546
|
+
return;
|
|
2504
2547
|
}
|
|
2505
|
-
return;
|
|
2506
2548
|
}
|
|
2507
|
-
if (
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
});
|
|
2517
|
-
this._log.info("DTMF collected: %s", result || "(empty)");
|
|
2518
|
-
} catch (err) {
|
|
2519
|
-
this._log.error({ err }, "collect_dtmf error");
|
|
2520
|
-
result = `Error: ${err}`;
|
|
2549
|
+
if (!this._tools || !this._tools.has(funcName)) {
|
|
2550
|
+
this._log.error("Unknown tool: %s", funcName);
|
|
2551
|
+
await this._waitForResponseDone();
|
|
2552
|
+
this._send({
|
|
2553
|
+
type: "conversation.item.create",
|
|
2554
|
+
item: {
|
|
2555
|
+
type: "function_call_output",
|
|
2556
|
+
call_id: callId,
|
|
2557
|
+
output: JSON.stringify({ error: `Unknown tool: ${funcName}` })
|
|
2521
2558
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2559
|
+
});
|
|
2560
|
+
this._send({ type: "response.create" });
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
let result;
|
|
2564
|
+
try {
|
|
2565
|
+
const args = JSON.parse(item["arguments"] ?? "{}");
|
|
2566
|
+
this._call?.recordToolCall();
|
|
2567
|
+
result = await this._tools.call(funcName, args);
|
|
2568
|
+
} catch (err) {
|
|
2569
|
+
this._log.error({ err }, "Tool call failed: %s", funcName);
|
|
2570
|
+
if (err instanceof Error) {
|
|
2571
|
+
this._call?.recordToolError(err);
|
|
2527
2572
|
}
|
|
2528
|
-
|
|
2573
|
+
result = `Error: ${err}`;
|
|
2529
2574
|
}
|
|
2530
|
-
if (
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2575
|
+
if (controller.signal.aborted) {
|
|
2576
|
+
this._log.info("Tool call cancelled (user interrupted): %s", funcName);
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
const resultStr = typeof result === "string" ? result : JSON.stringify(result);
|
|
2580
|
+
await this._waitForResponseDone();
|
|
2581
|
+
this._send({
|
|
2582
|
+
type: "conversation.item.create",
|
|
2583
|
+
item: {
|
|
2584
|
+
type: "function_call_output",
|
|
2585
|
+
call_id: callId,
|
|
2586
|
+
output: resultStr
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
this._log.info("[ToolResult] %s call_id=%s len=%d", funcName, callId, resultStr.length);
|
|
2590
|
+
this._send({ type: "response.create" });
|
|
2591
|
+
} finally {
|
|
2592
|
+
this._pendingToolCalls.delete(callId);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
_waitForResponseDone() {
|
|
2596
|
+
if (!this._responseInProgress) return Promise.resolve();
|
|
2597
|
+
return new Promise((resolve) => {
|
|
2598
|
+
this._onResponseDone = resolve;
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
_send(data) {
|
|
2602
|
+
if (this._ws && this._ws.readyState === 1 && !this._closed) {
|
|
2603
|
+
this._ws.send(JSON.stringify(data));
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
|
|
2608
|
+
// src/agent/pipeline/realtime/gemini-realtime.ts
|
|
2609
|
+
function resolveRef(ref, defs) {
|
|
2610
|
+
const parts = ref.replace(/^#\//, "").split("/");
|
|
2611
|
+
let result = defs;
|
|
2612
|
+
for (const part of parts) {
|
|
2613
|
+
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
2614
|
+
result = result[part];
|
|
2615
|
+
} else {
|
|
2616
|
+
return {};
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
return typeof result === "object" && result !== null && !Array.isArray(result) ? result : {};
|
|
2620
|
+
}
|
|
2621
|
+
function sanitizeSchemaForGemini(schema, defs, depth = 0) {
|
|
2622
|
+
if (depth > 15) return { type: "object", properties: {} };
|
|
2623
|
+
if (!schema || typeof schema !== "object") return { type: "object", properties: {} };
|
|
2624
|
+
if (defs === void 0) {
|
|
2625
|
+
defs = schema["$defs"] ?? schema["definitions"] ?? {};
|
|
2626
|
+
}
|
|
2627
|
+
if (typeof schema["$ref"] === "string") {
|
|
2628
|
+
const resolved = resolveRef(schema["$ref"], { $defs: defs, definitions: defs });
|
|
2629
|
+
if (resolved && Object.keys(resolved).length > 0) {
|
|
2630
|
+
return sanitizeSchemaForGemini(resolved, defs, depth + 1);
|
|
2631
|
+
}
|
|
2632
|
+
return { type: "object", properties: {} };
|
|
2633
|
+
}
|
|
2634
|
+
for (const comboKey of ["oneOf", "anyOf", "allOf"]) {
|
|
2635
|
+
const variants = schema[comboKey];
|
|
2636
|
+
if (Array.isArray(variants) && variants.length > 0) {
|
|
2637
|
+
for (const v of variants) {
|
|
2638
|
+
if (v && typeof v === "object") {
|
|
2639
|
+
const resolved = sanitizeSchemaForGemini(v, defs, depth + 1);
|
|
2640
|
+
if (resolved["type"] === "object" && resolved["properties"]) {
|
|
2641
|
+
return resolved;
|
|
2541
2642
|
}
|
|
2542
|
-
responses.push({ id: fcId, name, response: { result } });
|
|
2543
2643
|
}
|
|
2544
|
-
continue;
|
|
2545
|
-
}
|
|
2546
|
-
if (!this._tools || !this._tools.has(name)) {
|
|
2547
|
-
this._log.error("Unknown tool: %s", name);
|
|
2548
|
-
responses.push({ id: fcId, name, response: { error: `Unknown tool: ${name}` } });
|
|
2549
|
-
continue;
|
|
2550
2644
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
this._log.info("Tool result: %s -> %s", name, resultStr.substring(0, 200));
|
|
2555
|
-
responses.push({
|
|
2556
|
-
id: fcId,
|
|
2557
|
-
name,
|
|
2558
|
-
response: { result: resultStr }
|
|
2559
|
-
});
|
|
2560
|
-
} catch (err) {
|
|
2561
|
-
this._log.error({ err }, "Tool call failed: %s", name);
|
|
2562
|
-
responses.push({
|
|
2563
|
-
id: fcId,
|
|
2564
|
-
name,
|
|
2565
|
-
response: { error: String(err) }
|
|
2566
|
-
});
|
|
2645
|
+
const first = variants[0];
|
|
2646
|
+
if (first && typeof first === "object") {
|
|
2647
|
+
return sanitizeSchemaForGemini(first, defs, depth + 1);
|
|
2567
2648
|
}
|
|
2568
2649
|
}
|
|
2569
|
-
if (responses.length > 0 && this._session) {
|
|
2570
|
-
this._log.debug("Sending %d tool response(s)", responses.length);
|
|
2571
|
-
this._session.sendToolResponse({
|
|
2572
|
-
functionResponses: responses
|
|
2573
|
-
});
|
|
2574
|
-
}
|
|
2575
|
-
this._toolCallInProgress = false;
|
|
2576
2650
|
}
|
|
2577
|
-
};
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
description: "\uC0AC\uC6A9\uC790\uB85C\uBD80\uD130 DTMF(\uC804\uD654 \uD0A4\uD328\uB4DC) \uC785\uB825\uC744 \uC218\uC9D1\uD569\uB2C8\uB2E4.",
|
|
2583
|
-
parameters: {
|
|
2584
|
-
properties: {
|
|
2585
|
-
max_digits: { type: "integer", description: "\uC218\uC9D1\uD560 \uCD5C\uB300 \uC790\uB9BF\uC218" },
|
|
2586
|
-
finish_on_key: { type: "string", description: "\uC785\uB825 \uC885\uB8CC \uD0A4 (\uAE30\uBCF8: #)" },
|
|
2587
|
-
timeout: { type: "integer", description: "\uC785\uB825 \uB300\uAE30 \uC2DC\uAC04(\uCD08, \uAE30\uBCF8: 5)" }
|
|
2588
|
-
},
|
|
2589
|
-
required: ["max_digits"]
|
|
2590
|
-
}
|
|
2651
|
+
const result = {};
|
|
2652
|
+
let schemaType = schema["type"];
|
|
2653
|
+
if (Array.isArray(schemaType)) {
|
|
2654
|
+
const nonNull = schemaType.filter((t) => t !== "null");
|
|
2655
|
+
schemaType = nonNull[0] ?? "string";
|
|
2591
2656
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2657
|
+
if (schemaType) result["type"] = schemaType;
|
|
2658
|
+
if (schema["description"]) result["description"] = schema["description"];
|
|
2659
|
+
if (schema["enum"]) result["enum"] = schema["enum"];
|
|
2660
|
+
if (schema["required"]) result["required"] = schema["required"];
|
|
2661
|
+
if (schema["properties"] && typeof schema["properties"] === "object") {
|
|
2662
|
+
const props = {};
|
|
2663
|
+
for (const [key, val] of Object.entries(schema["properties"])) {
|
|
2664
|
+
if (val && typeof val === "object") {
|
|
2665
|
+
props[key] = sanitizeSchemaForGemini(val, defs, depth + 1);
|
|
2666
|
+
}
|
|
2601
2667
|
}
|
|
2668
|
+
result["properties"] = props;
|
|
2602
2669
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2670
|
+
if (schema["items"] && typeof schema["items"] === "object" && !Array.isArray(schema["items"])) {
|
|
2671
|
+
result["items"] = sanitizeSchemaForGemini(
|
|
2672
|
+
schema["items"],
|
|
2673
|
+
defs,
|
|
2674
|
+
depth + 1
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
if (!result["type"] && result["properties"]) result["type"] = "object";
|
|
2678
|
+
if (result["type"] === "object" && !result["properties"]) result["properties"] = {};
|
|
2679
|
+
return result;
|
|
2680
|
+
}
|
|
2681
|
+
var GeminiRealtime = class {
|
|
2682
|
+
_apiKey;
|
|
2608
2683
|
_systemPrompt;
|
|
2609
|
-
|
|
2684
|
+
_model;
|
|
2685
|
+
_voice;
|
|
2610
2686
|
_language;
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
_callSession = null;
|
|
2687
|
+
_greeting;
|
|
2688
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2689
|
+
_session = null;
|
|
2690
|
+
_call = null;
|
|
2616
2691
|
_tools = null;
|
|
2617
2692
|
_recorder = null;
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
_speaking = false;
|
|
2693
|
+
_closed = false;
|
|
2694
|
+
_sentAudioChunks = 0;
|
|
2695
|
+
_audioRemainder = Buffer.alloc(0);
|
|
2622
2696
|
_builtinTools = null;
|
|
2697
|
+
_toolCallInProgress = false;
|
|
2623
2698
|
_log = NOOP_LOGGER;
|
|
2624
|
-
constructor(options) {
|
|
2625
|
-
this.
|
|
2626
|
-
this.
|
|
2627
|
-
this.
|
|
2628
|
-
this.
|
|
2629
|
-
this._greeting = options.greeting ?? true;
|
|
2699
|
+
constructor(options = {}) {
|
|
2700
|
+
this._apiKey = options.apiKey ?? process.env["GOOGLE_API_KEY"] ?? "";
|
|
2701
|
+
this._systemPrompt = options.systemPrompt ?? "";
|
|
2702
|
+
this._model = options.model ?? "gemini-2.5-flash-native-audio-preview-12-2025";
|
|
2703
|
+
this._voice = options.voice ?? "Kore";
|
|
2630
2704
|
this._language = options.language ?? "ko";
|
|
2631
|
-
this.
|
|
2632
|
-
this._maxTokens = options.maxTokens;
|
|
2633
|
-
this._sampleRate = options.sampleRate ?? 8e3;
|
|
2634
|
-
this._interruptOnSpeech = options.interruptOnSpeech ?? true;
|
|
2635
|
-
if (options.toolRegistry) this._tools = options.toolRegistry;
|
|
2636
|
-
if (options.recorder) this._recorder = options.recorder;
|
|
2705
|
+
this._greeting = options.greeting ?? true;
|
|
2637
2706
|
}
|
|
2707
|
+
/** Inject per-call ToolRegistry. */
|
|
2638
2708
|
setToolRegistry(registry) {
|
|
2639
2709
|
this._tools = registry;
|
|
2640
2710
|
}
|
|
2711
|
+
/** Inject per-call AudioRecorder. */
|
|
2641
2712
|
setRecorder(recorder) {
|
|
2642
2713
|
this._recorder = recorder;
|
|
2643
2714
|
}
|
|
@@ -2646,239 +2717,266 @@ var PipelineSession = class {
|
|
|
2646
2717
|
}
|
|
2647
2718
|
setLogger(logger) {
|
|
2648
2719
|
this._log = logger;
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
this.
|
|
2654
|
-
|
|
2720
|
+
}
|
|
2721
|
+
getTelemetry() {
|
|
2722
|
+
return {
|
|
2723
|
+
sessionType: "gemini_realtime",
|
|
2724
|
+
llm: { provider: "gemini", model: this._model },
|
|
2725
|
+
stt: null,
|
|
2726
|
+
tts: null,
|
|
2727
|
+
voice: this._voice,
|
|
2728
|
+
language: this._language,
|
|
2729
|
+
greetingEnabled: this._greeting,
|
|
2730
|
+
recordingEnabled: !!this._recorder,
|
|
2731
|
+
toolCount: this._tools?.size ?? 0,
|
|
2732
|
+
mcpServerCount: 0,
|
|
2733
|
+
builtinTools: []
|
|
2734
|
+
};
|
|
2655
2735
|
}
|
|
2656
2736
|
async start(callSession, tools) {
|
|
2657
|
-
this.
|
|
2658
|
-
this._tools = tools
|
|
2659
|
-
this.
|
|
2660
|
-
this.
|
|
2661
|
-
this.
|
|
2662
|
-
if (this.
|
|
2663
|
-
|
|
2664
|
-
role: "system",
|
|
2665
|
-
content: this._systemPrompt
|
|
2666
|
-
});
|
|
2737
|
+
this._call = callSession;
|
|
2738
|
+
if (tools) this._tools = tools;
|
|
2739
|
+
this._closed = false;
|
|
2740
|
+
this._sentAudioChunks = 0;
|
|
2741
|
+
this._audioRemainder = Buffer.alloc(0);
|
|
2742
|
+
if (!this._apiKey) {
|
|
2743
|
+
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey option.");
|
|
2667
2744
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2745
|
+
const { GoogleGenAI } = await import('@google/genai/node');
|
|
2746
|
+
const client = new GoogleGenAI({ apiKey: this._apiKey });
|
|
2747
|
+
const config = {
|
|
2748
|
+
responseModalities: ["AUDIO"],
|
|
2749
|
+
speechConfig: {
|
|
2750
|
+
voiceConfig: {
|
|
2751
|
+
prebuiltVoiceConfig: {
|
|
2752
|
+
voiceName: this._voice
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
},
|
|
2756
|
+
inputAudioTranscription: {},
|
|
2757
|
+
outputAudioTranscription: {}
|
|
2758
|
+
};
|
|
2759
|
+
if (this._systemPrompt) {
|
|
2760
|
+
config["systemInstruction"] = this._systemPrompt;
|
|
2672
2761
|
}
|
|
2673
|
-
this.
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
}
|
|
2677
|
-
feedAudio(audio) {
|
|
2678
|
-
if (this._running) {
|
|
2679
|
-
this._audioBuffer.push(audio);
|
|
2762
|
+
const toolSchemas = this._buildToolSchemas();
|
|
2763
|
+
if (toolSchemas.length > 0) {
|
|
2764
|
+
config["tools"] = [{ functionDeclarations: toolSchemas }];
|
|
2680
2765
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
this._audioBuffer = [];
|
|
2693
|
-
}
|
|
2694
|
-
async _runSttLoop() {
|
|
2695
|
-
const audioStream = this._createAudioStream();
|
|
2696
|
-
for await (const event of this._stt.transcribe(audioStream, {
|
|
2697
|
-
sampleRate: this._sampleRate
|
|
2698
|
-
})) {
|
|
2699
|
-
if (!this._running) break;
|
|
2700
|
-
if (event.type === "interim" && this._speaking && this._interruptOnSpeech) {
|
|
2701
|
-
this._speaking = false;
|
|
2702
|
-
if (this._callSession) {
|
|
2703
|
-
this._callSession.clearAudio();
|
|
2766
|
+
this._session = await client.live.connect({
|
|
2767
|
+
model: this._model,
|
|
2768
|
+
config,
|
|
2769
|
+
callbacks: {
|
|
2770
|
+
onmessage: (msg) => this._handleMessage(msg),
|
|
2771
|
+
onerror: (err) => {
|
|
2772
|
+
this._log.error({ err }, "Gemini SDK error");
|
|
2773
|
+
},
|
|
2774
|
+
onclose: (ev) => {
|
|
2775
|
+
this._log.info({ code: ev?.code ?? "unknown" }, "Gemini connection closed");
|
|
2776
|
+
this._closed = true;
|
|
2704
2777
|
}
|
|
2705
|
-
this._log.info('Barge-in: "%s"', event.transcript.substring(0, 30));
|
|
2706
|
-
}
|
|
2707
|
-
if (event.type === "final" && event.transcript.trim()) {
|
|
2708
|
-
this._log.info("STT: %s", event.transcript);
|
|
2709
|
-
await this._handleUserSpeech(event.transcript);
|
|
2710
2778
|
}
|
|
2779
|
+
});
|
|
2780
|
+
if (this._greeting) {
|
|
2781
|
+
this._session.sendClientContent({
|
|
2782
|
+
turns: [
|
|
2783
|
+
{
|
|
2784
|
+
role: "user",
|
|
2785
|
+
parts: [{ text: "\uC778\uC0AC\uD574 \uC8FC\uC138\uC694." }]
|
|
2786
|
+
}
|
|
2787
|
+
],
|
|
2788
|
+
turnComplete: true
|
|
2789
|
+
});
|
|
2711
2790
|
}
|
|
2712
2791
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
const pcm16k = resamplePcm16(pcm8k, 8e3, 16e3);
|
|
2719
|
-
yield pcm16k;
|
|
2720
|
-
} else {
|
|
2721
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
2792
|
+
feedAudio(audio) {
|
|
2793
|
+
if (this._session && !this._closed && !this._toolCallInProgress) {
|
|
2794
|
+
const pcm8k = ulawToPcm16(audio);
|
|
2795
|
+
if (this._recorder) {
|
|
2796
|
+
this._recorder.writeInbound(pcm8k);
|
|
2722
2797
|
}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
async _handleUserSpeech(transcript) {
|
|
2730
|
-
this._conversation.push({ role: "user", content: transcript });
|
|
2731
|
-
await this._respond();
|
|
2732
|
-
}
|
|
2733
|
-
_buildEffectiveTools() {
|
|
2734
|
-
const includeCollectDtmf = !this._builtinTools || this._builtinTools.has("collect_dtmf" /* COLLECT_DTMF */);
|
|
2735
|
-
const includeSendDtmf = !this._builtinTools || this._builtinTools.has("send_dtmf" /* SEND_DTMF */);
|
|
2736
|
-
if (!includeCollectDtmf && !includeSendDtmf) return this._tools ?? void 0;
|
|
2737
|
-
const base = this._tools ? this._tools.fork() : new ToolRegistry();
|
|
2738
|
-
if (includeCollectDtmf) {
|
|
2739
|
-
base.register({
|
|
2740
|
-
name: "collect_dtmf",
|
|
2741
|
-
description: COLLECT_DTMF_TOOL3.function.description,
|
|
2742
|
-
parameters: COLLECT_DTMF_TOOL3.function.parameters.properties,
|
|
2743
|
-
required: COLLECT_DTMF_TOOL3.function.parameters.required,
|
|
2744
|
-
handler: async () => ""
|
|
2798
|
+
const pcm16k = resamplePcm16(pcm8k, 8e3, 16e3);
|
|
2799
|
+
this._session.sendRealtimeInput({
|
|
2800
|
+
audio: {
|
|
2801
|
+
data: Buffer.from(pcm16k).toString("base64"),
|
|
2802
|
+
mimeType: "audio/pcm;rate=16000"
|
|
2803
|
+
}
|
|
2745
2804
|
});
|
|
2746
2805
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
handler: async () => ""
|
|
2806
|
+
}
|
|
2807
|
+
async feedDtmf(digits) {
|
|
2808
|
+
if (this._session) {
|
|
2809
|
+
this._session.sendClientContent({
|
|
2810
|
+
turns: [{ role: "user", parts: [{ text: `[DTMF \uC785\uB825: ${digits}]` }] }],
|
|
2811
|
+
turnComplete: true
|
|
2754
2812
|
});
|
|
2755
2813
|
}
|
|
2756
|
-
return base;
|
|
2757
2814
|
}
|
|
2758
|
-
async
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
temperature: this._temperature,
|
|
2765
|
-
maxTokens: this._maxTokens
|
|
2766
|
-
});
|
|
2767
|
-
for await (const chunk of llmStream) {
|
|
2768
|
-
if (!this._running) break;
|
|
2769
|
-
if (chunk.type === "text" && chunk.text) {
|
|
2770
|
-
textChunks.push(chunk.text);
|
|
2771
|
-
fullResponse += chunk.text;
|
|
2772
|
-
} else if (chunk.type === "tool_call" && chunk.toolCall) {
|
|
2773
|
-
await this._handleToolCall(chunk);
|
|
2815
|
+
async stop() {
|
|
2816
|
+
this._closed = true;
|
|
2817
|
+
if (this._session) {
|
|
2818
|
+
try {
|
|
2819
|
+
this._session.close();
|
|
2820
|
+
} catch {
|
|
2774
2821
|
}
|
|
2775
|
-
|
|
2776
|
-
if (fullResponse.trim()) {
|
|
2777
|
-
this._log.info("Assistant: %s", fullResponse.substring(0, 100));
|
|
2778
|
-
this._conversation.push({ role: "assistant", content: fullResponse });
|
|
2779
|
-
await this._synthesizeAndSend(fullResponse);
|
|
2822
|
+
this._session = null;
|
|
2780
2823
|
}
|
|
2781
2824
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2825
|
+
_buildToolSchemas() {
|
|
2826
|
+
const toolDefs = this._tools ? this._tools.toOpenAITools().map((t) => ({
|
|
2827
|
+
name: t.function.name,
|
|
2828
|
+
description: t.function.description,
|
|
2829
|
+
parameters: sanitizeSchemaForGemini(
|
|
2830
|
+
t.function.parameters ?? { type: "object", properties: {} }
|
|
2831
|
+
)
|
|
2832
|
+
})) : [];
|
|
2833
|
+
toolDefs.push(...getBuiltinToolSchemas(this._builtinTools, "gemini"));
|
|
2834
|
+
return toolDefs;
|
|
2835
|
+
}
|
|
2836
|
+
_handleMessage(msg) {
|
|
2837
|
+
if (!this._call) return;
|
|
2838
|
+
const serverContent = msg.serverContent;
|
|
2839
|
+
if (serverContent) {
|
|
2840
|
+
const modelTurn = serverContent.modelTurn;
|
|
2841
|
+
if (modelTurn) {
|
|
2842
|
+
for (const part of modelTurn.parts ?? []) {
|
|
2843
|
+
const inlineData = part.inlineData;
|
|
2844
|
+
if (inlineData?.data) {
|
|
2845
|
+
const mimeType = inlineData.mimeType ?? "";
|
|
2846
|
+
if (mimeType.includes("audio")) {
|
|
2847
|
+
this._handleAudioData(inlineData.data);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2797
2850
|
}
|
|
2798
|
-
this._conversation.push({ role: "tool", content: result2 || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)", tool_call_id: id, name });
|
|
2799
|
-
await this._respond();
|
|
2800
|
-
return;
|
|
2801
2851
|
}
|
|
2802
|
-
if (
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
await this._callSession.sendDtmfSequence(args["digits"] ?? "");
|
|
2806
|
-
result2 = "sent";
|
|
2807
|
-
} catch (err) {
|
|
2808
|
-
result2 = `Error: ${err}`;
|
|
2809
|
-
}
|
|
2810
|
-
this._conversation.push({ role: "tool", content: result2, tool_call_id: id, name });
|
|
2811
|
-
await this._respond();
|
|
2812
|
-
return;
|
|
2852
|
+
if (serverContent.turnComplete) {
|
|
2853
|
+
this._log.debug("Turn complete");
|
|
2854
|
+
this._flushAudioRemainder();
|
|
2813
2855
|
}
|
|
2814
|
-
if (
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
content: ""
|
|
2819
|
-
// Tool call info stored in the message flow
|
|
2820
|
-
});
|
|
2821
|
-
this._conversation.push({
|
|
2822
|
-
role: "tool",
|
|
2823
|
-
content: typeof result === "string" ? result : JSON.stringify(result),
|
|
2824
|
-
tool_call_id: id,
|
|
2825
|
-
name
|
|
2826
|
-
});
|
|
2827
|
-
const effectiveTools = this._buildEffectiveTools();
|
|
2828
|
-
let followUpText = "";
|
|
2829
|
-
const followUpStream = this._llm.generate(this._conversation, {
|
|
2830
|
-
tools: effectiveTools,
|
|
2831
|
-
temperature: this._temperature,
|
|
2832
|
-
maxTokens: this._maxTokens
|
|
2833
|
-
});
|
|
2834
|
-
for await (const followChunk of followUpStream) {
|
|
2835
|
-
if (!this._running) break;
|
|
2836
|
-
if (followChunk.type === "text" && followChunk.text) {
|
|
2837
|
-
followUpText += followChunk.text;
|
|
2856
|
+
if (serverContent.interrupted) {
|
|
2857
|
+
this._log.info("Barge-in detected");
|
|
2858
|
+
if (this._call) {
|
|
2859
|
+
this._call.clearAudio();
|
|
2838
2860
|
}
|
|
2861
|
+
this._sentAudioChunks = 0;
|
|
2862
|
+
this._audioRemainder = Buffer.alloc(0);
|
|
2839
2863
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2864
|
+
const inputText = serverContent.inputTranscription?.text;
|
|
2865
|
+
if (inputText && this._call) {
|
|
2866
|
+
this._log.info("User: %s", inputText);
|
|
2867
|
+
this._call._emit("transcript", "user", inputText);
|
|
2843
2868
|
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2869
|
+
const outputText = serverContent.outputTranscription?.text;
|
|
2870
|
+
if (outputText && this._call) {
|
|
2871
|
+
this._log.info("Assistant: %s", outputText);
|
|
2872
|
+
this._call._emit("transcript", "assistant", outputText);
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
if (msg.toolCall) {
|
|
2876
|
+
this._handleToolCall(msg.toolCall);
|
|
2877
|
+
}
|
|
2878
|
+
const toolCancellation = msg["toolCallCancellation"];
|
|
2879
|
+
if (toolCancellation) {
|
|
2880
|
+
this._log.info({ ids: toolCancellation.ids }, "Tool call cancelled");
|
|
2846
2881
|
}
|
|
2847
2882
|
}
|
|
2848
|
-
|
|
2849
|
-
if (!this.
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2883
|
+
_handleAudioData(b64Data) {
|
|
2884
|
+
if (!this._call) return;
|
|
2885
|
+
const pcm24k = Buffer.from(b64Data, "base64");
|
|
2886
|
+
if (this._recorder) {
|
|
2887
|
+
this._recorder.writeOutbound(resamplePcm16(pcm24k, 24e3, 8e3));
|
|
2888
|
+
}
|
|
2889
|
+
const pcm8k = resamplePcm16(pcm24k, 24e3, 8e3);
|
|
2890
|
+
const ulaw = pcm16ToUlaw(pcm8k);
|
|
2891
|
+
const combined = Buffer.concat([this._audioRemainder, ulaw]);
|
|
2892
|
+
const chunkSize = 160;
|
|
2893
|
+
const fullEnd = Math.floor(combined.length / chunkSize) * chunkSize;
|
|
2894
|
+
if (fullEnd > 0) {
|
|
2895
|
+
this._call.sendAudio(combined.subarray(0, fullEnd));
|
|
2896
|
+
this._sentAudioChunks += fullEnd / chunkSize;
|
|
2897
|
+
}
|
|
2898
|
+
this._audioRemainder = combined.subarray(fullEnd);
|
|
2899
|
+
}
|
|
2900
|
+
_flushAudioRemainder() {
|
|
2901
|
+
if (this._audioRemainder.length > 0 && this._call) {
|
|
2902
|
+
const padded = Buffer.concat([
|
|
2903
|
+
this._audioRemainder,
|
|
2904
|
+
Buffer.alloc(160 - this._audioRemainder.length, 255)
|
|
2905
|
+
]);
|
|
2906
|
+
this._call.sendAudio(padded);
|
|
2907
|
+
this._sentAudioChunks++;
|
|
2908
|
+
this._audioRemainder = Buffer.alloc(0);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
async _handleToolCall(toolCall) {
|
|
2912
|
+
const functionCalls = toolCall.functionCalls;
|
|
2913
|
+
if (!functionCalls) return;
|
|
2914
|
+
this._toolCallInProgress = true;
|
|
2915
|
+
const responses = [];
|
|
2916
|
+
for (const fc of functionCalls) {
|
|
2917
|
+
const name = fc.name ?? "";
|
|
2918
|
+
const fcId = fc.id ?? "";
|
|
2919
|
+
const args = fc.args ?? {};
|
|
2920
|
+
this._log.info({ tool: name, args }, "Tool call: %s", name);
|
|
2921
|
+
if (BUILTIN_TOOL_NAMES.has(name) && this._call) {
|
|
2922
|
+
const result = await executeBuiltinTool(name, args, this._call);
|
|
2923
|
+
if (result !== null) {
|
|
2924
|
+
if (name === "hang_up") {
|
|
2925
|
+
this._log.info("hang_up: ending call");
|
|
2926
|
+
return;
|
|
2866
2927
|
}
|
|
2867
|
-
this.
|
|
2928
|
+
this._log.info("Builtin tool result: %s -> %s", name, result);
|
|
2929
|
+
responses.push({ id: fcId, name, response: { result } });
|
|
2930
|
+
continue;
|
|
2868
2931
|
}
|
|
2869
2932
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2933
|
+
if (!this._tools || !this._tools.has(name)) {
|
|
2934
|
+
this._log.error("Unknown tool: %s", name);
|
|
2935
|
+
responses.push({ id: fcId, name, response: { error: `Unknown tool: ${name}` } });
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
try {
|
|
2939
|
+
this._call?.recordToolCall();
|
|
2940
|
+
const result = await this._tools.call(name, args);
|
|
2941
|
+
const resultStr = typeof result === "string" ? result : JSON.stringify(result);
|
|
2942
|
+
this._log.info("Tool result: %s -> %s", name, resultStr.substring(0, 200));
|
|
2943
|
+
responses.push({
|
|
2944
|
+
id: fcId,
|
|
2945
|
+
name,
|
|
2946
|
+
response: { result: resultStr }
|
|
2947
|
+
});
|
|
2948
|
+
} catch (err) {
|
|
2949
|
+
this._log.error({ err }, "Tool call failed: %s", name);
|
|
2950
|
+
if (err instanceof Error) {
|
|
2951
|
+
this._call?.recordToolError(err);
|
|
2952
|
+
}
|
|
2953
|
+
responses.push({
|
|
2954
|
+
id: fcId,
|
|
2955
|
+
name,
|
|
2956
|
+
response: { error: String(err) }
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
if (responses.length > 0 && this._session) {
|
|
2961
|
+
this._log.debug("Sending %d tool response(s)", responses.length);
|
|
2962
|
+
this._session.sendToolResponse({
|
|
2963
|
+
functionResponses: responses
|
|
2964
|
+
});
|
|
2874
2965
|
}
|
|
2966
|
+
this._toolCallInProgress = false;
|
|
2875
2967
|
}
|
|
2876
2968
|
};
|
|
2877
2969
|
|
|
2878
|
-
// src/agent/pipeline/deepgram-stt.ts
|
|
2970
|
+
// src/agent/pipeline/stt/deepgram-stt.ts
|
|
2879
2971
|
var DeepgramSTT = class {
|
|
2880
2972
|
_options;
|
|
2881
2973
|
_log = NOOP_LOGGER;
|
|
2974
|
+
get provider() {
|
|
2975
|
+
return "deepgram";
|
|
2976
|
+
}
|
|
2977
|
+
get model() {
|
|
2978
|
+
return this._options.model ?? "nova-3";
|
|
2979
|
+
}
|
|
2882
2980
|
setLogger(logger) {
|
|
2883
2981
|
this._log = logger;
|
|
2884
2982
|
}
|
|
@@ -2996,10 +3094,19 @@ var DeepgramSTT = class {
|
|
|
2996
3094
|
}
|
|
2997
3095
|
};
|
|
2998
3096
|
|
|
2999
|
-
// src/agent/pipeline/elevenlabs-tts.ts
|
|
3097
|
+
// src/agent/pipeline/tts/elevenlabs-tts.ts
|
|
3000
3098
|
var ElevenLabsTTS = class {
|
|
3001
3099
|
_options;
|
|
3002
3100
|
_log = NOOP_LOGGER;
|
|
3101
|
+
get provider() {
|
|
3102
|
+
return "elevenlabs";
|
|
3103
|
+
}
|
|
3104
|
+
get model() {
|
|
3105
|
+
return this._options.model ?? "eleven_flash_v2_5";
|
|
3106
|
+
}
|
|
3107
|
+
get voiceId() {
|
|
3108
|
+
return this._options.voiceId ?? "EXAVITQu4vr4xnSDxMaL";
|
|
3109
|
+
}
|
|
3003
3110
|
setLogger(logger) {
|
|
3004
3111
|
this._log = logger;
|
|
3005
3112
|
}
|
|
@@ -3149,9 +3256,15 @@ var ElevenLabsTTS = class {
|
|
|
3149
3256
|
}
|
|
3150
3257
|
};
|
|
3151
3258
|
|
|
3152
|
-
// src/agent/pipeline/openai-llm.ts
|
|
3259
|
+
// src/agent/pipeline/llm/openai-llm.ts
|
|
3153
3260
|
var OpenAILLM = class {
|
|
3154
3261
|
_options;
|
|
3262
|
+
get provider() {
|
|
3263
|
+
return "openai";
|
|
3264
|
+
}
|
|
3265
|
+
get model() {
|
|
3266
|
+
return this._options.model;
|
|
3267
|
+
}
|
|
3155
3268
|
constructor(options = {}) {
|
|
3156
3269
|
this._options = {
|
|
3157
3270
|
model: "gpt-4o-mini",
|
|
@@ -3235,9 +3348,15 @@ var OpenAILLM = class {
|
|
|
3235
3348
|
}
|
|
3236
3349
|
};
|
|
3237
3350
|
|
|
3238
|
-
// src/agent/pipeline/anthropic-llm.ts
|
|
3351
|
+
// src/agent/pipeline/llm/anthropic-llm.ts
|
|
3239
3352
|
var AnthropicLLM = class {
|
|
3240
3353
|
_options;
|
|
3354
|
+
get provider() {
|
|
3355
|
+
return "anthropic";
|
|
3356
|
+
}
|
|
3357
|
+
get model() {
|
|
3358
|
+
return this._options.model;
|
|
3359
|
+
}
|
|
3241
3360
|
constructor(options = {}) {
|
|
3242
3361
|
this._options = {
|
|
3243
3362
|
model: "claude-sonnet-4-6",
|
|
@@ -3340,9 +3459,15 @@ var AnthropicLLM = class {
|
|
|
3340
3459
|
}
|
|
3341
3460
|
};
|
|
3342
3461
|
|
|
3343
|
-
// src/agent/pipeline/gemini-llm.ts
|
|
3462
|
+
// src/agent/pipeline/llm/gemini-llm.ts
|
|
3344
3463
|
var GeminiLLM = class {
|
|
3345
3464
|
_options;
|
|
3465
|
+
get provider() {
|
|
3466
|
+
return "gemini";
|
|
3467
|
+
}
|
|
3468
|
+
get model() {
|
|
3469
|
+
return this._options.model;
|
|
3470
|
+
}
|
|
3346
3471
|
constructor(options = {}) {
|
|
3347
3472
|
this._options = {
|
|
3348
3473
|
model: "gemini-2.5-flash",
|
|
@@ -3424,9 +3549,15 @@ var GeminiLLM = class {
|
|
|
3424
3549
|
}
|
|
3425
3550
|
};
|
|
3426
3551
|
|
|
3427
|
-
// src/agent/pipeline/openai-compat-llm.ts
|
|
3552
|
+
// src/agent/pipeline/llm/openai-compat-llm.ts
|
|
3428
3553
|
var OpenAICompatLLM = class {
|
|
3429
3554
|
_options;
|
|
3555
|
+
get provider() {
|
|
3556
|
+
return "openai_compatible";
|
|
3557
|
+
}
|
|
3558
|
+
get model() {
|
|
3559
|
+
return this._options.model;
|
|
3560
|
+
}
|
|
3430
3561
|
constructor(options) {
|
|
3431
3562
|
this._options = options;
|
|
3432
3563
|
}
|
|
@@ -3507,9 +3638,15 @@ var OpenAICompatLLM = class {
|
|
|
3507
3638
|
}
|
|
3508
3639
|
};
|
|
3509
3640
|
|
|
3510
|
-
// src/agent/pipeline/ollama-llm.ts
|
|
3641
|
+
// src/agent/pipeline/llm/ollama-llm.ts
|
|
3511
3642
|
var OllamaLLM = class {
|
|
3512
3643
|
_inner;
|
|
3644
|
+
get provider() {
|
|
3645
|
+
return "ollama";
|
|
3646
|
+
}
|
|
3647
|
+
get model() {
|
|
3648
|
+
return this._inner.model;
|
|
3649
|
+
}
|
|
3513
3650
|
constructor(options = {}) {
|
|
3514
3651
|
const baseUrl = options.baseUrl ?? process.env["OLLAMA_BASE_URL"] ?? "http://localhost:11434/v1";
|
|
3515
3652
|
this._inner = new OpenAICompatLLM({
|
|
@@ -3525,9 +3662,15 @@ var OllamaLLM = class {
|
|
|
3525
3662
|
}
|
|
3526
3663
|
};
|
|
3527
3664
|
|
|
3528
|
-
// src/agent/pipeline/mistral-llm.ts
|
|
3665
|
+
// src/agent/pipeline/llm/mistral-llm.ts
|
|
3529
3666
|
var MistralLLM = class {
|
|
3530
3667
|
_inner;
|
|
3668
|
+
get provider() {
|
|
3669
|
+
return "mistral";
|
|
3670
|
+
}
|
|
3671
|
+
get model() {
|
|
3672
|
+
return this._inner.model;
|
|
3673
|
+
}
|
|
3531
3674
|
constructor(options = {}) {
|
|
3532
3675
|
this._inner = new OpenAICompatLLM({
|
|
3533
3676
|
apiKey: options.apiKey ?? process.env["MISTRAL_API_KEY"],
|
|
@@ -3542,9 +3685,15 @@ var MistralLLM = class {
|
|
|
3542
3685
|
}
|
|
3543
3686
|
};
|
|
3544
3687
|
|
|
3545
|
-
// src/agent/pipeline/groq-llm.ts
|
|
3688
|
+
// src/agent/pipeline/llm/groq-llm.ts
|
|
3546
3689
|
var GroqLLM = class {
|
|
3547
3690
|
_inner;
|
|
3691
|
+
get provider() {
|
|
3692
|
+
return "groq";
|
|
3693
|
+
}
|
|
3694
|
+
get model() {
|
|
3695
|
+
return this._inner.model;
|
|
3696
|
+
}
|
|
3548
3697
|
constructor(options = {}) {
|
|
3549
3698
|
this._inner = new OpenAICompatLLM({
|
|
3550
3699
|
apiKey: options.apiKey ?? process.env["GROQ_API_KEY"],
|
|
@@ -3559,9 +3708,15 @@ var GroqLLM = class {
|
|
|
3559
3708
|
}
|
|
3560
3709
|
};
|
|
3561
3710
|
|
|
3562
|
-
// src/agent/pipeline/perplexity-llm.ts
|
|
3711
|
+
// src/agent/pipeline/llm/perplexity-llm.ts
|
|
3563
3712
|
var PerplexityLLM = class {
|
|
3564
3713
|
_inner;
|
|
3714
|
+
get provider() {
|
|
3715
|
+
return "perplexity";
|
|
3716
|
+
}
|
|
3717
|
+
get model() {
|
|
3718
|
+
return this._inner.model;
|
|
3719
|
+
}
|
|
3565
3720
|
constructor(options = {}) {
|
|
3566
3721
|
this._inner = new OpenAICompatLLM({
|
|
3567
3722
|
apiKey: options.apiKey ?? process.env["PERPLEXITY_API_KEY"],
|
|
@@ -3576,9 +3731,15 @@ var PerplexityLLM = class {
|
|
|
3576
3731
|
}
|
|
3577
3732
|
};
|
|
3578
3733
|
|
|
3579
|
-
// src/agent/pipeline/together-llm.ts
|
|
3734
|
+
// src/agent/pipeline/llm/together-llm.ts
|
|
3580
3735
|
var TogetherLLM = class {
|
|
3581
3736
|
_inner;
|
|
3737
|
+
get provider() {
|
|
3738
|
+
return "together";
|
|
3739
|
+
}
|
|
3740
|
+
get model() {
|
|
3741
|
+
return this._inner.model;
|
|
3742
|
+
}
|
|
3582
3743
|
constructor(options = {}) {
|
|
3583
3744
|
this._inner = new OpenAICompatLLM({
|
|
3584
3745
|
apiKey: options.apiKey ?? process.env["TOGETHER_API_KEY"],
|
|
@@ -3593,9 +3754,15 @@ var TogetherLLM = class {
|
|
|
3593
3754
|
}
|
|
3594
3755
|
};
|
|
3595
3756
|
|
|
3596
|
-
// src/agent/pipeline/fireworks-llm.ts
|
|
3757
|
+
// src/agent/pipeline/llm/fireworks-llm.ts
|
|
3597
3758
|
var FireworksLLM = class {
|
|
3598
3759
|
_inner;
|
|
3760
|
+
get provider() {
|
|
3761
|
+
return "fireworks";
|
|
3762
|
+
}
|
|
3763
|
+
get model() {
|
|
3764
|
+
return this._inner.model;
|
|
3765
|
+
}
|
|
3599
3766
|
constructor(options = {}) {
|
|
3600
3767
|
this._inner = new OpenAICompatLLM({
|
|
3601
3768
|
apiKey: options.apiKey ?? process.env["FIREWORKS_API_KEY"],
|
|
@@ -3610,9 +3777,15 @@ var FireworksLLM = class {
|
|
|
3610
3777
|
}
|
|
3611
3778
|
};
|
|
3612
3779
|
|
|
3613
|
-
// src/agent/pipeline/deepseek-llm.ts
|
|
3780
|
+
// src/agent/pipeline/llm/deepseek-llm.ts
|
|
3614
3781
|
var DeepSeekLLM = class {
|
|
3615
3782
|
_inner;
|
|
3783
|
+
get provider() {
|
|
3784
|
+
return "deepseek";
|
|
3785
|
+
}
|
|
3786
|
+
get model() {
|
|
3787
|
+
return this._inner.model;
|
|
3788
|
+
}
|
|
3616
3789
|
constructor(options = {}) {
|
|
3617
3790
|
this._inner = new OpenAICompatLLM({
|
|
3618
3791
|
apiKey: options.apiKey ?? process.env["DEEPSEEK_API_KEY"],
|
|
@@ -3627,9 +3800,15 @@ var DeepSeekLLM = class {
|
|
|
3627
3800
|
}
|
|
3628
3801
|
};
|
|
3629
3802
|
|
|
3630
|
-
// src/agent/pipeline/xai-llm.ts
|
|
3803
|
+
// src/agent/pipeline/llm/xai-llm.ts
|
|
3631
3804
|
var XaiLLM = class {
|
|
3632
3805
|
_inner;
|
|
3806
|
+
get provider() {
|
|
3807
|
+
return "xai";
|
|
3808
|
+
}
|
|
3809
|
+
get model() {
|
|
3810
|
+
return this._inner.model;
|
|
3811
|
+
}
|
|
3633
3812
|
constructor(options = {}) {
|
|
3634
3813
|
this._inner = new OpenAICompatLLM({
|
|
3635
3814
|
apiKey: options.apiKey ?? process.env["XAI_API_KEY"],
|
|
@@ -3663,6 +3842,6 @@ function mcpServerHTTP(options) {
|
|
|
3663
3842
|
};
|
|
3664
3843
|
}
|
|
3665
3844
|
|
|
3666
|
-
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 };
|
|
3845
|
+
export { AnthropicLLM, AudioRecorder, BUILTIN_TOOL_NAMES, 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, executeBuiltinTool, functionTool, getBuiltinToolSchemas, getTracingConfig, isBuiltinTool, mcpServerHTTP, mcpServerStdio, pcm16ToUlaw, resamplePcm16, resetTracingConfig, setTracingConfig, ulawToPcm16, zodToToolParams };
|
|
3667
3846
|
//# sourceMappingURL=index.js.map
|
|
3668
3847
|
//# sourceMappingURL=index.js.map
|