@teamlearners/clawops 0.16.5 → 0.17.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/README.md +30 -0
- package/dist/agent/index.cjs +291 -33
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +116 -2
- package/dist/agent/index.d.ts +116 -2
- package/dist/agent/index.js +282 -26
- package/dist/agent/index.js.map +1 -1
- package/dist/{chunk-BHGDB52N.js → chunk-FTZ2FC2Q.js} +3 -3
- package/dist/{chunk-BHGDB52N.js.map → chunk-FTZ2FC2Q.js.map} +1 -1
- package/dist/{chunk-TFYE5DWP.cjs → chunk-ZOSOGI2J.cjs} +3 -3
- package/dist/{chunk-TFYE5DWP.cjs.map → chunk-ZOSOGI2J.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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-
|
|
1
|
+
import { DEFAULT_BASE_URL, AgentError, AgentConnectionError, VERSION } from '../chunk-FTZ2FC2Q.js';
|
|
2
2
|
import pino from 'pino';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
@@ -1732,6 +1732,11 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
1732
1732
|
_holdAudioChunks = null;
|
|
1733
1733
|
_rxGain;
|
|
1734
1734
|
_txGain;
|
|
1735
|
+
_prewarmTasks = /* @__PURE__ */ new Map();
|
|
1736
|
+
_prewarmFailed = /* @__PURE__ */ new Set();
|
|
1737
|
+
/** prewarm 세션이 실제 CallSession 에 attach 완료된 callId. attached 이후의 stop() 은 정상 종료 경로가 책임진다. */
|
|
1738
|
+
_prewarmAttached = /* @__PURE__ */ new Set();
|
|
1739
|
+
_prewarmEnabled;
|
|
1735
1740
|
constructor(options) {
|
|
1736
1741
|
this._apiKey = options.apiKey ?? process.env["CLAWOPS_API_KEY"] ?? "";
|
|
1737
1742
|
this._accountId = options.accountId ?? process.env["CLAWOPS_ACCOUNT_ID"] ?? "";
|
|
@@ -1745,6 +1750,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
1745
1750
|
this._passiveDtmfDebounceMs = options.passiveDtmfDebounceMs ?? 500;
|
|
1746
1751
|
this._rxGain = _ClawOpsAgent._validateGain("rxGain", options.rxGain ?? 1);
|
|
1747
1752
|
this._txGain = _ClawOpsAgent._validateGain("txGain", options.txGain ?? 1);
|
|
1753
|
+
this._prewarmEnabled = options.prewarmEnabled ?? true;
|
|
1748
1754
|
if (options.tracing) {
|
|
1749
1755
|
setTracingConfig(options.tracing);
|
|
1750
1756
|
}
|
|
@@ -1903,6 +1909,9 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
1903
1909
|
callSession.setLogger(this._log);
|
|
1904
1910
|
this._activeSessions.set(callSession.callId, callSession);
|
|
1905
1911
|
this._log.info("Outbound call initiated: %s -> %s (%s)", this._fromNumber, to, callSession.callId);
|
|
1912
|
+
if (this._prewarmEnabled) {
|
|
1913
|
+
this._startPrewarm(callSession.callId);
|
|
1914
|
+
}
|
|
1906
1915
|
return callSession;
|
|
1907
1916
|
}
|
|
1908
1917
|
_handleIncoming(event) {
|
|
@@ -1939,6 +1948,34 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
1939
1948
|
session._markEnded();
|
|
1940
1949
|
this._activeSessions.delete(callId);
|
|
1941
1950
|
}
|
|
1951
|
+
void this._cleanupPrewarm(callId);
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Drop prewarm bookkeeping for a callId. Used on hangup/failure paths.
|
|
1955
|
+
*
|
|
1956
|
+
* prewarm 이 진행 중이거나 완료됐지만 attach 전에 호출되면 LLM WS 가 leak 되므로
|
|
1957
|
+
* race 후 session.stop() 으로 정리한다. (TS 에는 Promise.cancel 이 없어 Python
|
|
1958
|
+
* 의 task.cancel() 등가물은 _session.stop() 호출이다.)
|
|
1959
|
+
*
|
|
1960
|
+
* 이미 attach 된 callId 면 stop() 을 호출하지 않는다 — 정상 종료 경로 (call-session
|
|
1961
|
+
* finally) 가 책임지기 때문이다.
|
|
1962
|
+
*/
|
|
1963
|
+
async _cleanupPrewarm(callId) {
|
|
1964
|
+
const task = this._prewarmTasks.get(callId);
|
|
1965
|
+
const attached = this._prewarmAttached.has(callId);
|
|
1966
|
+
this._prewarmTasks.delete(callId);
|
|
1967
|
+
this._prewarmFailed.delete(callId);
|
|
1968
|
+
this._prewarmAttached.delete(callId);
|
|
1969
|
+
if (!task || attached) return;
|
|
1970
|
+
try {
|
|
1971
|
+
await task;
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
try {
|
|
1975
|
+
await this._session.stop();
|
|
1976
|
+
} catch (err) {
|
|
1977
|
+
this._log.warn({ err, callId }, "prewarm cleanup stop() failed");
|
|
1978
|
+
}
|
|
1942
1979
|
}
|
|
1943
1980
|
_handleOutboundReady(event) {
|
|
1944
1981
|
const callId = event["callId"];
|
|
@@ -1960,16 +1997,60 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
1960
1997
|
}
|
|
1961
1998
|
this._activeSessions.set(callId, session);
|
|
1962
1999
|
}
|
|
2000
|
+
if (this._prewarmEnabled) {
|
|
2001
|
+
this._startPrewarm(callId);
|
|
2002
|
+
}
|
|
1963
2003
|
if (mediaUrl) {
|
|
1964
2004
|
this._log.info("Outbound call answered: %s -> %s (%s)", this._fromNumber, session.toNumber, callId);
|
|
1965
2005
|
this._safeStartCallSession(session, mediaUrl, callId);
|
|
1966
2006
|
}
|
|
1967
2007
|
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Start the LLM session prewarm task for the given callId. Safe to call
|
|
2010
|
+
* multiple times — only the first invocation starts the task. Failures are
|
|
2011
|
+
* recorded in _prewarmFailed so the call-session path can fall back to start().
|
|
2012
|
+
*/
|
|
2013
|
+
_startPrewarm(callId) {
|
|
2014
|
+
if (this._prewarmTasks.has(callId)) return;
|
|
2015
|
+
const sessionHandler = this._session;
|
|
2016
|
+
if (typeof sessionHandler.prewarm !== "function") return;
|
|
2017
|
+
const PREWARM_TIMEOUT_MS = 1e4;
|
|
2018
|
+
const t0 = Date.now();
|
|
2019
|
+
this._log.info(`[PREWARM-T] start call_id=${callId} t=${(t0 / 1e3).toFixed(3)}`);
|
|
2020
|
+
const task = (async () => {
|
|
2021
|
+
let timer;
|
|
2022
|
+
try {
|
|
2023
|
+
const timeout = new Promise((_, reject) => {
|
|
2024
|
+
timer = setTimeout(
|
|
2025
|
+
() => reject(new Error("prewarm timeout")),
|
|
2026
|
+
PREWARM_TIMEOUT_MS
|
|
2027
|
+
);
|
|
2028
|
+
});
|
|
2029
|
+
await Promise.race([sessionHandler.prewarm(), timeout]);
|
|
2030
|
+
const elapsed = Date.now() - t0;
|
|
2031
|
+
this._log.info(`[PREWARM-T] done call_id=${callId} elapsed_ms=${elapsed}`);
|
|
2032
|
+
} catch (err) {
|
|
2033
|
+
const elapsed = Date.now() - t0;
|
|
2034
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2035
|
+
this._log.warn(
|
|
2036
|
+
{ err, callId },
|
|
2037
|
+
`[PREWARM-T] failed call_id=${callId} elapsed_ms=${elapsed} reason=${reason}`
|
|
2038
|
+
);
|
|
2039
|
+
this._prewarmFailed.add(callId);
|
|
2040
|
+
} finally {
|
|
2041
|
+
if (timer) clearTimeout(timer);
|
|
2042
|
+
}
|
|
2043
|
+
})();
|
|
2044
|
+
this._prewarmTasks.set(callId, task);
|
|
2045
|
+
}
|
|
1968
2046
|
_handleRinging(event) {
|
|
1969
2047
|
const callId = event["callId"];
|
|
1970
2048
|
const session = this._activeSessions.get(callId);
|
|
1971
2049
|
if (session) {
|
|
1972
2050
|
this._log.info("Outbound call ringing: %s", callId);
|
|
2051
|
+
if (this._prewarmEnabled) {
|
|
2052
|
+
this._startPrewarm(callId);
|
|
2053
|
+
}
|
|
1973
2054
|
}
|
|
1974
2055
|
}
|
|
1975
2056
|
_handleFailed(event) {
|
|
@@ -1981,6 +2062,7 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
1981
2062
|
session._markEnded();
|
|
1982
2063
|
this._activeSessions.delete(callId);
|
|
1983
2064
|
}
|
|
2065
|
+
void this._cleanupPrewarm(callId);
|
|
1984
2066
|
}
|
|
1985
2067
|
_onDtmfEvent(callSession, digit) {
|
|
1986
2068
|
callSession._emit("dtmf", digit);
|
|
@@ -2133,7 +2215,36 @@ var ClawOpsAgent = class _ClawOpsAgent {
|
|
|
2133
2215
|
try {
|
|
2134
2216
|
await mediaWs.connect(mediaWsUrl, this._apiKey);
|
|
2135
2217
|
this._log.info("Media stream started: %s", session.callId);
|
|
2136
|
-
|
|
2218
|
+
const prewarmTask = this._prewarmTasks.get(session.callId);
|
|
2219
|
+
if (prewarmTask && !this._prewarmFailed.has(session.callId)) {
|
|
2220
|
+
try {
|
|
2221
|
+
await prewarmTask;
|
|
2222
|
+
if (this._prewarmFailed.has(session.callId)) {
|
|
2223
|
+
await sessionHandler.start(session, sessionTools);
|
|
2224
|
+
} else {
|
|
2225
|
+
this._log.info(
|
|
2226
|
+
`[PREWARM-T] attach call_id=${session.callId} t=${(Date.now() / 1e3).toFixed(3)}`
|
|
2227
|
+
);
|
|
2228
|
+
await sessionHandler.attach(session);
|
|
2229
|
+
this._prewarmAttached.add(session.callId);
|
|
2230
|
+
}
|
|
2231
|
+
} catch (err) {
|
|
2232
|
+
this._log.warn(
|
|
2233
|
+
{ err, callId: session.callId },
|
|
2234
|
+
"prewarm/attach failed, falling back to start()"
|
|
2235
|
+
);
|
|
2236
|
+
try {
|
|
2237
|
+
await sessionHandler.stop();
|
|
2238
|
+
} catch {
|
|
2239
|
+
}
|
|
2240
|
+
await sessionHandler.start(session, sessionTools);
|
|
2241
|
+
}
|
|
2242
|
+
} else {
|
|
2243
|
+
await sessionHandler.start(session, sessionTools);
|
|
2244
|
+
}
|
|
2245
|
+
this._prewarmTasks.delete(session.callId);
|
|
2246
|
+
this._prewarmFailed.delete(session.callId);
|
|
2247
|
+
this._prewarmAttached.delete(session.callId);
|
|
2137
2248
|
const telemetry = sessionHandler.getTelemetry?.() ?? null;
|
|
2138
2249
|
if (telemetry) {
|
|
2139
2250
|
telemetry.toolCount = sessionTools?.size ?? 0;
|
|
@@ -2322,6 +2433,86 @@ async function executeBuiltinTool(funcName, args, call) {
|
|
|
2322
2433
|
return null;
|
|
2323
2434
|
}
|
|
2324
2435
|
|
|
2436
|
+
// src/agent/pipeline/buffering-call.ts
|
|
2437
|
+
var MetricsStub = class {
|
|
2438
|
+
recordToolCall() {
|
|
2439
|
+
}
|
|
2440
|
+
recordInterrupt() {
|
|
2441
|
+
}
|
|
2442
|
+
recordToolError() {
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
var BufferingCall = class {
|
|
2446
|
+
_buffer = [];
|
|
2447
|
+
_droppedEvents = {};
|
|
2448
|
+
metrics = new MetricsStub();
|
|
2449
|
+
async sendAudio(chunk) {
|
|
2450
|
+
this._buffer.push(chunk);
|
|
2451
|
+
}
|
|
2452
|
+
/** clearAudio 도 prewarm 동안엔 no-op (드물긴 하지만 안전하게). */
|
|
2453
|
+
clearAudio() {
|
|
2454
|
+
this._buffer = [];
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* transcript 등 lifecycle 이벤트는 prewarm 동안 무시한다. silent drop 은 디버깅이
|
|
2458
|
+
* 어려우므로 event name 별 카운터로 누적하고 attachBuffered() 시 한 번에 로깅한다.
|
|
2459
|
+
*/
|
|
2460
|
+
_emit(...args) {
|
|
2461
|
+
this._recordDropped(args);
|
|
2462
|
+
}
|
|
2463
|
+
async emit(...args) {
|
|
2464
|
+
this._recordDropped(args);
|
|
2465
|
+
}
|
|
2466
|
+
_recordDropped(args) {
|
|
2467
|
+
let eventName = "?";
|
|
2468
|
+
if (args.length > 0 && typeof args[0] === "string") {
|
|
2469
|
+
eventName = args[0];
|
|
2470
|
+
}
|
|
2471
|
+
this._droppedEvents[eventName] = (this._droppedEvents[eventName] ?? 0) + 1;
|
|
2472
|
+
}
|
|
2473
|
+
recordToolCall() {
|
|
2474
|
+
}
|
|
2475
|
+
recordToolError() {
|
|
2476
|
+
}
|
|
2477
|
+
recordFirstResponse() {
|
|
2478
|
+
}
|
|
2479
|
+
recordBargeIn() {
|
|
2480
|
+
}
|
|
2481
|
+
drainBuffer() {
|
|
2482
|
+
const out = this._buffer;
|
|
2483
|
+
this._buffer = [];
|
|
2484
|
+
return out;
|
|
2485
|
+
}
|
|
2486
|
+
drainDroppedEvents() {
|
|
2487
|
+
const out = this._droppedEvents;
|
|
2488
|
+
this._droppedEvents = {};
|
|
2489
|
+
return out;
|
|
2490
|
+
}
|
|
2491
|
+
};
|
|
2492
|
+
function attachBuffered(prev, next) {
|
|
2493
|
+
if (!(prev instanceof BufferingCall)) {
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
const drained = prev.drainBuffer();
|
|
2497
|
+
const flushed = drained.length > 0;
|
|
2498
|
+
const callId = next.callId ?? "?";
|
|
2499
|
+
if (flushed) {
|
|
2500
|
+
console.info(
|
|
2501
|
+
`[PREWARM-T] first-audio call_id=${callId} t=${(process.hrtime.bigint() / 1000000n).toString()} buffered_chunks=${drained.length} source=prebuffer`
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
for (const chunk of drained) {
|
|
2505
|
+
next.sendAudio(chunk);
|
|
2506
|
+
}
|
|
2507
|
+
const dropped = prev.drainDroppedEvents();
|
|
2508
|
+
if (Object.keys(dropped).length > 0) {
|
|
2509
|
+
console.info(
|
|
2510
|
+
`[PREWARM] dropped events during prewarm call_id=${callId} events=${JSON.stringify(dropped)}`
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
return flushed;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2325
2516
|
// src/agent/pipeline/pipeline-session.ts
|
|
2326
2517
|
var PipelineSession = class {
|
|
2327
2518
|
_stt;
|
|
@@ -2398,12 +2589,17 @@ var PipelineSession = class {
|
|
|
2398
2589
|
this._tts.setLogger(logger);
|
|
2399
2590
|
}
|
|
2400
2591
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2592
|
+
/**
|
|
2593
|
+
* Pre-bootstrap conversation state and (optionally) trigger greeting synthesis
|
|
2594
|
+
* before a real CallSession is attached. Audio chunks are buffered into a
|
|
2595
|
+
* BufferingCall until attach() flushes them.
|
|
2596
|
+
*/
|
|
2597
|
+
async prewarm(tools) {
|
|
2598
|
+
if (tools) this._tools = tools;
|
|
2599
|
+
this._callSession = new BufferingCall();
|
|
2404
2600
|
this._running = true;
|
|
2405
|
-
this._log.info("PipelineSession started");
|
|
2406
2601
|
this._conversation = [];
|
|
2602
|
+
this._log.info("PipelineSession prewarmed");
|
|
2407
2603
|
if (this._systemPrompt) {
|
|
2408
2604
|
this._conversation.push({
|
|
2409
2605
|
role: "system",
|
|
@@ -2419,6 +2615,17 @@ var PipelineSession = class {
|
|
|
2419
2615
|
this._log.error({ err }, "STT loop error");
|
|
2420
2616
|
});
|
|
2421
2617
|
}
|
|
2618
|
+
/** Attach a real CallSession to the prewarmed session and flush buffered audio. */
|
|
2619
|
+
async attach(callSession) {
|
|
2620
|
+
const prev = this._callSession;
|
|
2621
|
+
this._callSession = callSession;
|
|
2622
|
+
attachBuffered(prev, callSession);
|
|
2623
|
+
}
|
|
2624
|
+
async start(callSession, tools) {
|
|
2625
|
+
this._tools = tools ?? null;
|
|
2626
|
+
await this.prewarm();
|
|
2627
|
+
await this.attach(callSession);
|
|
2628
|
+
}
|
|
2422
2629
|
feedAudio(audio) {
|
|
2423
2630
|
if (this._running) {
|
|
2424
2631
|
this._audioBuffer.push(audio);
|
|
@@ -2525,7 +2732,7 @@ var PipelineSession = class {
|
|
|
2525
2732
|
const { id, name, arguments: argsStr } = chunk.toolCall;
|
|
2526
2733
|
try {
|
|
2527
2734
|
const args = JSON.parse(argsStr);
|
|
2528
|
-
if (BUILTIN_TOOL_NAMES.has(name) && this._callSession) {
|
|
2735
|
+
if (BUILTIN_TOOL_NAMES.has(name) && this._callSession && !(this._callSession instanceof BufferingCall)) {
|
|
2529
2736
|
const result2 = await executeBuiltinTool(name, args, this._callSession);
|
|
2530
2737
|
if (result2 !== null) {
|
|
2531
2738
|
if (name === "hang_up") return;
|
|
@@ -2536,7 +2743,7 @@ var PipelineSession = class {
|
|
|
2536
2743
|
}
|
|
2537
2744
|
if (!this._tools) return;
|
|
2538
2745
|
this._callSession?.recordToolCall();
|
|
2539
|
-
const player = this._holdAudioChunks && this._callSession ? new HoldAudioPlayer(this._callSession, this._holdAudioChunks) : null;
|
|
2746
|
+
const player = this._holdAudioChunks && this._callSession && !(this._callSession instanceof BufferingCall) ? new HoldAudioPlayer(this._callSession, this._holdAudioChunks) : null;
|
|
2540
2747
|
player?.start();
|
|
2541
2748
|
let result;
|
|
2542
2749
|
try {
|
|
@@ -2679,24 +2886,23 @@ var OpenAIRealtime = class {
|
|
|
2679
2886
|
setHoldAudio(chunks) {
|
|
2680
2887
|
this._holdAudioChunks = chunks;
|
|
2681
2888
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2889
|
+
/** Open WS + session.update + (optional) response.create without a CallSession. */
|
|
2890
|
+
async prewarm(tools) {
|
|
2684
2891
|
if (tools) this._tools = tools;
|
|
2892
|
+
this._call = new BufferingCall();
|
|
2685
2893
|
this._closed = false;
|
|
2686
2894
|
this._playback = null;
|
|
2687
2895
|
this._latestMediaTs = 0;
|
|
2688
2896
|
const { WebSocket } = await import('ws');
|
|
2689
2897
|
const url = `${OPENAI_REALTIME_URL}${this._model}`;
|
|
2690
2898
|
this._ws = new WebSocket(url, {
|
|
2691
|
-
headers: {
|
|
2692
|
-
Authorization: `Bearer ${this._apiKey}`
|
|
2693
|
-
}
|
|
2899
|
+
headers: { Authorization: `Bearer ${this._apiKey}` }
|
|
2694
2900
|
});
|
|
2695
2901
|
return new Promise((resolve, reject) => {
|
|
2696
2902
|
const ws = this._ws;
|
|
2697
2903
|
ws.on("open", () => {
|
|
2698
2904
|
this._sendSessionUpdate();
|
|
2699
|
-
this._log.info("OpenAI Realtime connected");
|
|
2905
|
+
this._log.info("OpenAI Realtime connected (prewarm)");
|
|
2700
2906
|
if (this._greeting) {
|
|
2701
2907
|
this._send({ type: "response.create" });
|
|
2702
2908
|
}
|
|
@@ -2720,6 +2926,17 @@ var OpenAIRealtime = class {
|
|
|
2720
2926
|
});
|
|
2721
2927
|
});
|
|
2722
2928
|
}
|
|
2929
|
+
/** Attach a real CallSession to the prewarmed session and flush buffered audio. */
|
|
2930
|
+
async attach(callSession) {
|
|
2931
|
+
const prev = this._call;
|
|
2932
|
+
this._call = callSession;
|
|
2933
|
+
attachBuffered(prev, callSession);
|
|
2934
|
+
}
|
|
2935
|
+
async start(callSession, tools) {
|
|
2936
|
+
if (tools) this._tools = tools;
|
|
2937
|
+
await this.prewarm();
|
|
2938
|
+
await this.attach(callSession);
|
|
2939
|
+
}
|
|
2723
2940
|
async feedDtmf(digits) {
|
|
2724
2941
|
await this._waitForResponseDone();
|
|
2725
2942
|
this._send({
|
|
@@ -2748,8 +2965,27 @@ var OpenAIRealtime = class {
|
|
|
2748
2965
|
}
|
|
2749
2966
|
this._pendingToolCalls.clear();
|
|
2750
2967
|
if (this._ws) {
|
|
2751
|
-
this._ws
|
|
2968
|
+
const ws = this._ws;
|
|
2752
2969
|
this._ws = null;
|
|
2970
|
+
await new Promise((resolve) => {
|
|
2971
|
+
let done = false;
|
|
2972
|
+
const finish = () => {
|
|
2973
|
+
if (done) return;
|
|
2974
|
+
done = true;
|
|
2975
|
+
resolve();
|
|
2976
|
+
};
|
|
2977
|
+
const timer = setTimeout(finish, 2e3);
|
|
2978
|
+
try {
|
|
2979
|
+
ws.on("close", () => {
|
|
2980
|
+
clearTimeout(timer);
|
|
2981
|
+
finish();
|
|
2982
|
+
});
|
|
2983
|
+
ws.close();
|
|
2984
|
+
} catch {
|
|
2985
|
+
clearTimeout(timer);
|
|
2986
|
+
finish();
|
|
2987
|
+
}
|
|
2988
|
+
});
|
|
2753
2989
|
}
|
|
2754
2990
|
}
|
|
2755
2991
|
_sendSessionUpdate() {
|
|
@@ -2900,7 +3136,7 @@ var OpenAIRealtime = class {
|
|
|
2900
3136
|
const controller = new AbortController();
|
|
2901
3137
|
this._pendingToolCalls.set(callId, controller);
|
|
2902
3138
|
try {
|
|
2903
|
-
if (BUILTIN_TOOL_NAMES.has(funcName) && this._call) {
|
|
3139
|
+
if (BUILTIN_TOOL_NAMES.has(funcName) && this._call && !(this._call instanceof BufferingCall)) {
|
|
2904
3140
|
const args = JSON.parse(item["arguments"] ?? "{}");
|
|
2905
3141
|
const result2 = await executeBuiltinTool(funcName, args, this._call);
|
|
2906
3142
|
if (result2 !== null) {
|
|
@@ -2934,7 +3170,7 @@ var OpenAIRealtime = class {
|
|
|
2934
3170
|
return;
|
|
2935
3171
|
}
|
|
2936
3172
|
let result;
|
|
2937
|
-
const player = this._holdAudioChunks && this._call ? new HoldAudioPlayer(this._call, this._holdAudioChunks) : null;
|
|
3173
|
+
const player = this._holdAudioChunks && this._call && !(this._call instanceof BufferingCall) ? new HoldAudioPlayer(this._call, this._holdAudioChunks) : null;
|
|
2938
3174
|
player?.start();
|
|
2939
3175
|
try {
|
|
2940
3176
|
const args = JSON.parse(item["arguments"] ?? "{}");
|
|
@@ -3119,9 +3355,10 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3119
3355
|
builtinTools: []
|
|
3120
3356
|
};
|
|
3121
3357
|
}
|
|
3122
|
-
|
|
3123
|
-
|
|
3358
|
+
/** Open Live session (no CallSession). Audio deltas accumulate into BufferingCall until attach(). */
|
|
3359
|
+
async prewarm(tools) {
|
|
3124
3360
|
if (tools) this._tools = tools;
|
|
3361
|
+
this._call = new BufferingCall();
|
|
3125
3362
|
this._closed = false;
|
|
3126
3363
|
this._sentAudioChunks = 0;
|
|
3127
3364
|
this._audioRemainder = Buffer.alloc(0);
|
|
@@ -3169,10 +3406,22 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3169
3406
|
}
|
|
3170
3407
|
}
|
|
3171
3408
|
});
|
|
3409
|
+
this._log.info("Gemini Live connected (prewarm)");
|
|
3172
3410
|
if (this._greeting) {
|
|
3173
3411
|
this._session.sendRealtimeInput({ text: "\uC778\uC0AC\uD574 \uC8FC\uC138\uC694." });
|
|
3174
3412
|
}
|
|
3175
3413
|
}
|
|
3414
|
+
/** Attach a real CallSession to the prewarmed session and flush buffered audio. */
|
|
3415
|
+
async attach(callSession) {
|
|
3416
|
+
const prev = this._call;
|
|
3417
|
+
this._call = callSession;
|
|
3418
|
+
attachBuffered(prev, callSession);
|
|
3419
|
+
}
|
|
3420
|
+
async start(callSession, tools) {
|
|
3421
|
+
if (tools) this._tools = tools;
|
|
3422
|
+
await this.prewarm();
|
|
3423
|
+
await this.attach(callSession);
|
|
3424
|
+
}
|
|
3176
3425
|
feedAudio(audio) {
|
|
3177
3426
|
if (this._session && !this._closed) {
|
|
3178
3427
|
const pcm8k = ulawToPcm16(audio);
|
|
@@ -3198,11 +3447,18 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3198
3447
|
}
|
|
3199
3448
|
this._pendingToolCall = null;
|
|
3200
3449
|
if (this._session) {
|
|
3201
|
-
|
|
3202
|
-
this._session.close();
|
|
3203
|
-
} catch {
|
|
3204
|
-
}
|
|
3450
|
+
const sess = this._session;
|
|
3205
3451
|
this._session = null;
|
|
3452
|
+
await Promise.race([
|
|
3453
|
+
(async () => {
|
|
3454
|
+
try {
|
|
3455
|
+
const ret = sess.close();
|
|
3456
|
+
if (ret && typeof ret.then === "function") await ret;
|
|
3457
|
+
} catch {
|
|
3458
|
+
}
|
|
3459
|
+
})(),
|
|
3460
|
+
new Promise((resolve) => setTimeout(resolve, 2e3))
|
|
3461
|
+
]);
|
|
3206
3462
|
}
|
|
3207
3463
|
}
|
|
3208
3464
|
_buildToolSchemas() {
|
|
@@ -3317,7 +3573,7 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3317
3573
|
const functionCalls = toolCall.functionCalls;
|
|
3318
3574
|
if (!functionCalls) return;
|
|
3319
3575
|
const responses = [];
|
|
3320
|
-
const player = this._holdAudioChunks && this._call ? new HoldAudioPlayer(this._call, this._holdAudioChunks) : null;
|
|
3576
|
+
const player = this._holdAudioChunks && this._call && !(this._call instanceof BufferingCall) ? new HoldAudioPlayer(this._call, this._holdAudioChunks) : null;
|
|
3321
3577
|
player?.start();
|
|
3322
3578
|
try {
|
|
3323
3579
|
for (const fc of functionCalls) {
|
|
@@ -3325,7 +3581,7 @@ var GeminiRealtime = class _GeminiRealtime {
|
|
|
3325
3581
|
const fcId = fc.id ?? "";
|
|
3326
3582
|
const args = fc.args ?? {};
|
|
3327
3583
|
this._log.info({ tool: name, args }, "Tool call: %s", name);
|
|
3328
|
-
if (BUILTIN_TOOL_NAMES.has(name) && this._call) {
|
|
3584
|
+
if (BUILTIN_TOOL_NAMES.has(name) && this._call && !(this._call instanceof BufferingCall)) {
|
|
3329
3585
|
const result = await executeBuiltinTool(
|
|
3330
3586
|
name,
|
|
3331
3587
|
args,
|
|
@@ -4254,6 +4510,6 @@ function mcpServerHTTP(options) {
|
|
|
4254
4510
|
};
|
|
4255
4511
|
}
|
|
4256
4512
|
|
|
4257
|
-
export { AnthropicLLM, AudioRecorder, BUILTIN_TOOL_NAMES, BuiltinTool, CallSession, ClawOpsAgent, DECODE_TABLE, DeepSeekLLM, DeepgramSTT, ElevenLabsTTS, FireworksLLM, GeminiLLM, GeminiRealtime, GroqLLM, MCPClient, MediaWebSocket, 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 };
|
|
4513
|
+
export { AnthropicLLM, AudioRecorder, BUILTIN_TOOL_NAMES, BuiltinTool, CallSession, ClawOpsAgent, ControlWebSocket, DECODE_TABLE, DeepSeekLLM, DeepgramSTT, ElevenLabsTTS, FireworksLLM, GeminiLLM, GeminiRealtime, GroqLLM, MCPClient, MediaWebSocket, MistralLLM, OllamaLLM, OpenAICompatLLM, OpenAILLM, OpenAIRealtime, PerplexityLLM, PipelineSession, TogetherLLM, ToolRegistry, XaiLLM, buildControlWsUrl, createAgentLogger, createPipelineLogger, executeBuiltinTool, functionTool, getBuiltinToolSchemas, getTracingConfig, isBuiltinTool, mcpServerHTTP, mcpServerStdio, pcm16ToUlaw, resamplePcm16, resetTracingConfig, setTracingConfig, ulawToPcm16, zodToToolParams };
|
|
4258
4514
|
//# sourceMappingURL=index.js.map
|
|
4259
4515
|
//# sourceMappingURL=index.js.map
|