@teamlearners/clawops 0.36.0 → 0.37.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 +78 -409
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +5 -340
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/livekit/index.cjs +9 -8
- package/dist/agent/livekit/index.cjs.map +1 -1
- package/dist/agent/livekit/index.d.cts +1 -1
- package/dist/agent/livekit/index.d.ts +1 -1
- package/dist/agent/livekit/index.js +2 -1
- package/dist/agent/livekit/index.js.map +1 -1
- package/dist/{base-C3yzF1e0.d.cts → base-BalWsEyb.d.cts} +31 -2
- package/dist/{base-C3yzF1e0.d.ts → base-BalWsEyb.d.ts} +31 -2
- package/dist/{chunk-4YLSDMLL.js → chunk-2OGQSWTW.js} +372 -3
- package/dist/chunk-2OGQSWTW.js.map +1 -0
- package/dist/{chunk-XL2VWU36.js → chunk-35XJNMFU.js} +3 -6
- package/dist/chunk-35XJNMFU.js.map +1 -0
- package/dist/{chunk-QD2ZY4UP.cjs → chunk-7TEOYLZI.cjs} +375 -2
- package/dist/chunk-7TEOYLZI.cjs.map +1 -0
- package/dist/chunk-CKPVV6SZ.js +6 -0
- package/dist/chunk-CKPVV6SZ.js.map +1 -0
- package/dist/chunk-EYI3ULDU.cjs +8 -0
- package/dist/chunk-EYI3ULDU.cjs.map +1 -0
- package/dist/{chunk-LKFNV2GI.cjs → chunk-LLPMUDNE.cjs} +2 -6
- package/dist/chunk-LLPMUDNE.cjs.map +1 -0
- package/dist/index.cjs +12 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4YLSDMLL.js.map +0 -1
- package/dist/chunk-LKFNV2GI.cjs.map +0 -1
- package/dist/chunk-QD2ZY4UP.cjs.map +0 -1
- package/dist/chunk-XL2VWU36.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk7TEOYLZI_cjs = require('../../chunk-7TEOYLZI.cjs');
|
|
4
|
+
require('../../chunk-EYI3ULDU.cjs');
|
|
4
5
|
|
|
5
6
|
// src/agent/livekit/io.ts
|
|
6
7
|
var SAMPLE_RATE = 8e3;
|
|
@@ -62,7 +63,7 @@ async function loadClawOpsIO() {
|
|
|
62
63
|
if (this._closed || !this._controller || ulaw.length === 0) return;
|
|
63
64
|
const samples = new Int16Array(ulaw.length);
|
|
64
65
|
for (let i = 0; i < ulaw.length; i++) {
|
|
65
|
-
samples[i] =
|
|
66
|
+
samples[i] = chunk7TEOYLZI_cjs.DECODE_TABLE[ulaw[i]];
|
|
66
67
|
}
|
|
67
68
|
const frame = new AudioFrame(samples, SAMPLE_RATE, 1, samples.length);
|
|
68
69
|
try {
|
|
@@ -131,7 +132,7 @@ async function loadClawOpsIO() {
|
|
|
131
132
|
}
|
|
132
133
|
this._pushedDurationMs += frame.samplesPerChannel / frame.sampleRate * 1e3;
|
|
133
134
|
const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);
|
|
134
|
-
const encoded =
|
|
135
|
+
const encoded = chunk7TEOYLZI_cjs.pcm16ToUlaw(pcm);
|
|
135
136
|
const ulaw = this._tail.length === 0 ? encoded : Buffer.concat([this._tail, encoded]);
|
|
136
137
|
let off = 0;
|
|
137
138
|
while (off + FRAME_BYTES <= ulaw.length) {
|
|
@@ -242,7 +243,7 @@ function createClawOpsPhoneTools(llm, options) {
|
|
|
242
243
|
};
|
|
243
244
|
const exclude = options.excludeNames ?? /* @__PURE__ */ new Set();
|
|
244
245
|
const ignoreFlag = llm.ToolFlag?.IGNORE_ON_ENTER;
|
|
245
|
-
const schemas =
|
|
246
|
+
const schemas = chunk7TEOYLZI_cjs.getBuiltinToolSchemas(options.enabled, "chat");
|
|
246
247
|
const tools = [];
|
|
247
248
|
for (const schema of schemas) {
|
|
248
249
|
const { name, description, parameters } = schema.function;
|
|
@@ -255,7 +256,7 @@ function createClawOpsPhoneTools(llm, options) {
|
|
|
255
256
|
// raw JSON schema (agents-js 가 JSONSchema7 을 그대로 받는다).
|
|
256
257
|
flags: IGNORE_ON_ENTER_TOOLS.has(name) ? ignoreFlag : void 0,
|
|
257
258
|
// raw schema tool 의 execute 는 파싱된 args 를 그대로 받는다.
|
|
258
|
-
execute: async (args) => await
|
|
259
|
+
execute: async (args) => await chunk7TEOYLZI_cjs.executeBuiltinTool(name, args ?? {}, requireCall()) ?? ""
|
|
259
260
|
})
|
|
260
261
|
);
|
|
261
262
|
}
|
|
@@ -284,7 +285,7 @@ var LiveKitSession = class {
|
|
|
284
285
|
_userToolCtx = null;
|
|
285
286
|
_toolRegistry = null;
|
|
286
287
|
_builtinTools = null;
|
|
287
|
-
_log =
|
|
288
|
+
_log = chunk7TEOYLZI_cjs.NOOP_LOGGER;
|
|
288
289
|
// 동적 로드한 agents-js 네임스페이스 (boot 시 캐시).
|
|
289
290
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
290
291
|
_llm = null;
|
|
@@ -322,7 +323,7 @@ var LiveKitSession = class {
|
|
|
322
323
|
this._output.setCall(callSession);
|
|
323
324
|
this._target = callSession;
|
|
324
325
|
await this._applyTools(callSession);
|
|
325
|
-
|
|
326
|
+
chunk7TEOYLZI_cjs.attachBuffered(prev, callSession);
|
|
326
327
|
}
|
|
327
328
|
feedAudio(audio, _timestamp) {
|
|
328
329
|
this._input?.pushUlaw(audio);
|
|
@@ -348,7 +349,7 @@ var LiveKitSession = class {
|
|
|
348
349
|
]);
|
|
349
350
|
this._llm = agents.llm;
|
|
350
351
|
const { ClawOpsAudioInput, ClawOpsAudioOutput } = io;
|
|
351
|
-
const target = call ?? new
|
|
352
|
+
const target = call ?? new chunk7TEOYLZI_cjs.BufferingCall();
|
|
352
353
|
const [session, agent] = await this._create(call);
|
|
353
354
|
this._validate(session, agent);
|
|
354
355
|
const input = new ClawOpsAudioInput();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/agent/livekit/io.ts","../../../src/agent/livekit/toolset.ts","../../../src/agent/livekit/session.ts"],"names":["DECODE_TABLE","pcm16ToUlaw","getBuiltinToolSchemas","executeBuiltinTool","NOOP_LOGGER","attachBuffered","BufferingCall"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * LiveKit AudioInput/AudioOutput <-> ClawOps Media WS 브리지.\n *\n * LiveKit 의 AgentSession 은 room 없이도 동작한다 — `session.input.audio` /\n * `session.output.audio` 에 커스텀 구현을 꽂으면 된다. 이 모듈이 그 구현이다.\n *\n * 전화망 wire format 은 G.711 μ-law 8kHz, 160바이트(20ms) 프레임이다. AudioOutput 이\n * `sampleRate=8000` 을 선언하므로 프레임워크가 TTS 출력을 8kHz 로 리샘플해준다 —\n * 우리는 μ-law 인코딩만 한다.\n *\n * 참조 구현: agents-js `voice/console_io.ts` 의 `TcpAudioInput`/`TcpAudioOutput` —\n * 같은 문제(비-WebRTC transport 를 AgentSession 에 물리기)를 푸는 LiveKit 자신의 코드다.\n * ⚠️ Python(clawops-python) 과 달리 agents-js 는 `AudioInput` 베이스와\n * `TranscriptionSynchronizer` 를 public export 하지 않는다. 그래서\n * - AudioInput 은 구조적으로 구현(ReadableStream 노출)하고,\n * - 재생 완료(playout) 판정은 `TcpAudioOutput` 과 동일한 Future 핸드셰이크로 하되\n * 완료 신호를 Media WS `mark` echo 로 받는다. (LiveKit 자신의 `AgentsConsole` 도\n * room-less 경로에서 transcription sync 를 생략하므로 우리도 생략한다.)\n *\n * ⚠️ AudioOutput 계약 2가지 — 어기면 조용히 깨진다:\n * 1. 첫 프레임에서 `onPlaybackStarted()` 를 반드시 호출한다. 빼면 assistant 메시지가\n * 히스토리에서 통째로 사라진다.\n * 2. `onPlaybackFinished()` 는 flush 세그먼트당 정확히 한 번, 취소되더라도 반드시\n * 호출한다. 안 그러면 프레임워크의 재생 회계가 어긋나 wait_for_playout 이 영구 대기한다.\n */\n\nimport { pcm16ToUlaw, DECODE_TABLE } from '../audio.js';\nimport type { CallSession } from '../session.js';\n\nexport const SAMPLE_RATE = 8000;\n/** 전화망 wire rate. AudioOutput 이 선언하면 프레임워크가 여기 맞춰 리샘플한다. */\n\nexport const FRAME_BYTES = 160;\n/** μ-law 20ms = 160바이트. */\n\nexport const ULAW_SILENCE_BYTE = 0xff;\n/** μ-law 무음 — 마지막 프레임 패딩용. */\n\nconst MARK_TIMEOUT_MARGIN_MS = 10_000;\n/** mark 대기 timeout = 밀어넣은 오디오 길이(ms) + 이 여유. */\n\n/** ClawOpsAudioOutput 이 오디오를 흘려보내는 대상(실 통화 또는 prewarm 버퍼)의 최소 계약. */\ninterface AudioSink {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n}\n\n/** 외부에서 참조할 인스턴스 타입 (실제 클래스는 optional dep 로드 후 팩토리에서 정의된다). */\nexport interface ClawOpsAudioInputInstance {\n readonly stream: unknown;\n pushUlaw(ulaw: Buffer): void;\n endInput(): void;\n close(): Promise<void>;\n onAttached(): void;\n onDetached(): void;\n}\n\nexport interface ClawOpsAudioOutputInstance {\n setCall(call: CallSession): void;\n close(): void;\n // AudioOutput 상속 메서드(captureFrame/flush/clearBuffer/onPlaybackStarted/...)는\n // 프레임워크가 호출하므로 여기 노출하지 않는다.\n}\n\nexport interface ClawOpsIO {\n ClawOpsAudioInput: new () => ClawOpsAudioInputInstance;\n ClawOpsAudioOutput: new (call: AudioSink) => ClawOpsAudioOutputInstance;\n}\n\n/** 외부 resolve 를 노출하는 1회성 Deferred (agents-js 의 non-exported `Future` 대응). */\nclass Deferred<T = void> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _done = false;\n constructor() {\n this.promise = new Promise<T>((resolve) => {\n this._resolve = resolve;\n });\n }\n get done(): boolean {\n return this._done;\n }\n resolve(value: T): void {\n if (this._done) return;\n this._done = true;\n this._resolve(value);\n }\n}\n\nlet _cached: ClawOpsIO | null = null;\n\n/**\n * agents-js / rtc-node 를 동적 로드해 IO 클래스를 정의한다 (1회 캐시).\n *\n * 클래스가 `voice.AudioOutput`(optional dep)을 상속하므로 top-level 이 아니라\n * import 성공 후 팩토리 안에서 정의해야 한다.\n */\nexport async function loadClawOpsIO(): Promise<ClawOpsIO> {\n if (_cached) return _cached;\n\n // 두 optional dep 를 병렬 로드한다 (독립적, boot 1회).\n const [agents, rtc] = (await Promise.all([\n import('@livekit/agents'),\n import('@livekit/rtc-node'),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ])) as [any, any];\n\n // agents-js 는 AudioOutput 생성자(instance-init)에서 log() 를 부르고, 로거가 초기화되지\n // 않았으면 throw 한다. room-less 는 worker/cli(initializeLogger 를 대신 부르는)를 거치지\n // 않으므로 우리가 초기화한다 — 단, 유저가 이미 했으면(loggerOptions() 존재) 건드리지 않는다.\n if (\n typeof agents.initializeLogger === 'function' &&\n (typeof agents.loggerOptions !== 'function' || !agents.loggerOptions())\n ) {\n agents.initializeLogger({ pretty: false, level: process.env['LIVEKIT_LOG_LEVEL'] ?? 'info' });\n }\n\n const AudioOutputBase = agents.voice.AudioOutput;\n const AudioFrame = rtc.AudioFrame;\n\n /**\n * 통화 인바운드 오디오(μ-law) -> rtc.AudioFrame 스트림.\n *\n * agents-js `AudioInput` 은 public export 되지 않으므로 상속하지 않고, 프레임워크가\n * 실제로 읽는 계약(`.stream` + onAttached/onDetached/close)만 구조적으로 구현한다.\n * `session.input.audio = <this>` 로 꽂으면 프레임워크가 `.stream` 을 소비한다.\n */\n class ClawOpsAudioInput implements ClawOpsAudioInputInstance {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _controller: ReadableStreamDefaultController<any> | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _stream: ReadableStream<any>;\n private _closed = false;\n\n constructor() {\n this._stream = new ReadableStream({\n start: (c) => {\n this._controller = c;\n },\n cancel: () => {\n this._closed = true;\n },\n });\n }\n\n get stream(): unknown {\n return this._stream;\n }\n\n /** G.711 μ-law 청크를 PCM16 프레임으로 바꿔 스트림에 밀어넣는다. */\n pushUlaw(ulaw: Buffer): void {\n if (this._closed || !this._controller || ulaw.length === 0) return;\n // μ-law 를 곧장 Int16Array 로 디코드한다 (중간 Buffer + 재읽기 루프 생략).\n const samples = new Int16Array(ulaw.length);\n for (let i = 0; i < ulaw.length; i++) {\n samples[i] = DECODE_TABLE[ulaw[i]!]!;\n }\n const frame = new AudioFrame(samples, SAMPLE_RATE, 1, samples.length);\n try {\n this._controller.enqueue(frame);\n } catch {\n // 스트림이 이미 닫혔다 — 무시.\n }\n }\n\n /** 스트림 종료 — 리더가 완료를 본다. */\n endInput(): void {\n this._closeStream();\n }\n\n async close(): Promise<void> {\n this._closeStream();\n }\n\n onAttached(): void {}\n onDetached(): void {}\n\n private _closeStream(): void {\n if (this._closed) return;\n this._closed = true;\n try {\n this._controller?.close();\n } catch {\n // 이미 닫힘.\n }\n }\n }\n\n /**\n * AgentSession 출력 -> μ-law 160바이트 프레임 -> CallSession.sendAudio().\n *\n * 재생 완료 판정은 Media WS `mark` 로 한다 — 플랫폼이 큐의 오디오를 다 내보낸 뒤\n * mark 를 에코한다. prewarm 중(BufferingCall)에는 media WS 가 없으므로 attach 될\n * 때까지 기다렸다가 fence 를 건다(그 전엔 아직 아무것도 재생되지 않는다).\n *\n * 구조는 agents-js `TcpAudioOutput` 을 따르고, 재생 완료 신호만 broker 대신 mark 로 받는다.\n */\n class ClawOpsAudioOutput extends AudioOutputBase implements ClawOpsAudioOutputInstance {\n private _call: AudioSink;\n private _pushedDurationMs = 0;\n private _captureStartedAt = 0;\n private _tail: Buffer = Buffer.alloc(0);\n private _flushPromise: Promise<void> | null = null;\n private _flushInProgress = false;\n private _interrupted = new Deferred();\n private _attached = new Deferred();\n private _markSeq = 0;\n private _closed = false;\n\n constructor(call: AudioSink) {\n // AudioOutput(sampleRate?, nextInChain?, capabilities?)\n super(SAMPLE_RATE, undefined, { pause: false });\n this._call = call;\n }\n\n /**\n * attach() 시 실제 CallSession 으로 교체한다 (prewarm -> 실제 통화).\n *\n * prewarm 중 버퍼링된 오디오는 지금부터 실제 재생되므로 경과 기준점을 여기로 옮긴다 —\n * 안 그러면 링 구간까지 \"재생됐다\"고 계산돼 barge-in 절단 위치가 뒤로 밀린다.\n */\n setCall(call: CallSession): void {\n this._call = call as unknown as AudioSink;\n if (this._pushedDurationMs > 0) {\n this._captureStartedAt = Date.now();\n }\n this._attached.resolve();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async captureFrame(frame: any): Promise<void> {\n // ⚠️ close() 후엔 base 를 건드리지 않는다 — super.captureFrame 이 세그먼트 카운트를\n // 올린 뒤 여기서 bail 하면 onPlaybackFinished 가 안 나가 teardown 시 wait 이 샌다.\n if (this._closed) return;\n await super.captureFrame(frame);\n\n // 이전 세그먼트 flush 가 진행 중이면 기다린다 (TcpAudioOutput 과 동일).\n if (this._flushInProgress && this._flushPromise) {\n await this._flushPromise;\n }\n\n if (this._pushedDurationMs === 0) {\n this._captureStartedAt = Date.now();\n // ⚠️ 계약 1: 빼면 assistant 메시지가 히스토리에서 사라진다.\n this.onPlaybackStarted(Date.now());\n }\n\n this._pushedDurationMs += (frame.samplesPerChannel / frame.sampleRate) * 1000;\n\n // frame.data 는 Int16Array(PCM16). LE 플랫폼에서 그대로 바이트 뷰로 읽는다.\n const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);\n const encoded = pcm16ToUlaw(pcm);\n // 대부분의 프레임은 160바이트 정렬돼 tail 이 비어 있다 — 그때는 concat(추가 할당+복사)을 건너뛴다.\n const ulaw = this._tail.length === 0 ? encoded : Buffer.concat([this._tail, encoded]);\n let off = 0;\n while (off + FRAME_BYTES <= ulaw.length) {\n await this._call.sendAudio(ulaw.subarray(off, off + FRAME_BYTES));\n off += FRAME_BYTES;\n }\n this._tail = Buffer.from(ulaw.subarray(off));\n }\n\n flush(): void {\n super.flush();\n if (this._pushedDurationMs > 0) {\n this._interrupted = new Deferred();\n this._flushInProgress = true;\n this._flushPromise = this._runPlayoutHandshake().finally(() => {\n this._flushInProgress = false;\n });\n } else {\n this._tail = Buffer.alloc(0);\n }\n }\n\n clearBuffer(): void {\n // ⚠️ 여기서 onPlaybackFinished 를 부르지 않는다 — 핸드셰이크가 interrupted 를\n // 보고 한 번만 쏜다.\n this._tail = Buffer.alloc(0);\n if (this._pushedDurationMs > 0) {\n this._interrupted.resolve();\n }\n // prewarm 중이면 BufferingCall.clearAudio 가 버퍼를 비우고, 실 통화면 media WS 큐를 flush.\n this._call.clearAudio();\n }\n\n /** 세션 종료 시 남은 대기를 깨워 정리한다. */\n close(): void {\n this._closed = true;\n this._interrupted.resolve();\n this._attached.resolve();\n }\n\n // ── 내부 ───────────────────────────────────────────────────\n\n /** 실 통화이고 transport 가 살아 있으면 mark 훅을 가진 CallSession 을 돌려준다. */\n private _mediaCall(): CallSession | null {\n const c = this._call as unknown as {\n _sendMark?: unknown;\n _waitForMark?: unknown;\n _flushTransport?: unknown;\n _isTransportConnected?: () => boolean;\n };\n if (\n typeof c._sendMark !== 'function' ||\n typeof c._waitForMark !== 'function' ||\n typeof c._flushTransport !== 'function'\n ) {\n return null; // BufferingCall(prewarm) 또는 아직 미바인딩.\n }\n if (typeof c._isTransportConnected === 'function' && !c._isTransportConnected()) {\n return null; // WS 사망.\n }\n return this._call as unknown as CallSession;\n }\n\n /** `p` 와 barge-in 중 먼저 오는 걸 기다린다. 반환: p 가 이겼는가(=중단 아님). */\n private async _race(p: Promise<unknown>): Promise<boolean> {\n const winner = await Promise.race([\n p.then(() => 'main' as const),\n this._interrupted.promise.then(() => 'interrupt' as const),\n ]);\n return winner === 'main';\n }\n\n private async _runPlayoutHandshake(): Promise<void> {\n let interrupted = true;\n try {\n // 남은 자투리를 무음으로 패딩해 한 프레임으로 내보낸다.\n const tail = this._tail;\n this._tail = Buffer.alloc(0);\n if (tail.length > 0) {\n const padded = Buffer.concat([\n tail,\n Buffer.alloc(FRAME_BYTES - tail.length, ULAW_SILENCE_BYTE),\n ]);\n await this._call.sendAudio(padded);\n }\n interrupted = await this._awaitPlayout();\n } catch {\n // WS 사망 등 전송 오류 — interrupted 로 처리하고 프레임워크로 전파하지 않는다.\n interrupted = true;\n } finally {\n // ⚠️ 계약 2: 세그먼트당 정확히 한 번 — 취소되더라도 반드시 emit.\n const played = interrupted\n ? Math.min(Math.max(0, Date.now() - this._captureStartedAt), this._pushedDurationMs)\n : this._pushedDurationMs;\n this.onPlaybackFinished({ playbackPosition: played / 1000, interrupted });\n this._pushedDurationMs = 0;\n this._interrupted = new Deferred();\n }\n }\n\n /** 재생이 끝나거나 barge-in 될 때까지 대기. 반환: interrupted 여부. */\n private async _awaitPlayout(): Promise<boolean> {\n let media = this._mediaCall();\n if (!media) {\n // prewarm — 아직 재생 주체가 없다. attach 될 때까지 기다렸다가 fence 를 건다.\n if (!(await this._race(this._attached.promise))) {\n return true; // attach 전에 중단됐다.\n }\n media = this._mediaCall();\n if (!media) {\n return this._interrupted.done; // attach 됐는데도 WS 가 없다 — fence 불가.\n }\n }\n\n // send_mark 는 즉시 나가지만 send_audio 는 로컬 큐에 쌓인다. flush 로 큐를 비우지\n // 않으면 mark 가 오디오를 추월한다.\n await media._flushTransport!();\n const markName = `lk-${this._markSeq++}`;\n media._sendMark!(markName);\n const timeout = Math.round(this._pushedDurationMs) + MARK_TIMEOUT_MARGIN_MS;\n // mark echo(재생 완료)와 barge-in 중 먼저 오는 것을 기다린다.\n return !(await this._race(media._waitForMark!(markName, timeout)));\n }\n }\n\n _cached = { ClawOpsAudioInput, ClawOpsAudioOutput };\n return _cached;\n}\n","/**\n * ClawOps 전화 내장 도구를 LiveKit Toolset 으로 노출한다.\n *\n * `hang_up` / `collect_dtmf` / `send_dtmf` / `transfer_call` — 스키마와 동작을\n * `pipeline/builtin-tool-schemas.ts` 에서 그대로 재사용한다 (핸들러 중복 없음).\n *\n * 설계 메모:\n * - LiveKit 이 파는 전화 도구(`end_call`/`send_dtmf`)는 room(`get_job_context()`)을\n * 잡아 room 없이는 죽으므로 쓰지 않는다. 패턴만 가져오고 실행은 우리 것을 쓴다.\n * - `CallSession` 은 클로저 상태로 들고 간다 (prewarm -> attach 시 setCall 로 교체).\n */\n\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport { getBuiltinToolSchemas, executeBuiltinTool } from '../pipeline/builtin-tool-schemas.js';\nimport type { CallSession } from '../session.js';\n\n/** 인사말 도중 모델이 통화를 끊거나 전환하는 사고를 막는다 (ToolFlag.IGNORE_ON_ENTER). */\nconst IGNORE_ON_ENTER_TOOLS = new Set(['hang_up', 'transfer_call']);\n\nexport interface ClawOpsPhoneTools {\n /** LiveKit Toolset 인스턴스 — `agent.updateTools([...])` 에 넣는 ToolContextEntry. */\n toolset: unknown;\n /** prewarm -> attach 시 실제 통화로 교체한다. */\n setCall(call: CallSession): void;\n}\n\n/**\n * ClawOps 통화 제어 도구 묶음을 만든다.\n *\n * @param llm 동적 로드한 `@livekit/agents` 의 `llm` 네임스페이스 (`tool`/`Toolset`/`ToolFlag`).\n * @param options.enabled 활성화할 내장 도구 (`ClawOpsAgent(builtinTools=...)` 와 동일).\n * @param options.excludeNames 유저/registry 도구와 이름이 겹쳐 제외할 내장 도구 이름.\n */\nexport function createClawOpsPhoneTools(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n llm: any,\n options: { enabled: Set<BuiltinTool>; excludeNames?: Set<string> },\n): ClawOpsPhoneTools {\n const state: { call: CallSession | null } = { call: null };\n const requireCall = (): CallSession => {\n if (!state.call) {\n throw new Error('통화가 아직 연결되지 않았습니다');\n }\n return state.call;\n };\n\n const exclude = options.excludeNames ?? new Set<string>();\n const ignoreFlag = llm.ToolFlag?.IGNORE_ON_ENTER;\n\n // 활성화된 내장 도구의 정규 스키마(name/description/parameters)를 가져온다.\n const schemas = getBuiltinToolSchemas(options.enabled, 'chat') as Array<{\n function: { name: string; description: string; parameters: Record<string, unknown> };\n }>;\n\n const tools: unknown[] = [];\n for (const schema of schemas) {\n const { name, description, parameters } = schema.function;\n if (exclude.has(name)) continue; // 유저/registry 도구와 충돌 — 내장 쪽을 뺀다.\n tools.push(\n llm.tool({\n name,\n description,\n parameters, // raw JSON schema (agents-js 가 JSONSchema7 을 그대로 받는다).\n flags: IGNORE_ON_ENTER_TOOLS.has(name) ? ignoreFlag : undefined,\n // raw schema tool 의 execute 는 파싱된 args 를 그대로 받는다.\n execute: async (args: Record<string, unknown>) =>\n (await executeBuiltinTool(name, args ?? {}, requireCall())) ?? '',\n }),\n );\n }\n\n const toolset = llm.Toolset.create({ id: 'clawops_phone', tools });\n\n return {\n toolset,\n setCall: (call: CallSession) => {\n state.call = call;\n },\n };\n}\n","/**\n * LiveKitSession — 유저의 LiveKit AgentSession 을 ClawOps `Session` 계약으로 감싼다.\n *\n * `ClawOpsAgent` 는 `Session`(pipeline/base.ts)만 알고 통화를 굴린다. 이 클래스가 그\n * 계약을 구현하면서 내부적으로 LiveKit AgentSession 을 돌린다 — room 없이.\n *\n * 유저는 관용적인 LiveKit 코드를 그대로 쓴다. `Agent` 서브클래스와 `AgentSession` 설정은\n * 손대지 않아도 되고, 유일한 차이는 `session.start({ room })` 을 우리가 room 없이\n * 대신 불러준다는 것이다.\n *\n * ⚠️ v0 은 동시통화 1건이다 (`ClawOpsAgent` 가 세션 인스턴스를 통화 간 공유한다).\n * `create` 를 팩토리로 받아두었으므로, 추후 통화당 격리로 넘어갈 때 유저 코드는 그대로 둔다.\n */\n\nimport type { Logger } from 'pino';\n\nimport { NOOP_LOGGER } from '../logger.js';\nimport type { CallSession } from '../session.js';\nimport type { Session } from '../pipeline/base.js';\nimport type { ToolRegistry } from '../tool.js';\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport type { SessionTelemetry } from '../telemetry.js';\nimport { BufferingCall, attachBuffered } from '../pipeline/buffering-call.js';\nimport { loadClawOpsIO } from './io.js';\nimport type { ClawOpsAudioInputInstance, ClawOpsAudioOutputInstance } from './io.js';\nimport { createClawOpsPhoneTools } from './toolset.js';\nimport type { ClawOpsPhoneTools } from './toolset.js';\n\n/**\n * 통화당 1회 호출되는 팩토리. `[AgentSession, Agent]` 를 반환한다.\n *\n * `call` 은 착신/일반 시작에서는 실제 CallSession, 발신 prewarm 중에는 null 이다\n * (prewarm 은 응답 전에 도는데 `Session.prewarm()` 이 call 을 넘겨주지 않는다).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type LiveKitCreateFn = (call: CallSession | null) => Promise<[any, any]>;\n\nexport class LiveKitSession implements Session {\n private readonly _create: LiveKitCreateFn;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _session: any = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _agent: any = null;\n private _input: ClawOpsAudioInputInstance | null = null;\n private _output: ClawOpsAudioOutputInstance | null = null;\n private _phone: ClawOpsPhoneTools | null = null;\n private _target: CallSession | BufferingCall | null = null;\n /** 유저 원본 도구 스냅샷 (우리 도구 추가 전). attach 재적용 시 누적 방지. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _userToolCtx: any = null;\n\n private _toolRegistry: ToolRegistry | null = null;\n private _builtinTools: Set<BuiltinTool> | null = null;\n private _log: Logger = NOOP_LOGGER;\n\n // 동적 로드한 agents-js 네임스페이스 (boot 시 캐시).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _llm: any = null;\n\n constructor(create: LiveKitCreateFn) {\n this._create = create;\n }\n\n // ── ClawOpsAgent 가 duck-typing 으로 주입하는 setter ──────────\n\n setToolRegistry(registry: ToolRegistry): void {\n this._toolRegistry = registry;\n }\n\n setBuiltinTools(tools: Set<BuiltinTool>): void {\n this._builtinTools = tools;\n }\n\n setLogger(logger: Logger): void {\n this._log = logger;\n }\n\n getTelemetry(): SessionTelemetry | null {\n // 녹음/텔레메트리는 미디어 레이어에서 세션 무관하게 동작하므로 여기선 null.\n return null;\n }\n\n // ── Session 계약 ────────────────────────────────────────────\n\n async start(callSession: CallSession, tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(callSession);\n }\n\n async prewarm(tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(null);\n }\n\n async attach(callSession: CallSession): Promise<void> {\n // boot 성공 신호는 _session (부분 완료된 boot 은 _output 만 set 돼 있을 수 있다).\n if (!this._session || !this._output || !this._agent) {\n await this._boot(callSession);\n return;\n }\n const prev = this._target;\n this._output.setCall(callSession);\n this._target = callSession;\n // ⚠️ 아웃바운드 prewarm 은 _boot(null) 이 setter 실행 전에 돌아 도구를 비운 채\n // 시작한다. setter 는 이제 실행됐으므로 여기서 registry/builtin 도구를 다시 붙인다.\n await this._applyTools(callSession);\n attachBuffered(prev, callSession);\n }\n\n feedAudio(audio: Buffer, _timestamp?: number): void {\n this._input?.pushUlaw(audio);\n }\n\n async feedDtmf(digits: string): Promise<void> {\n if (!this._session) return;\n // generateReply 는 동기 메서드다. teardown 중에는 세션 activity 가 이미 정리돼\n // 던질 수 있으므로 삼킨다.\n try {\n this._session.generateReply({ userInput: `[전화 키패드 입력] ${digits}` });\n } catch (err) {\n this._log.debug({ err }, 'feedDtmf skipped (session not running)');\n }\n }\n\n async stop(): Promise<void> {\n this._input?.endInput();\n await this._closeAgentSession();\n }\n\n // ── 내부 ────────────────────────────────────────────────────\n\n private async _boot(call: CallSession | null): Promise<void> {\n // 재진입(attach 실패 폴백) 시 이전 세션이 새지 않도록 먼저 닫는다.\n await this._closeAgentSession();\n\n // agents import 와 IO 로드는 독립적이라 병렬로 (boot 1회, 이후 loadClawOpsIO 는 캐시).\n const [agents, io] = await Promise.all([\n import('@livekit/agents') as Promise<{ llm: unknown }>,\n loadClawOpsIO(),\n ]);\n this._llm = (agents as { llm: unknown }).llm;\n const { ClawOpsAudioInput, ClawOpsAudioOutput } = io;\n\n const target: CallSession | BufferingCall = call ?? new BufferingCall();\n\n const [session, agent] = await this._create(call);\n this._validate(session, agent);\n\n const input = new ClawOpsAudioInput();\n const output = new ClawOpsAudioOutput(target as unknown as {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n });\n this._input = input;\n this._output = output;\n\n session.input.audio = input;\n session.output.audio = output;\n // agents-js 의 room-less 레퍼런스(AgentsConsole)도 transcription sync 를 생략한다.\n // (transcript 는 conversation_item_added 로 별도 브리지한다 — _wireTranscripts.)\n session.output.transcription = null;\n\n this._wireTranscripts(session);\n\n this._agent = agent;\n // 우리 도구를 붙이기 전 유저 원본 도구를 스냅샷한다 (attach 재적용 시 누적 방지).\n this._userToolCtx = agent.toolCtx;\n await this._applyTools(call);\n\n // room 을 넘기지 않는 것이 이 통합의 전부다.\n await session.start({ agent });\n\n this._session = session;\n this._target = target;\n }\n\n /**\n * 유저 도구 + 브리지된 registry 도구 + 내장 전화 도구를 이름 충돌 없이 붙인다.\n *\n * 아웃바운드 prewarm(_boot(null))은 setter 실행 전에 돌아 registry/builtin 이 비어\n * 있고, 그 사이엔 통화가 연결되지 않아 도구를 쓸 일도 없다. 그래서 이때는 유저 도구만\n * 올리고, attach() 가 setter 실행 후 실제 도구를 붙이는 유일한 조립 지점이 된다.\n */\n private async _applyTools(call: CallSession | null): Promise<void> {\n if (!this._agent) return;\n const agent = this._agent;\n const llm = this._llm;\n\n if (call === null) {\n // prewarm — 아직 registry/builtin 이 없다. 유저 원본 ToolContext 를 그대로 재적용.\n await agent.updateTools(this._userToolCtx);\n return;\n }\n\n // 유저 도구(중첩 Toolset 포함)의 실제 함수 이름을 모은다 — 내장 도구와 겹칠 때\n // ToolContext.flatten() 이 \"duplicate function name\" 으로 통화를 떨구는 걸 막는다.\n const taken = new Set<string>(llm.sortedToolNames(this._userToolCtx));\n const bridged = this._bridgeRegistry(this._toolRegistry, taken);\n\n const enabled = this._builtinTools ?? new Set<BuiltinTool>();\n this._phone = createClawOpsPhoneTools(llm, { enabled, excludeNames: taken });\n this._phone.setCall(call);\n\n // ⚠️ sortedToolEntries 는 `[name, tool]` 쌍을 준다. updateTools/ToolContext 는 bare\n // tool 배열(ToolContextEntry[])을 받으므로 tool 만 추출해 넘긴다 — 쌍을 그대로 펼치면\n // 유저 도구가 유효하지 않은 엔트리로 취급돼 전부 유실된다.\n const userTools = (llm.sortedToolEntries(this._userToolCtx) as Array<[string, unknown]>).map(\n (entry) => entry[1],\n );\n await agent.updateTools([...userTools, ...bridged, this._phone.toolset]);\n }\n\n /** `@tool` / MCP 도구(ToolRegistry)를 LiveKit function tool 로 노출한다. */\n private _bridgeRegistry(registry: ToolRegistry | null, taken: Set<string>): unknown[] {\n if (!registry) return [];\n const llm = this._llm;\n const out: unknown[] = [];\n for (const def of registry.toOpenAITools()) {\n const name = def.function.name;\n if (taken.has(name)) continue; // 유저 도구가 우선.\n taken.add(name);\n out.push(\n llm.tool({\n name,\n description: def.function.description,\n parameters: def.function.parameters,\n execute: async (args: Record<string, unknown>) => {\n const result = await registry.call(name, args ?? {});\n return typeof result === 'string' ? result : JSON.stringify(result);\n },\n }),\n );\n }\n return out;\n }\n\n /**\n * LiveKit 의 최종 대화 항목을 ClawOps `transcript` 훅으로 흘려보낸다.\n *\n * 네이티브 세션(OpenAI/Gemini/Pipeline)은 `call._emit('transcript', role, text)` 를\n * 부른다. `@on('transcript')` 로 트랜스크립트를 모으는 기존 앱이 세션만 바꿔도 그대로\n * 돌게 하려면 여기서 같은 계약을 재현한다.\n *\n * `conversation_item_added` 는 user·assistant 최종 메시지가 히스토리에 커밋될 때 한\n * 번씩 뜬다 — 부분(interim) 중복 없이 최종만 얻는다. item 이 handoff 등 ChatMessage 가\n * 아니면 role/textContent 가 없어 자연히 걸러진다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _wireTranscripts(session: any): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n session.on('conversation_item_added', (ev: any) => {\n const item = ev?.item;\n const role = item?.role;\n if (role !== 'user' && role !== 'assistant') return;\n const text = item?.textContent;\n if (!text) return;\n const target = this._target as unknown as { _emit?: (event: string, ...args: unknown[]) => void };\n // Node 의 _emit 은 동기다 — Python 처럼 fire-and-forget task 를 추적할 필요 없다.\n // prewarm 중이면 _target 이 BufferingCall 이라 _emit 이 드롭 카운트로 흡수한다.\n target?._emit?.('transcript', role, text);\n });\n }\n\n private async _closeAgentSession(): Promise<void> {\n if (this._output) {\n this._output.close();\n this._output = null;\n }\n if (this._session) {\n const s = this._session;\n this._session = null;\n try {\n if (typeof s.aclose === 'function') {\n await s.aclose();\n } else if (typeof s.close === 'function') {\n await s.close();\n }\n } catch (err) {\n this._log.warn({ err }, 'AgentSession close failed');\n }\n }\n }\n\n /**\n * LiveKit 이 조용히 넘어가는 두 조합을 우리가 막는다 — 실수하면 \"말을 못 하는\n * 에이전트\"가 아무 예외 없이 배포된다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _validate(session: any, agent: any): void {\n let audioOutput: unknown;\n let tts: unknown;\n try {\n const llm = agent?.llm ?? session?.llm;\n tts = agent?.tts ?? session?.tts;\n audioOutput = llm?.capabilities?.audioOutput;\n } catch {\n return; // capabilities 를 못 읽으면 검증 생략 (거짓 양성 방지).\n }\n if (audioOutput === undefined || audioOutput === null) {\n return; // realtime 모델이 아니다 (일반 LLM + TTS 파이프라인).\n }\n if (audioOutput === false && (tts === undefined || tts === null)) {\n throw new Error(\n \"RealtimeModel 이 modalities=['text'] 인데 tts 가 없습니다 — 에이전트가 소리를 \" +\n \"내지 못합니다. AgentSession(tts=...) 를 주거나 modalities 에 'audio' 를 넣으세요.\",\n );\n }\n if (audioOutput === true && tts !== undefined && tts !== null) {\n this._log.warn(\n 'RealtimeModel 이 오디오를 직접 출력하므로 tts 가 무시됩니다. TTS 를 쓰려면 ' +\n \"RealtimeModel(modalities=['text']) 로 설정하세요.\",\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/agent/livekit/io.ts","../../../src/agent/livekit/toolset.ts","../../../src/agent/livekit/session.ts"],"names":["DECODE_TABLE","pcm16ToUlaw","getBuiltinToolSchemas","executeBuiltinTool","NOOP_LOGGER","attachBuffered","BufferingCall"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * LiveKit AudioInput/AudioOutput <-> ClawOps Media WS 브리지.\n *\n * LiveKit 의 AgentSession 은 room 없이도 동작한다 — `session.input.audio` /\n * `session.output.audio` 에 커스텀 구현을 꽂으면 된다. 이 모듈이 그 구현이다.\n *\n * 전화망 wire format 은 G.711 μ-law 8kHz, 160바이트(20ms) 프레임이다. AudioOutput 이\n * `sampleRate=8000` 을 선언하므로 프레임워크가 TTS 출력을 8kHz 로 리샘플해준다 —\n * 우리는 μ-law 인코딩만 한다.\n *\n * 참조 구현: agents-js `voice/console_io.ts` 의 `TcpAudioInput`/`TcpAudioOutput` —\n * 같은 문제(비-WebRTC transport 를 AgentSession 에 물리기)를 푸는 LiveKit 자신의 코드다.\n * ⚠️ Python(clawops-python) 과 달리 agents-js 는 `AudioInput` 베이스와\n * `TranscriptionSynchronizer` 를 public export 하지 않는다. 그래서\n * - AudioInput 은 구조적으로 구현(ReadableStream 노출)하고,\n * - 재생 완료(playout) 판정은 `TcpAudioOutput` 과 동일한 Future 핸드셰이크로 하되\n * 완료 신호를 Media WS `mark` echo 로 받는다. (LiveKit 자신의 `AgentsConsole` 도\n * room-less 경로에서 transcription sync 를 생략하므로 우리도 생략한다.)\n *\n * ⚠️ AudioOutput 계약 2가지 — 어기면 조용히 깨진다:\n * 1. 첫 프레임에서 `onPlaybackStarted()` 를 반드시 호출한다. 빼면 assistant 메시지가\n * 히스토리에서 통째로 사라진다.\n * 2. `onPlaybackFinished()` 는 flush 세그먼트당 정확히 한 번, 취소되더라도 반드시\n * 호출한다. 안 그러면 프레임워크의 재생 회계가 어긋나 wait_for_playout 이 영구 대기한다.\n */\n\nimport { pcm16ToUlaw, DECODE_TABLE } from '../audio.js';\nimport type { CallSession } from '../session.js';\n\nexport const SAMPLE_RATE = 8000;\n/** 전화망 wire rate. AudioOutput 이 선언하면 프레임워크가 여기 맞춰 리샘플한다. */\n\nexport const FRAME_BYTES = 160;\n/** μ-law 20ms = 160바이트. */\n\nexport const ULAW_SILENCE_BYTE = 0xff;\n/** μ-law 무음 — 마지막 프레임 패딩용. */\n\nconst MARK_TIMEOUT_MARGIN_MS = 10_000;\n/** mark 대기 timeout = 밀어넣은 오디오 길이(ms) + 이 여유. */\n\n/** ClawOpsAudioOutput 이 오디오를 흘려보내는 대상(실 통화 또는 prewarm 버퍼)의 최소 계약. */\ninterface AudioSink {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n}\n\n/** 외부에서 참조할 인스턴스 타입 (실제 클래스는 optional dep 로드 후 팩토리에서 정의된다). */\nexport interface ClawOpsAudioInputInstance {\n readonly stream: unknown;\n pushUlaw(ulaw: Buffer): void;\n endInput(): void;\n close(): Promise<void>;\n onAttached(): void;\n onDetached(): void;\n}\n\nexport interface ClawOpsAudioOutputInstance {\n setCall(call: CallSession): void;\n close(): void;\n // AudioOutput 상속 메서드(captureFrame/flush/clearBuffer/onPlaybackStarted/...)는\n // 프레임워크가 호출하므로 여기 노출하지 않는다.\n}\n\nexport interface ClawOpsIO {\n ClawOpsAudioInput: new () => ClawOpsAudioInputInstance;\n ClawOpsAudioOutput: new (call: AudioSink) => ClawOpsAudioOutputInstance;\n}\n\n/** 외부 resolve 를 노출하는 1회성 Deferred (agents-js 의 non-exported `Future` 대응). */\nclass Deferred<T = void> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _done = false;\n constructor() {\n this.promise = new Promise<T>((resolve) => {\n this._resolve = resolve;\n });\n }\n get done(): boolean {\n return this._done;\n }\n resolve(value: T): void {\n if (this._done) return;\n this._done = true;\n this._resolve(value);\n }\n}\n\nlet _cached: ClawOpsIO | null = null;\n\n/**\n * agents-js / rtc-node 를 동적 로드해 IO 클래스를 정의한다 (1회 캐시).\n *\n * 클래스가 `voice.AudioOutput`(optional dep)을 상속하므로 top-level 이 아니라\n * import 성공 후 팩토리 안에서 정의해야 한다.\n */\nexport async function loadClawOpsIO(): Promise<ClawOpsIO> {\n if (_cached) return _cached;\n\n // 두 optional dep 를 병렬 로드한다 (독립적, boot 1회).\n const [agents, rtc] = (await Promise.all([\n import('@livekit/agents'),\n import('@livekit/rtc-node'),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ])) as [any, any];\n\n // agents-js 는 AudioOutput 생성자(instance-init)에서 log() 를 부르고, 로거가 초기화되지\n // 않았으면 throw 한다. room-less 는 worker/cli(initializeLogger 를 대신 부르는)를 거치지\n // 않으므로 우리가 초기화한다 — 단, 유저가 이미 했으면(loggerOptions() 존재) 건드리지 않는다.\n if (\n typeof agents.initializeLogger === 'function' &&\n (typeof agents.loggerOptions !== 'function' || !agents.loggerOptions())\n ) {\n agents.initializeLogger({ pretty: false, level: process.env['LIVEKIT_LOG_LEVEL'] ?? 'info' });\n }\n\n const AudioOutputBase = agents.voice.AudioOutput;\n const AudioFrame = rtc.AudioFrame;\n\n /**\n * 통화 인바운드 오디오(μ-law) -> rtc.AudioFrame 스트림.\n *\n * agents-js `AudioInput` 은 public export 되지 않으므로 상속하지 않고, 프레임워크가\n * 실제로 읽는 계약(`.stream` + onAttached/onDetached/close)만 구조적으로 구현한다.\n * `session.input.audio = <this>` 로 꽂으면 프레임워크가 `.stream` 을 소비한다.\n */\n class ClawOpsAudioInput implements ClawOpsAudioInputInstance {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _controller: ReadableStreamDefaultController<any> | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _stream: ReadableStream<any>;\n private _closed = false;\n\n constructor() {\n this._stream = new ReadableStream({\n start: (c) => {\n this._controller = c;\n },\n cancel: () => {\n this._closed = true;\n },\n });\n }\n\n get stream(): unknown {\n return this._stream;\n }\n\n /** G.711 μ-law 청크를 PCM16 프레임으로 바꿔 스트림에 밀어넣는다. */\n pushUlaw(ulaw: Buffer): void {\n if (this._closed || !this._controller || ulaw.length === 0) return;\n // μ-law 를 곧장 Int16Array 로 디코드한다 (중간 Buffer + 재읽기 루프 생략).\n const samples = new Int16Array(ulaw.length);\n for (let i = 0; i < ulaw.length; i++) {\n samples[i] = DECODE_TABLE[ulaw[i]!]!;\n }\n const frame = new AudioFrame(samples, SAMPLE_RATE, 1, samples.length);\n try {\n this._controller.enqueue(frame);\n } catch {\n // 스트림이 이미 닫혔다 — 무시.\n }\n }\n\n /** 스트림 종료 — 리더가 완료를 본다. */\n endInput(): void {\n this._closeStream();\n }\n\n async close(): Promise<void> {\n this._closeStream();\n }\n\n onAttached(): void {}\n onDetached(): void {}\n\n private _closeStream(): void {\n if (this._closed) return;\n this._closed = true;\n try {\n this._controller?.close();\n } catch {\n // 이미 닫힘.\n }\n }\n }\n\n /**\n * AgentSession 출력 -> μ-law 160바이트 프레임 -> CallSession.sendAudio().\n *\n * 재생 완료 판정은 Media WS `mark` 로 한다 — 플랫폼이 큐의 오디오를 다 내보낸 뒤\n * mark 를 에코한다. prewarm 중(BufferingCall)에는 media WS 가 없으므로 attach 될\n * 때까지 기다렸다가 fence 를 건다(그 전엔 아직 아무것도 재생되지 않는다).\n *\n * 구조는 agents-js `TcpAudioOutput` 을 따르고, 재생 완료 신호만 broker 대신 mark 로 받는다.\n */\n class ClawOpsAudioOutput extends AudioOutputBase implements ClawOpsAudioOutputInstance {\n private _call: AudioSink;\n private _pushedDurationMs = 0;\n private _captureStartedAt = 0;\n private _tail: Buffer = Buffer.alloc(0);\n private _flushPromise: Promise<void> | null = null;\n private _flushInProgress = false;\n private _interrupted = new Deferred();\n private _attached = new Deferred();\n private _markSeq = 0;\n private _closed = false;\n\n constructor(call: AudioSink) {\n // AudioOutput(sampleRate?, nextInChain?, capabilities?)\n super(SAMPLE_RATE, undefined, { pause: false });\n this._call = call;\n }\n\n /**\n * attach() 시 실제 CallSession 으로 교체한다 (prewarm -> 실제 통화).\n *\n * prewarm 중 버퍼링된 오디오는 지금부터 실제 재생되므로 경과 기준점을 여기로 옮긴다 —\n * 안 그러면 링 구간까지 \"재생됐다\"고 계산돼 barge-in 절단 위치가 뒤로 밀린다.\n */\n setCall(call: CallSession): void {\n this._call = call as unknown as AudioSink;\n if (this._pushedDurationMs > 0) {\n this._captureStartedAt = Date.now();\n }\n this._attached.resolve();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async captureFrame(frame: any): Promise<void> {\n // ⚠️ close() 후엔 base 를 건드리지 않는다 — super.captureFrame 이 세그먼트 카운트를\n // 올린 뒤 여기서 bail 하면 onPlaybackFinished 가 안 나가 teardown 시 wait 이 샌다.\n if (this._closed) return;\n await super.captureFrame(frame);\n\n // 이전 세그먼트 flush 가 진행 중이면 기다린다 (TcpAudioOutput 과 동일).\n if (this._flushInProgress && this._flushPromise) {\n await this._flushPromise;\n }\n\n if (this._pushedDurationMs === 0) {\n this._captureStartedAt = Date.now();\n // ⚠️ 계약 1: 빼면 assistant 메시지가 히스토리에서 사라진다.\n this.onPlaybackStarted(Date.now());\n }\n\n this._pushedDurationMs += (frame.samplesPerChannel / frame.sampleRate) * 1000;\n\n // frame.data 는 Int16Array(PCM16). LE 플랫폼에서 그대로 바이트 뷰로 읽는다.\n const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);\n const encoded = pcm16ToUlaw(pcm);\n // 대부분의 프레임은 160바이트 정렬돼 tail 이 비어 있다 — 그때는 concat(추가 할당+복사)을 건너뛴다.\n const ulaw = this._tail.length === 0 ? encoded : Buffer.concat([this._tail, encoded]);\n let off = 0;\n while (off + FRAME_BYTES <= ulaw.length) {\n await this._call.sendAudio(ulaw.subarray(off, off + FRAME_BYTES));\n off += FRAME_BYTES;\n }\n this._tail = Buffer.from(ulaw.subarray(off));\n }\n\n flush(): void {\n super.flush();\n if (this._pushedDurationMs > 0) {\n this._interrupted = new Deferred();\n this._flushInProgress = true;\n this._flushPromise = this._runPlayoutHandshake().finally(() => {\n this._flushInProgress = false;\n });\n } else {\n this._tail = Buffer.alloc(0);\n }\n }\n\n clearBuffer(): void {\n // ⚠️ 여기서 onPlaybackFinished 를 부르지 않는다 — 핸드셰이크가 interrupted 를\n // 보고 한 번만 쏜다.\n this._tail = Buffer.alloc(0);\n if (this._pushedDurationMs > 0) {\n this._interrupted.resolve();\n }\n // prewarm 중이면 BufferingCall.clearAudio 가 버퍼를 비우고, 실 통화면 media WS 큐를 flush.\n this._call.clearAudio();\n }\n\n /** 세션 종료 시 남은 대기를 깨워 정리한다. */\n close(): void {\n this._closed = true;\n this._interrupted.resolve();\n this._attached.resolve();\n }\n\n // ── 내부 ───────────────────────────────────────────────────\n\n /** 실 통화이고 transport 가 살아 있으면 mark 훅을 가진 CallSession 을 돌려준다. */\n private _mediaCall(): CallSession | null {\n const c = this._call as unknown as {\n _sendMark?: unknown;\n _waitForMark?: unknown;\n _flushTransport?: unknown;\n _isTransportConnected?: () => boolean;\n };\n if (\n typeof c._sendMark !== 'function' ||\n typeof c._waitForMark !== 'function' ||\n typeof c._flushTransport !== 'function'\n ) {\n return null; // BufferingCall(prewarm) 또는 아직 미바인딩.\n }\n if (typeof c._isTransportConnected === 'function' && !c._isTransportConnected()) {\n return null; // WS 사망.\n }\n return this._call as unknown as CallSession;\n }\n\n /** `p` 와 barge-in 중 먼저 오는 걸 기다린다. 반환: p 가 이겼는가(=중단 아님). */\n private async _race(p: Promise<unknown>): Promise<boolean> {\n const winner = await Promise.race([\n p.then(() => 'main' as const),\n this._interrupted.promise.then(() => 'interrupt' as const),\n ]);\n return winner === 'main';\n }\n\n private async _runPlayoutHandshake(): Promise<void> {\n let interrupted = true;\n try {\n // 남은 자투리를 무음으로 패딩해 한 프레임으로 내보낸다.\n const tail = this._tail;\n this._tail = Buffer.alloc(0);\n if (tail.length > 0) {\n const padded = Buffer.concat([\n tail,\n Buffer.alloc(FRAME_BYTES - tail.length, ULAW_SILENCE_BYTE),\n ]);\n await this._call.sendAudio(padded);\n }\n interrupted = await this._awaitPlayout();\n } catch {\n // WS 사망 등 전송 오류 — interrupted 로 처리하고 프레임워크로 전파하지 않는다.\n interrupted = true;\n } finally {\n // ⚠️ 계약 2: 세그먼트당 정확히 한 번 — 취소되더라도 반드시 emit.\n const played = interrupted\n ? Math.min(Math.max(0, Date.now() - this._captureStartedAt), this._pushedDurationMs)\n : this._pushedDurationMs;\n this.onPlaybackFinished({ playbackPosition: played / 1000, interrupted });\n this._pushedDurationMs = 0;\n this._interrupted = new Deferred();\n }\n }\n\n /** 재생이 끝나거나 barge-in 될 때까지 대기. 반환: interrupted 여부. */\n private async _awaitPlayout(): Promise<boolean> {\n let media = this._mediaCall();\n if (!media) {\n // prewarm — 아직 재생 주체가 없다. attach 될 때까지 기다렸다가 fence 를 건다.\n if (!(await this._race(this._attached.promise))) {\n return true; // attach 전에 중단됐다.\n }\n media = this._mediaCall();\n if (!media) {\n return this._interrupted.done; // attach 됐는데도 WS 가 없다 — fence 불가.\n }\n }\n\n // send_mark 는 즉시 나가지만 send_audio 는 로컬 큐에 쌓인다. flush 로 큐를 비우지\n // 않으면 mark 가 오디오를 추월한다.\n await media._flushTransport!();\n const markName = `lk-${this._markSeq++}`;\n media._sendMark!(markName);\n const timeout = Math.round(this._pushedDurationMs) + MARK_TIMEOUT_MARGIN_MS;\n // mark echo(재생 완료)와 barge-in 중 먼저 오는 것을 기다린다.\n return !(await this._race(media._waitForMark!(markName, timeout)));\n }\n }\n\n _cached = { ClawOpsAudioInput, ClawOpsAudioOutput };\n return _cached;\n}\n","/**\n * ClawOps 전화 내장 도구를 LiveKit Toolset 으로 노출한다.\n *\n * `hang_up` / `collect_dtmf` / `send_dtmf` / `transfer_call` — 스키마와 동작을\n * `pipeline/builtin-tool-schemas.ts` 에서 그대로 재사용한다 (핸들러 중복 없음).\n *\n * 설계 메모:\n * - LiveKit 이 파는 전화 도구(`end_call`/`send_dtmf`)는 room(`get_job_context()`)을\n * 잡아 room 없이는 죽으므로 쓰지 않는다. 패턴만 가져오고 실행은 우리 것을 쓴다.\n * - `CallSession` 은 클로저 상태로 들고 간다 (prewarm -> attach 시 setCall 로 교체).\n */\n\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport { getBuiltinToolSchemas, executeBuiltinTool } from '../pipeline/builtin-tool-schemas.js';\nimport type { CallSession } from '../session.js';\n\n/** 인사말 도중 모델이 통화를 끊거나 전환하는 사고를 막는다 (ToolFlag.IGNORE_ON_ENTER). */\nconst IGNORE_ON_ENTER_TOOLS = new Set(['hang_up', 'transfer_call']);\n\nexport interface ClawOpsPhoneTools {\n /** LiveKit Toolset 인스턴스 — `agent.updateTools([...])` 에 넣는 ToolContextEntry. */\n toolset: unknown;\n /** prewarm -> attach 시 실제 통화로 교체한다. */\n setCall(call: CallSession): void;\n}\n\n/**\n * ClawOps 통화 제어 도구 묶음을 만든다.\n *\n * @param llm 동적 로드한 `@livekit/agents` 의 `llm` 네임스페이스 (`tool`/`Toolset`/`ToolFlag`).\n * @param options.enabled 활성화할 내장 도구 (`ClawOpsAgent(builtinTools=...)` 와 동일).\n * @param options.excludeNames 유저/registry 도구와 이름이 겹쳐 제외할 내장 도구 이름.\n */\nexport function createClawOpsPhoneTools(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n llm: any,\n options: { enabled: Set<BuiltinTool>; excludeNames?: Set<string> },\n): ClawOpsPhoneTools {\n const state: { call: CallSession | null } = { call: null };\n const requireCall = (): CallSession => {\n if (!state.call) {\n throw new Error('통화가 아직 연결되지 않았습니다');\n }\n return state.call;\n };\n\n const exclude = options.excludeNames ?? new Set<string>();\n const ignoreFlag = llm.ToolFlag?.IGNORE_ON_ENTER;\n\n // 활성화된 내장 도구의 정규 스키마(name/description/parameters)를 가져온다.\n const schemas = getBuiltinToolSchemas(options.enabled, 'chat') as Array<{\n function: { name: string; description: string; parameters: Record<string, unknown> };\n }>;\n\n const tools: unknown[] = [];\n for (const schema of schemas) {\n const { name, description, parameters } = schema.function;\n if (exclude.has(name)) continue; // 유저/registry 도구와 충돌 — 내장 쪽을 뺀다.\n tools.push(\n llm.tool({\n name,\n description,\n parameters, // raw JSON schema (agents-js 가 JSONSchema7 을 그대로 받는다).\n flags: IGNORE_ON_ENTER_TOOLS.has(name) ? ignoreFlag : undefined,\n // raw schema tool 의 execute 는 파싱된 args 를 그대로 받는다.\n execute: async (args: Record<string, unknown>) =>\n (await executeBuiltinTool(name, args ?? {}, requireCall())) ?? '',\n }),\n );\n }\n\n const toolset = llm.Toolset.create({ id: 'clawops_phone', tools });\n\n return {\n toolset,\n setCall: (call: CallSession) => {\n state.call = call;\n },\n };\n}\n","/**\n * LiveKitSession — 유저의 LiveKit AgentSession 을 ClawOps `Session` 계약으로 감싼다.\n *\n * `ClawOpsAgent` 는 `Session`(pipeline/base.ts)만 알고 통화를 굴린다. 이 클래스가 그\n * 계약을 구현하면서 내부적으로 LiveKit AgentSession 을 돌린다 — room 없이.\n *\n * 유저는 관용적인 LiveKit 코드를 그대로 쓴다. `Agent` 서브클래스와 `AgentSession` 설정은\n * 손대지 않아도 되고, 유일한 차이는 `session.start({ room })` 을 우리가 room 없이\n * 대신 불러준다는 것이다.\n *\n * ⚠️ v0 은 동시통화 1건이다 (`ClawOpsAgent` 가 세션 인스턴스를 통화 간 공유한다).\n * `create` 를 팩토리로 받아두었으므로, 추후 통화당 격리로 넘어갈 때 유저 코드는 그대로 둔다.\n */\n\nimport type { Logger } from 'pino';\n\nimport { NOOP_LOGGER } from '../logger.js';\nimport type { CallSession } from '../session.js';\nimport type { Session } from '../pipeline/base.js';\nimport type { ToolRegistry } from '../tool.js';\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport type { SessionTelemetry } from '../telemetry.js';\nimport { BufferingCall, attachBuffered } from '../pipeline/buffering-call.js';\nimport { loadClawOpsIO } from './io.js';\nimport type { ClawOpsAudioInputInstance, ClawOpsAudioOutputInstance } from './io.js';\nimport { createClawOpsPhoneTools } from './toolset.js';\nimport type { ClawOpsPhoneTools } from './toolset.js';\n\n/**\n * 통화당 1회 호출되는 팩토리. `[AgentSession, Agent]` 를 반환한다.\n *\n * `call` 은 착신/일반 시작에서는 실제 CallSession, 발신 prewarm 중에는 null 이다\n * (prewarm 은 응답 전에 도는데 `Session.prewarm()` 이 call 을 넘겨주지 않는다).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type LiveKitCreateFn = (call: CallSession | null) => Promise<[any, any]>;\n\nexport class LiveKitSession implements Session {\n private readonly _create: LiveKitCreateFn;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _session: any = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _agent: any = null;\n private _input: ClawOpsAudioInputInstance | null = null;\n private _output: ClawOpsAudioOutputInstance | null = null;\n private _phone: ClawOpsPhoneTools | null = null;\n private _target: CallSession | BufferingCall | null = null;\n /** 유저 원본 도구 스냅샷 (우리 도구 추가 전). attach 재적용 시 누적 방지. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _userToolCtx: any = null;\n\n private _toolRegistry: ToolRegistry | null = null;\n private _builtinTools: Set<BuiltinTool> | null = null;\n private _log: Logger = NOOP_LOGGER;\n\n // 동적 로드한 agents-js 네임스페이스 (boot 시 캐시).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _llm: any = null;\n\n constructor(create: LiveKitCreateFn) {\n this._create = create;\n }\n\n // ── ClawOpsAgent 가 duck-typing 으로 주입하는 setter ──────────\n\n setToolRegistry(registry: ToolRegistry): void {\n this._toolRegistry = registry;\n }\n\n setBuiltinTools(tools: Set<BuiltinTool>): void {\n this._builtinTools = tools;\n }\n\n setLogger(logger: Logger): void {\n this._log = logger;\n }\n\n getTelemetry(): SessionTelemetry | null {\n // 녹음/텔레메트리는 미디어 레이어에서 세션 무관하게 동작하므로 여기선 null.\n return null;\n }\n\n // ── Session 계약 ────────────────────────────────────────────\n\n async start(callSession: CallSession, tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(callSession);\n }\n\n async prewarm(tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(null);\n }\n\n async attach(callSession: CallSession): Promise<void> {\n // boot 성공 신호는 _session (부분 완료된 boot 은 _output 만 set 돼 있을 수 있다).\n if (!this._session || !this._output || !this._agent) {\n await this._boot(callSession);\n return;\n }\n const prev = this._target;\n this._output.setCall(callSession);\n this._target = callSession;\n // ⚠️ 아웃바운드 prewarm 은 _boot(null) 이 setter 실행 전에 돌아 도구를 비운 채\n // 시작한다. setter 는 이제 실행됐으므로 여기서 registry/builtin 도구를 다시 붙인다.\n await this._applyTools(callSession);\n attachBuffered(prev, callSession);\n }\n\n feedAudio(audio: Buffer, _timestamp?: number): void {\n this._input?.pushUlaw(audio);\n }\n\n async feedDtmf(digits: string): Promise<void> {\n if (!this._session) return;\n // generateReply 는 동기 메서드다. teardown 중에는 세션 activity 가 이미 정리돼\n // 던질 수 있으므로 삼킨다.\n try {\n this._session.generateReply({ userInput: `[전화 키패드 입력] ${digits}` });\n } catch (err) {\n this._log.debug({ err }, 'feedDtmf skipped (session not running)');\n }\n }\n\n async stop(): Promise<void> {\n this._input?.endInput();\n await this._closeAgentSession();\n }\n\n // ── 내부 ────────────────────────────────────────────────────\n\n private async _boot(call: CallSession | null): Promise<void> {\n // 재진입(attach 실패 폴백) 시 이전 세션이 새지 않도록 먼저 닫는다.\n await this._closeAgentSession();\n\n // agents import 와 IO 로드는 독립적이라 병렬로 (boot 1회, 이후 loadClawOpsIO 는 캐시).\n const [agents, io] = await Promise.all([\n import('@livekit/agents') as Promise<{ llm: unknown }>,\n loadClawOpsIO(),\n ]);\n this._llm = (agents as { llm: unknown }).llm;\n const { ClawOpsAudioInput, ClawOpsAudioOutput } = io;\n\n const target: CallSession | BufferingCall = call ?? new BufferingCall();\n\n const [session, agent] = await this._create(call);\n this._validate(session, agent);\n\n const input = new ClawOpsAudioInput();\n const output = new ClawOpsAudioOutput(target as unknown as {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n });\n this._input = input;\n this._output = output;\n\n session.input.audio = input;\n session.output.audio = output;\n // agents-js 의 room-less 레퍼런스(AgentsConsole)도 transcription sync 를 생략한다.\n // (transcript 는 conversation_item_added 로 별도 브리지한다 — _wireTranscripts.)\n session.output.transcription = null;\n\n this._wireTranscripts(session);\n\n this._agent = agent;\n // 우리 도구를 붙이기 전 유저 원본 도구를 스냅샷한다 (attach 재적용 시 누적 방지).\n this._userToolCtx = agent.toolCtx;\n await this._applyTools(call);\n\n // room 을 넘기지 않는 것이 이 통합의 전부다.\n await session.start({ agent });\n\n this._session = session;\n this._target = target;\n }\n\n /**\n * 유저 도구 + 브리지된 registry 도구 + 내장 전화 도구를 이름 충돌 없이 붙인다.\n *\n * 아웃바운드 prewarm(_boot(null))은 setter 실행 전에 돌아 registry/builtin 이 비어\n * 있고, 그 사이엔 통화가 연결되지 않아 도구를 쓸 일도 없다. 그래서 이때는 유저 도구만\n * 올리고, attach() 가 setter 실행 후 실제 도구를 붙이는 유일한 조립 지점이 된다.\n */\n private async _applyTools(call: CallSession | null): Promise<void> {\n if (!this._agent) return;\n const agent = this._agent;\n const llm = this._llm;\n\n if (call === null) {\n // prewarm — 아직 registry/builtin 이 없다. 유저 원본 ToolContext 를 그대로 재적용.\n await agent.updateTools(this._userToolCtx);\n return;\n }\n\n // 유저 도구(중첩 Toolset 포함)의 실제 함수 이름을 모은다 — 내장 도구와 겹칠 때\n // ToolContext.flatten() 이 \"duplicate function name\" 으로 통화를 떨구는 걸 막는다.\n const taken = new Set<string>(llm.sortedToolNames(this._userToolCtx));\n const bridged = this._bridgeRegistry(this._toolRegistry, taken);\n\n const enabled = this._builtinTools ?? new Set<BuiltinTool>();\n this._phone = createClawOpsPhoneTools(llm, { enabled, excludeNames: taken });\n this._phone.setCall(call);\n\n // ⚠️ sortedToolEntries 는 `[name, tool]` 쌍을 준다. updateTools/ToolContext 는 bare\n // tool 배열(ToolContextEntry[])을 받으므로 tool 만 추출해 넘긴다 — 쌍을 그대로 펼치면\n // 유저 도구가 유효하지 않은 엔트리로 취급돼 전부 유실된다.\n const userTools = (llm.sortedToolEntries(this._userToolCtx) as Array<[string, unknown]>).map(\n (entry) => entry[1],\n );\n await agent.updateTools([...userTools, ...bridged, this._phone.toolset]);\n }\n\n /** `@tool` / MCP 도구(ToolRegistry)를 LiveKit function tool 로 노출한다. */\n private _bridgeRegistry(registry: ToolRegistry | null, taken: Set<string>): unknown[] {\n if (!registry) return [];\n const llm = this._llm;\n const out: unknown[] = [];\n for (const def of registry.toOpenAITools()) {\n const name = def.function.name;\n if (taken.has(name)) continue; // 유저 도구가 우선.\n taken.add(name);\n out.push(\n llm.tool({\n name,\n description: def.function.description,\n parameters: def.function.parameters,\n execute: async (args: Record<string, unknown>) => {\n const result = await registry.call(name, args ?? {});\n return typeof result === 'string' ? result : JSON.stringify(result);\n },\n }),\n );\n }\n return out;\n }\n\n /**\n * LiveKit 의 최종 대화 항목을 ClawOps `transcript` 훅으로 흘려보낸다.\n *\n * 네이티브 세션(OpenAI/Gemini/Pipeline)은 `call._emit('transcript', role, text)` 를\n * 부른다. `@on('transcript')` 로 트랜스크립트를 모으는 기존 앱이 세션만 바꿔도 그대로\n * 돌게 하려면 여기서 같은 계약을 재현한다.\n *\n * `conversation_item_added` 는 user·assistant 최종 메시지가 히스토리에 커밋될 때 한\n * 번씩 뜬다 — 부분(interim) 중복 없이 최종만 얻는다. item 이 handoff 등 ChatMessage 가\n * 아니면 role/textContent 가 없어 자연히 걸러진다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _wireTranscripts(session: any): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n session.on('conversation_item_added', (ev: any) => {\n const item = ev?.item;\n const role = item?.role;\n if (role !== 'user' && role !== 'assistant') return;\n const text = item?.textContent;\n if (!text) return;\n const target = this._target as unknown as { _emit?: (event: string, ...args: unknown[]) => void };\n // Node 의 _emit 은 동기다 — Python 처럼 fire-and-forget task 를 추적할 필요 없다.\n // prewarm 중이면 _target 이 BufferingCall 이라 _emit 이 드롭 카운트로 흡수한다.\n target?._emit?.('transcript', role, text);\n });\n }\n\n private async _closeAgentSession(): Promise<void> {\n if (this._output) {\n this._output.close();\n this._output = null;\n }\n if (this._session) {\n const s = this._session;\n this._session = null;\n try {\n if (typeof s.aclose === 'function') {\n await s.aclose();\n } else if (typeof s.close === 'function') {\n await s.close();\n }\n } catch (err) {\n this._log.warn({ err }, 'AgentSession close failed');\n }\n }\n }\n\n /**\n * LiveKit 이 조용히 넘어가는 두 조합을 우리가 막는다 — 실수하면 \"말을 못 하는\n * 에이전트\"가 아무 예외 없이 배포된다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _validate(session: any, agent: any): void {\n let audioOutput: unknown;\n let tts: unknown;\n try {\n const llm = agent?.llm ?? session?.llm;\n tts = agent?.tts ?? session?.tts;\n audioOutput = llm?.capabilities?.audioOutput;\n } catch {\n return; // capabilities 를 못 읽으면 검증 생략 (거짓 양성 방지).\n }\n if (audioOutput === undefined || audioOutput === null) {\n return; // realtime 모델이 아니다 (일반 LLM + TTS 파이프라인).\n }\n if (audioOutput === false && (tts === undefined || tts === null)) {\n throw new Error(\n \"RealtimeModel 이 modalities=['text'] 인데 tts 가 없습니다 — 에이전트가 소리를 \" +\n \"내지 못합니다. AgentSession(tts=...) 를 주거나 modalities 에 'audio' 를 넣으세요.\",\n );\n }\n if (audioOutput === true && tts !== undefined && tts !== null) {\n this._log.warn(\n 'RealtimeModel 이 오디오를 직접 출력하므로 tts 가 무시됩니다. TTS 를 쓰려면 ' +\n \"RealtimeModel(modalities=['text']) 로 설정하세요.\",\n );\n }\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from 'pino';
|
|
2
|
-
import { C as CallSession, S as Session, b as ToolRegistry, B as BuiltinTool, c as SessionTelemetry } from '../../base-
|
|
2
|
+
import { C as CallSession, S as Session, b as ToolRegistry, B as BuiltinTool, c as SessionTelemetry } from '../../base-BalWsEyb.cjs';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from 'pino';
|
|
2
|
-
import { C as CallSession, S as Session, b as ToolRegistry, B as BuiltinTool, c as SessionTelemetry } from '../../base-
|
|
2
|
+
import { C as CallSession, S as Session, b as ToolRegistry, B as BuiltinTool, c as SessionTelemetry } from '../../base-BalWsEyb.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getBuiltinToolSchemas, executeBuiltinTool, NOOP_LOGGER, attachBuffered, BufferingCall, pcm16ToUlaw, DECODE_TABLE } from '../../chunk-
|
|
1
|
+
import { getBuiltinToolSchemas, executeBuiltinTool, NOOP_LOGGER, attachBuffered, BufferingCall, pcm16ToUlaw, DECODE_TABLE } from '../../chunk-2OGQSWTW.js';
|
|
2
|
+
import '../../chunk-CKPVV6SZ.js';
|
|
2
3
|
|
|
3
4
|
// src/agent/livekit/io.ts
|
|
4
5
|
var SAMPLE_RATE = 8e3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/agent/livekit/io.ts","../../../src/agent/livekit/toolset.ts","../../../src/agent/livekit/session.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * LiveKit AudioInput/AudioOutput <-> ClawOps Media WS 브리지.\n *\n * LiveKit 의 AgentSession 은 room 없이도 동작한다 — `session.input.audio` /\n * `session.output.audio` 에 커스텀 구현을 꽂으면 된다. 이 모듈이 그 구현이다.\n *\n * 전화망 wire format 은 G.711 μ-law 8kHz, 160바이트(20ms) 프레임이다. AudioOutput 이\n * `sampleRate=8000` 을 선언하므로 프레임워크가 TTS 출력을 8kHz 로 리샘플해준다 —\n * 우리는 μ-law 인코딩만 한다.\n *\n * 참조 구현: agents-js `voice/console_io.ts` 의 `TcpAudioInput`/`TcpAudioOutput` —\n * 같은 문제(비-WebRTC transport 를 AgentSession 에 물리기)를 푸는 LiveKit 자신의 코드다.\n * ⚠️ Python(clawops-python) 과 달리 agents-js 는 `AudioInput` 베이스와\n * `TranscriptionSynchronizer` 를 public export 하지 않는다. 그래서\n * - AudioInput 은 구조적으로 구현(ReadableStream 노출)하고,\n * - 재생 완료(playout) 판정은 `TcpAudioOutput` 과 동일한 Future 핸드셰이크로 하되\n * 완료 신호를 Media WS `mark` echo 로 받는다. (LiveKit 자신의 `AgentsConsole` 도\n * room-less 경로에서 transcription sync 를 생략하므로 우리도 생략한다.)\n *\n * ⚠️ AudioOutput 계약 2가지 — 어기면 조용히 깨진다:\n * 1. 첫 프레임에서 `onPlaybackStarted()` 를 반드시 호출한다. 빼면 assistant 메시지가\n * 히스토리에서 통째로 사라진다.\n * 2. `onPlaybackFinished()` 는 flush 세그먼트당 정확히 한 번, 취소되더라도 반드시\n * 호출한다. 안 그러면 프레임워크의 재생 회계가 어긋나 wait_for_playout 이 영구 대기한다.\n */\n\nimport { pcm16ToUlaw, DECODE_TABLE } from '../audio.js';\nimport type { CallSession } from '../session.js';\n\nexport const SAMPLE_RATE = 8000;\n/** 전화망 wire rate. AudioOutput 이 선언하면 프레임워크가 여기 맞춰 리샘플한다. */\n\nexport const FRAME_BYTES = 160;\n/** μ-law 20ms = 160바이트. */\n\nexport const ULAW_SILENCE_BYTE = 0xff;\n/** μ-law 무음 — 마지막 프레임 패딩용. */\n\nconst MARK_TIMEOUT_MARGIN_MS = 10_000;\n/** mark 대기 timeout = 밀어넣은 오디오 길이(ms) + 이 여유. */\n\n/** ClawOpsAudioOutput 이 오디오를 흘려보내는 대상(실 통화 또는 prewarm 버퍼)의 최소 계약. */\ninterface AudioSink {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n}\n\n/** 외부에서 참조할 인스턴스 타입 (실제 클래스는 optional dep 로드 후 팩토리에서 정의된다). */\nexport interface ClawOpsAudioInputInstance {\n readonly stream: unknown;\n pushUlaw(ulaw: Buffer): void;\n endInput(): void;\n close(): Promise<void>;\n onAttached(): void;\n onDetached(): void;\n}\n\nexport interface ClawOpsAudioOutputInstance {\n setCall(call: CallSession): void;\n close(): void;\n // AudioOutput 상속 메서드(captureFrame/flush/clearBuffer/onPlaybackStarted/...)는\n // 프레임워크가 호출하므로 여기 노출하지 않는다.\n}\n\nexport interface ClawOpsIO {\n ClawOpsAudioInput: new () => ClawOpsAudioInputInstance;\n ClawOpsAudioOutput: new (call: AudioSink) => ClawOpsAudioOutputInstance;\n}\n\n/** 외부 resolve 를 노출하는 1회성 Deferred (agents-js 의 non-exported `Future` 대응). */\nclass Deferred<T = void> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _done = false;\n constructor() {\n this.promise = new Promise<T>((resolve) => {\n this._resolve = resolve;\n });\n }\n get done(): boolean {\n return this._done;\n }\n resolve(value: T): void {\n if (this._done) return;\n this._done = true;\n this._resolve(value);\n }\n}\n\nlet _cached: ClawOpsIO | null = null;\n\n/**\n * agents-js / rtc-node 를 동적 로드해 IO 클래스를 정의한다 (1회 캐시).\n *\n * 클래스가 `voice.AudioOutput`(optional dep)을 상속하므로 top-level 이 아니라\n * import 성공 후 팩토리 안에서 정의해야 한다.\n */\nexport async function loadClawOpsIO(): Promise<ClawOpsIO> {\n if (_cached) return _cached;\n\n // 두 optional dep 를 병렬 로드한다 (독립적, boot 1회).\n const [agents, rtc] = (await Promise.all([\n import('@livekit/agents'),\n import('@livekit/rtc-node'),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ])) as [any, any];\n\n // agents-js 는 AudioOutput 생성자(instance-init)에서 log() 를 부르고, 로거가 초기화되지\n // 않았으면 throw 한다. room-less 는 worker/cli(initializeLogger 를 대신 부르는)를 거치지\n // 않으므로 우리가 초기화한다 — 단, 유저가 이미 했으면(loggerOptions() 존재) 건드리지 않는다.\n if (\n typeof agents.initializeLogger === 'function' &&\n (typeof agents.loggerOptions !== 'function' || !agents.loggerOptions())\n ) {\n agents.initializeLogger({ pretty: false, level: process.env['LIVEKIT_LOG_LEVEL'] ?? 'info' });\n }\n\n const AudioOutputBase = agents.voice.AudioOutput;\n const AudioFrame = rtc.AudioFrame;\n\n /**\n * 통화 인바운드 오디오(μ-law) -> rtc.AudioFrame 스트림.\n *\n * agents-js `AudioInput` 은 public export 되지 않으므로 상속하지 않고, 프레임워크가\n * 실제로 읽는 계약(`.stream` + onAttached/onDetached/close)만 구조적으로 구현한다.\n * `session.input.audio = <this>` 로 꽂으면 프레임워크가 `.stream` 을 소비한다.\n */\n class ClawOpsAudioInput implements ClawOpsAudioInputInstance {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _controller: ReadableStreamDefaultController<any> | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _stream: ReadableStream<any>;\n private _closed = false;\n\n constructor() {\n this._stream = new ReadableStream({\n start: (c) => {\n this._controller = c;\n },\n cancel: () => {\n this._closed = true;\n },\n });\n }\n\n get stream(): unknown {\n return this._stream;\n }\n\n /** G.711 μ-law 청크를 PCM16 프레임으로 바꿔 스트림에 밀어넣는다. */\n pushUlaw(ulaw: Buffer): void {\n if (this._closed || !this._controller || ulaw.length === 0) return;\n // μ-law 를 곧장 Int16Array 로 디코드한다 (중간 Buffer + 재읽기 루프 생략).\n const samples = new Int16Array(ulaw.length);\n for (let i = 0; i < ulaw.length; i++) {\n samples[i] = DECODE_TABLE[ulaw[i]!]!;\n }\n const frame = new AudioFrame(samples, SAMPLE_RATE, 1, samples.length);\n try {\n this._controller.enqueue(frame);\n } catch {\n // 스트림이 이미 닫혔다 — 무시.\n }\n }\n\n /** 스트림 종료 — 리더가 완료를 본다. */\n endInput(): void {\n this._closeStream();\n }\n\n async close(): Promise<void> {\n this._closeStream();\n }\n\n onAttached(): void {}\n onDetached(): void {}\n\n private _closeStream(): void {\n if (this._closed) return;\n this._closed = true;\n try {\n this._controller?.close();\n } catch {\n // 이미 닫힘.\n }\n }\n }\n\n /**\n * AgentSession 출력 -> μ-law 160바이트 프레임 -> CallSession.sendAudio().\n *\n * 재생 완료 판정은 Media WS `mark` 로 한다 — 플랫폼이 큐의 오디오를 다 내보낸 뒤\n * mark 를 에코한다. prewarm 중(BufferingCall)에는 media WS 가 없으므로 attach 될\n * 때까지 기다렸다가 fence 를 건다(그 전엔 아직 아무것도 재생되지 않는다).\n *\n * 구조는 agents-js `TcpAudioOutput` 을 따르고, 재생 완료 신호만 broker 대신 mark 로 받는다.\n */\n class ClawOpsAudioOutput extends AudioOutputBase implements ClawOpsAudioOutputInstance {\n private _call: AudioSink;\n private _pushedDurationMs = 0;\n private _captureStartedAt = 0;\n private _tail: Buffer = Buffer.alloc(0);\n private _flushPromise: Promise<void> | null = null;\n private _flushInProgress = false;\n private _interrupted = new Deferred();\n private _attached = new Deferred();\n private _markSeq = 0;\n private _closed = false;\n\n constructor(call: AudioSink) {\n // AudioOutput(sampleRate?, nextInChain?, capabilities?)\n super(SAMPLE_RATE, undefined, { pause: false });\n this._call = call;\n }\n\n /**\n * attach() 시 실제 CallSession 으로 교체한다 (prewarm -> 실제 통화).\n *\n * prewarm 중 버퍼링된 오디오는 지금부터 실제 재생되므로 경과 기준점을 여기로 옮긴다 —\n * 안 그러면 링 구간까지 \"재생됐다\"고 계산돼 barge-in 절단 위치가 뒤로 밀린다.\n */\n setCall(call: CallSession): void {\n this._call = call as unknown as AudioSink;\n if (this._pushedDurationMs > 0) {\n this._captureStartedAt = Date.now();\n }\n this._attached.resolve();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async captureFrame(frame: any): Promise<void> {\n // ⚠️ close() 후엔 base 를 건드리지 않는다 — super.captureFrame 이 세그먼트 카운트를\n // 올린 뒤 여기서 bail 하면 onPlaybackFinished 가 안 나가 teardown 시 wait 이 샌다.\n if (this._closed) return;\n await super.captureFrame(frame);\n\n // 이전 세그먼트 flush 가 진행 중이면 기다린다 (TcpAudioOutput 과 동일).\n if (this._flushInProgress && this._flushPromise) {\n await this._flushPromise;\n }\n\n if (this._pushedDurationMs === 0) {\n this._captureStartedAt = Date.now();\n // ⚠️ 계약 1: 빼면 assistant 메시지가 히스토리에서 사라진다.\n this.onPlaybackStarted(Date.now());\n }\n\n this._pushedDurationMs += (frame.samplesPerChannel / frame.sampleRate) * 1000;\n\n // frame.data 는 Int16Array(PCM16). LE 플랫폼에서 그대로 바이트 뷰로 읽는다.\n const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);\n const encoded = pcm16ToUlaw(pcm);\n // 대부분의 프레임은 160바이트 정렬돼 tail 이 비어 있다 — 그때는 concat(추가 할당+복사)을 건너뛴다.\n const ulaw = this._tail.length === 0 ? encoded : Buffer.concat([this._tail, encoded]);\n let off = 0;\n while (off + FRAME_BYTES <= ulaw.length) {\n await this._call.sendAudio(ulaw.subarray(off, off + FRAME_BYTES));\n off += FRAME_BYTES;\n }\n this._tail = Buffer.from(ulaw.subarray(off));\n }\n\n flush(): void {\n super.flush();\n if (this._pushedDurationMs > 0) {\n this._interrupted = new Deferred();\n this._flushInProgress = true;\n this._flushPromise = this._runPlayoutHandshake().finally(() => {\n this._flushInProgress = false;\n });\n } else {\n this._tail = Buffer.alloc(0);\n }\n }\n\n clearBuffer(): void {\n // ⚠️ 여기서 onPlaybackFinished 를 부르지 않는다 — 핸드셰이크가 interrupted 를\n // 보고 한 번만 쏜다.\n this._tail = Buffer.alloc(0);\n if (this._pushedDurationMs > 0) {\n this._interrupted.resolve();\n }\n // prewarm 중이면 BufferingCall.clearAudio 가 버퍼를 비우고, 실 통화면 media WS 큐를 flush.\n this._call.clearAudio();\n }\n\n /** 세션 종료 시 남은 대기를 깨워 정리한다. */\n close(): void {\n this._closed = true;\n this._interrupted.resolve();\n this._attached.resolve();\n }\n\n // ── 내부 ───────────────────────────────────────────────────\n\n /** 실 통화이고 transport 가 살아 있으면 mark 훅을 가진 CallSession 을 돌려준다. */\n private _mediaCall(): CallSession | null {\n const c = this._call as unknown as {\n _sendMark?: unknown;\n _waitForMark?: unknown;\n _flushTransport?: unknown;\n _isTransportConnected?: () => boolean;\n };\n if (\n typeof c._sendMark !== 'function' ||\n typeof c._waitForMark !== 'function' ||\n typeof c._flushTransport !== 'function'\n ) {\n return null; // BufferingCall(prewarm) 또는 아직 미바인딩.\n }\n if (typeof c._isTransportConnected === 'function' && !c._isTransportConnected()) {\n return null; // WS 사망.\n }\n return this._call as unknown as CallSession;\n }\n\n /** `p` 와 barge-in 중 먼저 오는 걸 기다린다. 반환: p 가 이겼는가(=중단 아님). */\n private async _race(p: Promise<unknown>): Promise<boolean> {\n const winner = await Promise.race([\n p.then(() => 'main' as const),\n this._interrupted.promise.then(() => 'interrupt' as const),\n ]);\n return winner === 'main';\n }\n\n private async _runPlayoutHandshake(): Promise<void> {\n let interrupted = true;\n try {\n // 남은 자투리를 무음으로 패딩해 한 프레임으로 내보낸다.\n const tail = this._tail;\n this._tail = Buffer.alloc(0);\n if (tail.length > 0) {\n const padded = Buffer.concat([\n tail,\n Buffer.alloc(FRAME_BYTES - tail.length, ULAW_SILENCE_BYTE),\n ]);\n await this._call.sendAudio(padded);\n }\n interrupted = await this._awaitPlayout();\n } catch {\n // WS 사망 등 전송 오류 — interrupted 로 처리하고 프레임워크로 전파하지 않는다.\n interrupted = true;\n } finally {\n // ⚠️ 계약 2: 세그먼트당 정확히 한 번 — 취소되더라도 반드시 emit.\n const played = interrupted\n ? Math.min(Math.max(0, Date.now() - this._captureStartedAt), this._pushedDurationMs)\n : this._pushedDurationMs;\n this.onPlaybackFinished({ playbackPosition: played / 1000, interrupted });\n this._pushedDurationMs = 0;\n this._interrupted = new Deferred();\n }\n }\n\n /** 재생이 끝나거나 barge-in 될 때까지 대기. 반환: interrupted 여부. */\n private async _awaitPlayout(): Promise<boolean> {\n let media = this._mediaCall();\n if (!media) {\n // prewarm — 아직 재생 주체가 없다. attach 될 때까지 기다렸다가 fence 를 건다.\n if (!(await this._race(this._attached.promise))) {\n return true; // attach 전에 중단됐다.\n }\n media = this._mediaCall();\n if (!media) {\n return this._interrupted.done; // attach 됐는데도 WS 가 없다 — fence 불가.\n }\n }\n\n // send_mark 는 즉시 나가지만 send_audio 는 로컬 큐에 쌓인다. flush 로 큐를 비우지\n // 않으면 mark 가 오디오를 추월한다.\n await media._flushTransport!();\n const markName = `lk-${this._markSeq++}`;\n media._sendMark!(markName);\n const timeout = Math.round(this._pushedDurationMs) + MARK_TIMEOUT_MARGIN_MS;\n // mark echo(재생 완료)와 barge-in 중 먼저 오는 것을 기다린다.\n return !(await this._race(media._waitForMark!(markName, timeout)));\n }\n }\n\n _cached = { ClawOpsAudioInput, ClawOpsAudioOutput };\n return _cached;\n}\n","/**\n * ClawOps 전화 내장 도구를 LiveKit Toolset 으로 노출한다.\n *\n * `hang_up` / `collect_dtmf` / `send_dtmf` / `transfer_call` — 스키마와 동작을\n * `pipeline/builtin-tool-schemas.ts` 에서 그대로 재사용한다 (핸들러 중복 없음).\n *\n * 설계 메모:\n * - LiveKit 이 파는 전화 도구(`end_call`/`send_dtmf`)는 room(`get_job_context()`)을\n * 잡아 room 없이는 죽으므로 쓰지 않는다. 패턴만 가져오고 실행은 우리 것을 쓴다.\n * - `CallSession` 은 클로저 상태로 들고 간다 (prewarm -> attach 시 setCall 로 교체).\n */\n\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport { getBuiltinToolSchemas, executeBuiltinTool } from '../pipeline/builtin-tool-schemas.js';\nimport type { CallSession } from '../session.js';\n\n/** 인사말 도중 모델이 통화를 끊거나 전환하는 사고를 막는다 (ToolFlag.IGNORE_ON_ENTER). */\nconst IGNORE_ON_ENTER_TOOLS = new Set(['hang_up', 'transfer_call']);\n\nexport interface ClawOpsPhoneTools {\n /** LiveKit Toolset 인스턴스 — `agent.updateTools([...])` 에 넣는 ToolContextEntry. */\n toolset: unknown;\n /** prewarm -> attach 시 실제 통화로 교체한다. */\n setCall(call: CallSession): void;\n}\n\n/**\n * ClawOps 통화 제어 도구 묶음을 만든다.\n *\n * @param llm 동적 로드한 `@livekit/agents` 의 `llm` 네임스페이스 (`tool`/`Toolset`/`ToolFlag`).\n * @param options.enabled 활성화할 내장 도구 (`ClawOpsAgent(builtinTools=...)` 와 동일).\n * @param options.excludeNames 유저/registry 도구와 이름이 겹쳐 제외할 내장 도구 이름.\n */\nexport function createClawOpsPhoneTools(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n llm: any,\n options: { enabled: Set<BuiltinTool>; excludeNames?: Set<string> },\n): ClawOpsPhoneTools {\n const state: { call: CallSession | null } = { call: null };\n const requireCall = (): CallSession => {\n if (!state.call) {\n throw new Error('통화가 아직 연결되지 않았습니다');\n }\n return state.call;\n };\n\n const exclude = options.excludeNames ?? new Set<string>();\n const ignoreFlag = llm.ToolFlag?.IGNORE_ON_ENTER;\n\n // 활성화된 내장 도구의 정규 스키마(name/description/parameters)를 가져온다.\n const schemas = getBuiltinToolSchemas(options.enabled, 'chat') as Array<{\n function: { name: string; description: string; parameters: Record<string, unknown> };\n }>;\n\n const tools: unknown[] = [];\n for (const schema of schemas) {\n const { name, description, parameters } = schema.function;\n if (exclude.has(name)) continue; // 유저/registry 도구와 충돌 — 내장 쪽을 뺀다.\n tools.push(\n llm.tool({\n name,\n description,\n parameters, // raw JSON schema (agents-js 가 JSONSchema7 을 그대로 받는다).\n flags: IGNORE_ON_ENTER_TOOLS.has(name) ? ignoreFlag : undefined,\n // raw schema tool 의 execute 는 파싱된 args 를 그대로 받는다.\n execute: async (args: Record<string, unknown>) =>\n (await executeBuiltinTool(name, args ?? {}, requireCall())) ?? '',\n }),\n );\n }\n\n const toolset = llm.Toolset.create({ id: 'clawops_phone', tools });\n\n return {\n toolset,\n setCall: (call: CallSession) => {\n state.call = call;\n },\n };\n}\n","/**\n * LiveKitSession — 유저의 LiveKit AgentSession 을 ClawOps `Session` 계약으로 감싼다.\n *\n * `ClawOpsAgent` 는 `Session`(pipeline/base.ts)만 알고 통화를 굴린다. 이 클래스가 그\n * 계약을 구현하면서 내부적으로 LiveKit AgentSession 을 돌린다 — room 없이.\n *\n * 유저는 관용적인 LiveKit 코드를 그대로 쓴다. `Agent` 서브클래스와 `AgentSession` 설정은\n * 손대지 않아도 되고, 유일한 차이는 `session.start({ room })` 을 우리가 room 없이\n * 대신 불러준다는 것이다.\n *\n * ⚠️ v0 은 동시통화 1건이다 (`ClawOpsAgent` 가 세션 인스턴스를 통화 간 공유한다).\n * `create` 를 팩토리로 받아두었으므로, 추후 통화당 격리로 넘어갈 때 유저 코드는 그대로 둔다.\n */\n\nimport type { Logger } from 'pino';\n\nimport { NOOP_LOGGER } from '../logger.js';\nimport type { CallSession } from '../session.js';\nimport type { Session } from '../pipeline/base.js';\nimport type { ToolRegistry } from '../tool.js';\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport type { SessionTelemetry } from '../telemetry.js';\nimport { BufferingCall, attachBuffered } from '../pipeline/buffering-call.js';\nimport { loadClawOpsIO } from './io.js';\nimport type { ClawOpsAudioInputInstance, ClawOpsAudioOutputInstance } from './io.js';\nimport { createClawOpsPhoneTools } from './toolset.js';\nimport type { ClawOpsPhoneTools } from './toolset.js';\n\n/**\n * 통화당 1회 호출되는 팩토리. `[AgentSession, Agent]` 를 반환한다.\n *\n * `call` 은 착신/일반 시작에서는 실제 CallSession, 발신 prewarm 중에는 null 이다\n * (prewarm 은 응답 전에 도는데 `Session.prewarm()` 이 call 을 넘겨주지 않는다).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type LiveKitCreateFn = (call: CallSession | null) => Promise<[any, any]>;\n\nexport class LiveKitSession implements Session {\n private readonly _create: LiveKitCreateFn;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _session: any = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _agent: any = null;\n private _input: ClawOpsAudioInputInstance | null = null;\n private _output: ClawOpsAudioOutputInstance | null = null;\n private _phone: ClawOpsPhoneTools | null = null;\n private _target: CallSession | BufferingCall | null = null;\n /** 유저 원본 도구 스냅샷 (우리 도구 추가 전). attach 재적용 시 누적 방지. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _userToolCtx: any = null;\n\n private _toolRegistry: ToolRegistry | null = null;\n private _builtinTools: Set<BuiltinTool> | null = null;\n private _log: Logger = NOOP_LOGGER;\n\n // 동적 로드한 agents-js 네임스페이스 (boot 시 캐시).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _llm: any = null;\n\n constructor(create: LiveKitCreateFn) {\n this._create = create;\n }\n\n // ── ClawOpsAgent 가 duck-typing 으로 주입하는 setter ──────────\n\n setToolRegistry(registry: ToolRegistry): void {\n this._toolRegistry = registry;\n }\n\n setBuiltinTools(tools: Set<BuiltinTool>): void {\n this._builtinTools = tools;\n }\n\n setLogger(logger: Logger): void {\n this._log = logger;\n }\n\n getTelemetry(): SessionTelemetry | null {\n // 녹음/텔레메트리는 미디어 레이어에서 세션 무관하게 동작하므로 여기선 null.\n return null;\n }\n\n // ── Session 계약 ────────────────────────────────────────────\n\n async start(callSession: CallSession, tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(callSession);\n }\n\n async prewarm(tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(null);\n }\n\n async attach(callSession: CallSession): Promise<void> {\n // boot 성공 신호는 _session (부분 완료된 boot 은 _output 만 set 돼 있을 수 있다).\n if (!this._session || !this._output || !this._agent) {\n await this._boot(callSession);\n return;\n }\n const prev = this._target;\n this._output.setCall(callSession);\n this._target = callSession;\n // ⚠️ 아웃바운드 prewarm 은 _boot(null) 이 setter 실행 전에 돌아 도구를 비운 채\n // 시작한다. setter 는 이제 실행됐으므로 여기서 registry/builtin 도구를 다시 붙인다.\n await this._applyTools(callSession);\n attachBuffered(prev, callSession);\n }\n\n feedAudio(audio: Buffer, _timestamp?: number): void {\n this._input?.pushUlaw(audio);\n }\n\n async feedDtmf(digits: string): Promise<void> {\n if (!this._session) return;\n // generateReply 는 동기 메서드다. teardown 중에는 세션 activity 가 이미 정리돼\n // 던질 수 있으므로 삼킨다.\n try {\n this._session.generateReply({ userInput: `[전화 키패드 입력] ${digits}` });\n } catch (err) {\n this._log.debug({ err }, 'feedDtmf skipped (session not running)');\n }\n }\n\n async stop(): Promise<void> {\n this._input?.endInput();\n await this._closeAgentSession();\n }\n\n // ── 내부 ────────────────────────────────────────────────────\n\n private async _boot(call: CallSession | null): Promise<void> {\n // 재진입(attach 실패 폴백) 시 이전 세션이 새지 않도록 먼저 닫는다.\n await this._closeAgentSession();\n\n // agents import 와 IO 로드는 독립적이라 병렬로 (boot 1회, 이후 loadClawOpsIO 는 캐시).\n const [agents, io] = await Promise.all([\n import('@livekit/agents') as Promise<{ llm: unknown }>,\n loadClawOpsIO(),\n ]);\n this._llm = (agents as { llm: unknown }).llm;\n const { ClawOpsAudioInput, ClawOpsAudioOutput } = io;\n\n const target: CallSession | BufferingCall = call ?? new BufferingCall();\n\n const [session, agent] = await this._create(call);\n this._validate(session, agent);\n\n const input = new ClawOpsAudioInput();\n const output = new ClawOpsAudioOutput(target as unknown as {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n });\n this._input = input;\n this._output = output;\n\n session.input.audio = input;\n session.output.audio = output;\n // agents-js 의 room-less 레퍼런스(AgentsConsole)도 transcription sync 를 생략한다.\n // (transcript 는 conversation_item_added 로 별도 브리지한다 — _wireTranscripts.)\n session.output.transcription = null;\n\n this._wireTranscripts(session);\n\n this._agent = agent;\n // 우리 도구를 붙이기 전 유저 원본 도구를 스냅샷한다 (attach 재적용 시 누적 방지).\n this._userToolCtx = agent.toolCtx;\n await this._applyTools(call);\n\n // room 을 넘기지 않는 것이 이 통합의 전부다.\n await session.start({ agent });\n\n this._session = session;\n this._target = target;\n }\n\n /**\n * 유저 도구 + 브리지된 registry 도구 + 내장 전화 도구를 이름 충돌 없이 붙인다.\n *\n * 아웃바운드 prewarm(_boot(null))은 setter 실행 전에 돌아 registry/builtin 이 비어\n * 있고, 그 사이엔 통화가 연결되지 않아 도구를 쓸 일도 없다. 그래서 이때는 유저 도구만\n * 올리고, attach() 가 setter 실행 후 실제 도구를 붙이는 유일한 조립 지점이 된다.\n */\n private async _applyTools(call: CallSession | null): Promise<void> {\n if (!this._agent) return;\n const agent = this._agent;\n const llm = this._llm;\n\n if (call === null) {\n // prewarm — 아직 registry/builtin 이 없다. 유저 원본 ToolContext 를 그대로 재적용.\n await agent.updateTools(this._userToolCtx);\n return;\n }\n\n // 유저 도구(중첩 Toolset 포함)의 실제 함수 이름을 모은다 — 내장 도구와 겹칠 때\n // ToolContext.flatten() 이 \"duplicate function name\" 으로 통화를 떨구는 걸 막는다.\n const taken = new Set<string>(llm.sortedToolNames(this._userToolCtx));\n const bridged = this._bridgeRegistry(this._toolRegistry, taken);\n\n const enabled = this._builtinTools ?? new Set<BuiltinTool>();\n this._phone = createClawOpsPhoneTools(llm, { enabled, excludeNames: taken });\n this._phone.setCall(call);\n\n // ⚠️ sortedToolEntries 는 `[name, tool]` 쌍을 준다. updateTools/ToolContext 는 bare\n // tool 배열(ToolContextEntry[])을 받으므로 tool 만 추출해 넘긴다 — 쌍을 그대로 펼치면\n // 유저 도구가 유효하지 않은 엔트리로 취급돼 전부 유실된다.\n const userTools = (llm.sortedToolEntries(this._userToolCtx) as Array<[string, unknown]>).map(\n (entry) => entry[1],\n );\n await agent.updateTools([...userTools, ...bridged, this._phone.toolset]);\n }\n\n /** `@tool` / MCP 도구(ToolRegistry)를 LiveKit function tool 로 노출한다. */\n private _bridgeRegistry(registry: ToolRegistry | null, taken: Set<string>): unknown[] {\n if (!registry) return [];\n const llm = this._llm;\n const out: unknown[] = [];\n for (const def of registry.toOpenAITools()) {\n const name = def.function.name;\n if (taken.has(name)) continue; // 유저 도구가 우선.\n taken.add(name);\n out.push(\n llm.tool({\n name,\n description: def.function.description,\n parameters: def.function.parameters,\n execute: async (args: Record<string, unknown>) => {\n const result = await registry.call(name, args ?? {});\n return typeof result === 'string' ? result : JSON.stringify(result);\n },\n }),\n );\n }\n return out;\n }\n\n /**\n * LiveKit 의 최종 대화 항목을 ClawOps `transcript` 훅으로 흘려보낸다.\n *\n * 네이티브 세션(OpenAI/Gemini/Pipeline)은 `call._emit('transcript', role, text)` 를\n * 부른다. `@on('transcript')` 로 트랜스크립트를 모으는 기존 앱이 세션만 바꿔도 그대로\n * 돌게 하려면 여기서 같은 계약을 재현한다.\n *\n * `conversation_item_added` 는 user·assistant 최종 메시지가 히스토리에 커밋될 때 한\n * 번씩 뜬다 — 부분(interim) 중복 없이 최종만 얻는다. item 이 handoff 등 ChatMessage 가\n * 아니면 role/textContent 가 없어 자연히 걸러진다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _wireTranscripts(session: any): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n session.on('conversation_item_added', (ev: any) => {\n const item = ev?.item;\n const role = item?.role;\n if (role !== 'user' && role !== 'assistant') return;\n const text = item?.textContent;\n if (!text) return;\n const target = this._target as unknown as { _emit?: (event: string, ...args: unknown[]) => void };\n // Node 의 _emit 은 동기다 — Python 처럼 fire-and-forget task 를 추적할 필요 없다.\n // prewarm 중이면 _target 이 BufferingCall 이라 _emit 이 드롭 카운트로 흡수한다.\n target?._emit?.('transcript', role, text);\n });\n }\n\n private async _closeAgentSession(): Promise<void> {\n if (this._output) {\n this._output.close();\n this._output = null;\n }\n if (this._session) {\n const s = this._session;\n this._session = null;\n try {\n if (typeof s.aclose === 'function') {\n await s.aclose();\n } else if (typeof s.close === 'function') {\n await s.close();\n }\n } catch (err) {\n this._log.warn({ err }, 'AgentSession close failed');\n }\n }\n }\n\n /**\n * LiveKit 이 조용히 넘어가는 두 조합을 우리가 막는다 — 실수하면 \"말을 못 하는\n * 에이전트\"가 아무 예외 없이 배포된다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _validate(session: any, agent: any): void {\n let audioOutput: unknown;\n let tts: unknown;\n try {\n const llm = agent?.llm ?? session?.llm;\n tts = agent?.tts ?? session?.tts;\n audioOutput = llm?.capabilities?.audioOutput;\n } catch {\n return; // capabilities 를 못 읽으면 검증 생략 (거짓 양성 방지).\n }\n if (audioOutput === undefined || audioOutput === null) {\n return; // realtime 모델이 아니다 (일반 LLM + TTS 파이프라인).\n }\n if (audioOutput === false && (tts === undefined || tts === null)) {\n throw new Error(\n \"RealtimeModel 이 modalities=['text'] 인데 tts 가 없습니다 — 에이전트가 소리를 \" +\n \"내지 못합니다. AgentSession(tts=...) 를 주거나 modalities 에 'audio' 를 넣으세요.\",\n );\n }\n if (audioOutput === true && tts !== undefined && tts !== null) {\n this._log.warn(\n 'RealtimeModel 이 오디오를 직접 출력하므로 tts 가 무시됩니다. TTS 를 쓰려면 ' +\n \"RealtimeModel(modalities=['text']) 로 설정하세요.\",\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/agent/livekit/io.ts","../../../src/agent/livekit/toolset.ts","../../../src/agent/livekit/session.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * LiveKit AudioInput/AudioOutput <-> ClawOps Media WS 브리지.\n *\n * LiveKit 의 AgentSession 은 room 없이도 동작한다 — `session.input.audio` /\n * `session.output.audio` 에 커스텀 구현을 꽂으면 된다. 이 모듈이 그 구현이다.\n *\n * 전화망 wire format 은 G.711 μ-law 8kHz, 160바이트(20ms) 프레임이다. AudioOutput 이\n * `sampleRate=8000` 을 선언하므로 프레임워크가 TTS 출력을 8kHz 로 리샘플해준다 —\n * 우리는 μ-law 인코딩만 한다.\n *\n * 참조 구현: agents-js `voice/console_io.ts` 의 `TcpAudioInput`/`TcpAudioOutput` —\n * 같은 문제(비-WebRTC transport 를 AgentSession 에 물리기)를 푸는 LiveKit 자신의 코드다.\n * ⚠️ Python(clawops-python) 과 달리 agents-js 는 `AudioInput` 베이스와\n * `TranscriptionSynchronizer` 를 public export 하지 않는다. 그래서\n * - AudioInput 은 구조적으로 구현(ReadableStream 노출)하고,\n * - 재생 완료(playout) 판정은 `TcpAudioOutput` 과 동일한 Future 핸드셰이크로 하되\n * 완료 신호를 Media WS `mark` echo 로 받는다. (LiveKit 자신의 `AgentsConsole` 도\n * room-less 경로에서 transcription sync 를 생략하므로 우리도 생략한다.)\n *\n * ⚠️ AudioOutput 계약 2가지 — 어기면 조용히 깨진다:\n * 1. 첫 프레임에서 `onPlaybackStarted()` 를 반드시 호출한다. 빼면 assistant 메시지가\n * 히스토리에서 통째로 사라진다.\n * 2. `onPlaybackFinished()` 는 flush 세그먼트당 정확히 한 번, 취소되더라도 반드시\n * 호출한다. 안 그러면 프레임워크의 재생 회계가 어긋나 wait_for_playout 이 영구 대기한다.\n */\n\nimport { pcm16ToUlaw, DECODE_TABLE } from '../audio.js';\nimport type { CallSession } from '../session.js';\n\nexport const SAMPLE_RATE = 8000;\n/** 전화망 wire rate. AudioOutput 이 선언하면 프레임워크가 여기 맞춰 리샘플한다. */\n\nexport const FRAME_BYTES = 160;\n/** μ-law 20ms = 160바이트. */\n\nexport const ULAW_SILENCE_BYTE = 0xff;\n/** μ-law 무음 — 마지막 프레임 패딩용. */\n\nconst MARK_TIMEOUT_MARGIN_MS = 10_000;\n/** mark 대기 timeout = 밀어넣은 오디오 길이(ms) + 이 여유. */\n\n/** ClawOpsAudioOutput 이 오디오를 흘려보내는 대상(실 통화 또는 prewarm 버퍼)의 최소 계약. */\ninterface AudioSink {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n}\n\n/** 외부에서 참조할 인스턴스 타입 (실제 클래스는 optional dep 로드 후 팩토리에서 정의된다). */\nexport interface ClawOpsAudioInputInstance {\n readonly stream: unknown;\n pushUlaw(ulaw: Buffer): void;\n endInput(): void;\n close(): Promise<void>;\n onAttached(): void;\n onDetached(): void;\n}\n\nexport interface ClawOpsAudioOutputInstance {\n setCall(call: CallSession): void;\n close(): void;\n // AudioOutput 상속 메서드(captureFrame/flush/clearBuffer/onPlaybackStarted/...)는\n // 프레임워크가 호출하므로 여기 노출하지 않는다.\n}\n\nexport interface ClawOpsIO {\n ClawOpsAudioInput: new () => ClawOpsAudioInputInstance;\n ClawOpsAudioOutput: new (call: AudioSink) => ClawOpsAudioOutputInstance;\n}\n\n/** 외부 resolve 를 노출하는 1회성 Deferred (agents-js 의 non-exported `Future` 대응). */\nclass Deferred<T = void> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _done = false;\n constructor() {\n this.promise = new Promise<T>((resolve) => {\n this._resolve = resolve;\n });\n }\n get done(): boolean {\n return this._done;\n }\n resolve(value: T): void {\n if (this._done) return;\n this._done = true;\n this._resolve(value);\n }\n}\n\nlet _cached: ClawOpsIO | null = null;\n\n/**\n * agents-js / rtc-node 를 동적 로드해 IO 클래스를 정의한다 (1회 캐시).\n *\n * 클래스가 `voice.AudioOutput`(optional dep)을 상속하므로 top-level 이 아니라\n * import 성공 후 팩토리 안에서 정의해야 한다.\n */\nexport async function loadClawOpsIO(): Promise<ClawOpsIO> {\n if (_cached) return _cached;\n\n // 두 optional dep 를 병렬 로드한다 (독립적, boot 1회).\n const [agents, rtc] = (await Promise.all([\n import('@livekit/agents'),\n import('@livekit/rtc-node'),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ])) as [any, any];\n\n // agents-js 는 AudioOutput 생성자(instance-init)에서 log() 를 부르고, 로거가 초기화되지\n // 않았으면 throw 한다. room-less 는 worker/cli(initializeLogger 를 대신 부르는)를 거치지\n // 않으므로 우리가 초기화한다 — 단, 유저가 이미 했으면(loggerOptions() 존재) 건드리지 않는다.\n if (\n typeof agents.initializeLogger === 'function' &&\n (typeof agents.loggerOptions !== 'function' || !agents.loggerOptions())\n ) {\n agents.initializeLogger({ pretty: false, level: process.env['LIVEKIT_LOG_LEVEL'] ?? 'info' });\n }\n\n const AudioOutputBase = agents.voice.AudioOutput;\n const AudioFrame = rtc.AudioFrame;\n\n /**\n * 통화 인바운드 오디오(μ-law) -> rtc.AudioFrame 스트림.\n *\n * agents-js `AudioInput` 은 public export 되지 않으므로 상속하지 않고, 프레임워크가\n * 실제로 읽는 계약(`.stream` + onAttached/onDetached/close)만 구조적으로 구현한다.\n * `session.input.audio = <this>` 로 꽂으면 프레임워크가 `.stream` 을 소비한다.\n */\n class ClawOpsAudioInput implements ClawOpsAudioInputInstance {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _controller: ReadableStreamDefaultController<any> | null = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private readonly _stream: ReadableStream<any>;\n private _closed = false;\n\n constructor() {\n this._stream = new ReadableStream({\n start: (c) => {\n this._controller = c;\n },\n cancel: () => {\n this._closed = true;\n },\n });\n }\n\n get stream(): unknown {\n return this._stream;\n }\n\n /** G.711 μ-law 청크를 PCM16 프레임으로 바꿔 스트림에 밀어넣는다. */\n pushUlaw(ulaw: Buffer): void {\n if (this._closed || !this._controller || ulaw.length === 0) return;\n // μ-law 를 곧장 Int16Array 로 디코드한다 (중간 Buffer + 재읽기 루프 생략).\n const samples = new Int16Array(ulaw.length);\n for (let i = 0; i < ulaw.length; i++) {\n samples[i] = DECODE_TABLE[ulaw[i]!]!;\n }\n const frame = new AudioFrame(samples, SAMPLE_RATE, 1, samples.length);\n try {\n this._controller.enqueue(frame);\n } catch {\n // 스트림이 이미 닫혔다 — 무시.\n }\n }\n\n /** 스트림 종료 — 리더가 완료를 본다. */\n endInput(): void {\n this._closeStream();\n }\n\n async close(): Promise<void> {\n this._closeStream();\n }\n\n onAttached(): void {}\n onDetached(): void {}\n\n private _closeStream(): void {\n if (this._closed) return;\n this._closed = true;\n try {\n this._controller?.close();\n } catch {\n // 이미 닫힘.\n }\n }\n }\n\n /**\n * AgentSession 출력 -> μ-law 160바이트 프레임 -> CallSession.sendAudio().\n *\n * 재생 완료 판정은 Media WS `mark` 로 한다 — 플랫폼이 큐의 오디오를 다 내보낸 뒤\n * mark 를 에코한다. prewarm 중(BufferingCall)에는 media WS 가 없으므로 attach 될\n * 때까지 기다렸다가 fence 를 건다(그 전엔 아직 아무것도 재생되지 않는다).\n *\n * 구조는 agents-js `TcpAudioOutput` 을 따르고, 재생 완료 신호만 broker 대신 mark 로 받는다.\n */\n class ClawOpsAudioOutput extends AudioOutputBase implements ClawOpsAudioOutputInstance {\n private _call: AudioSink;\n private _pushedDurationMs = 0;\n private _captureStartedAt = 0;\n private _tail: Buffer = Buffer.alloc(0);\n private _flushPromise: Promise<void> | null = null;\n private _flushInProgress = false;\n private _interrupted = new Deferred();\n private _attached = new Deferred();\n private _markSeq = 0;\n private _closed = false;\n\n constructor(call: AudioSink) {\n // AudioOutput(sampleRate?, nextInChain?, capabilities?)\n super(SAMPLE_RATE, undefined, { pause: false });\n this._call = call;\n }\n\n /**\n * attach() 시 실제 CallSession 으로 교체한다 (prewarm -> 실제 통화).\n *\n * prewarm 중 버퍼링된 오디오는 지금부터 실제 재생되므로 경과 기준점을 여기로 옮긴다 —\n * 안 그러면 링 구간까지 \"재생됐다\"고 계산돼 barge-in 절단 위치가 뒤로 밀린다.\n */\n setCall(call: CallSession): void {\n this._call = call as unknown as AudioSink;\n if (this._pushedDurationMs > 0) {\n this._captureStartedAt = Date.now();\n }\n this._attached.resolve();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async captureFrame(frame: any): Promise<void> {\n // ⚠️ close() 후엔 base 를 건드리지 않는다 — super.captureFrame 이 세그먼트 카운트를\n // 올린 뒤 여기서 bail 하면 onPlaybackFinished 가 안 나가 teardown 시 wait 이 샌다.\n if (this._closed) return;\n await super.captureFrame(frame);\n\n // 이전 세그먼트 flush 가 진행 중이면 기다린다 (TcpAudioOutput 과 동일).\n if (this._flushInProgress && this._flushPromise) {\n await this._flushPromise;\n }\n\n if (this._pushedDurationMs === 0) {\n this._captureStartedAt = Date.now();\n // ⚠️ 계약 1: 빼면 assistant 메시지가 히스토리에서 사라진다.\n this.onPlaybackStarted(Date.now());\n }\n\n this._pushedDurationMs += (frame.samplesPerChannel / frame.sampleRate) * 1000;\n\n // frame.data 는 Int16Array(PCM16). LE 플랫폼에서 그대로 바이트 뷰로 읽는다.\n const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);\n const encoded = pcm16ToUlaw(pcm);\n // 대부분의 프레임은 160바이트 정렬돼 tail 이 비어 있다 — 그때는 concat(추가 할당+복사)을 건너뛴다.\n const ulaw = this._tail.length === 0 ? encoded : Buffer.concat([this._tail, encoded]);\n let off = 0;\n while (off + FRAME_BYTES <= ulaw.length) {\n await this._call.sendAudio(ulaw.subarray(off, off + FRAME_BYTES));\n off += FRAME_BYTES;\n }\n this._tail = Buffer.from(ulaw.subarray(off));\n }\n\n flush(): void {\n super.flush();\n if (this._pushedDurationMs > 0) {\n this._interrupted = new Deferred();\n this._flushInProgress = true;\n this._flushPromise = this._runPlayoutHandshake().finally(() => {\n this._flushInProgress = false;\n });\n } else {\n this._tail = Buffer.alloc(0);\n }\n }\n\n clearBuffer(): void {\n // ⚠️ 여기서 onPlaybackFinished 를 부르지 않는다 — 핸드셰이크가 interrupted 를\n // 보고 한 번만 쏜다.\n this._tail = Buffer.alloc(0);\n if (this._pushedDurationMs > 0) {\n this._interrupted.resolve();\n }\n // prewarm 중이면 BufferingCall.clearAudio 가 버퍼를 비우고, 실 통화면 media WS 큐를 flush.\n this._call.clearAudio();\n }\n\n /** 세션 종료 시 남은 대기를 깨워 정리한다. */\n close(): void {\n this._closed = true;\n this._interrupted.resolve();\n this._attached.resolve();\n }\n\n // ── 내부 ───────────────────────────────────────────────────\n\n /** 실 통화이고 transport 가 살아 있으면 mark 훅을 가진 CallSession 을 돌려준다. */\n private _mediaCall(): CallSession | null {\n const c = this._call as unknown as {\n _sendMark?: unknown;\n _waitForMark?: unknown;\n _flushTransport?: unknown;\n _isTransportConnected?: () => boolean;\n };\n if (\n typeof c._sendMark !== 'function' ||\n typeof c._waitForMark !== 'function' ||\n typeof c._flushTransport !== 'function'\n ) {\n return null; // BufferingCall(prewarm) 또는 아직 미바인딩.\n }\n if (typeof c._isTransportConnected === 'function' && !c._isTransportConnected()) {\n return null; // WS 사망.\n }\n return this._call as unknown as CallSession;\n }\n\n /** `p` 와 barge-in 중 먼저 오는 걸 기다린다. 반환: p 가 이겼는가(=중단 아님). */\n private async _race(p: Promise<unknown>): Promise<boolean> {\n const winner = await Promise.race([\n p.then(() => 'main' as const),\n this._interrupted.promise.then(() => 'interrupt' as const),\n ]);\n return winner === 'main';\n }\n\n private async _runPlayoutHandshake(): Promise<void> {\n let interrupted = true;\n try {\n // 남은 자투리를 무음으로 패딩해 한 프레임으로 내보낸다.\n const tail = this._tail;\n this._tail = Buffer.alloc(0);\n if (tail.length > 0) {\n const padded = Buffer.concat([\n tail,\n Buffer.alloc(FRAME_BYTES - tail.length, ULAW_SILENCE_BYTE),\n ]);\n await this._call.sendAudio(padded);\n }\n interrupted = await this._awaitPlayout();\n } catch {\n // WS 사망 등 전송 오류 — interrupted 로 처리하고 프레임워크로 전파하지 않는다.\n interrupted = true;\n } finally {\n // ⚠️ 계약 2: 세그먼트당 정확히 한 번 — 취소되더라도 반드시 emit.\n const played = interrupted\n ? Math.min(Math.max(0, Date.now() - this._captureStartedAt), this._pushedDurationMs)\n : this._pushedDurationMs;\n this.onPlaybackFinished({ playbackPosition: played / 1000, interrupted });\n this._pushedDurationMs = 0;\n this._interrupted = new Deferred();\n }\n }\n\n /** 재생이 끝나거나 barge-in 될 때까지 대기. 반환: interrupted 여부. */\n private async _awaitPlayout(): Promise<boolean> {\n let media = this._mediaCall();\n if (!media) {\n // prewarm — 아직 재생 주체가 없다. attach 될 때까지 기다렸다가 fence 를 건다.\n if (!(await this._race(this._attached.promise))) {\n return true; // attach 전에 중단됐다.\n }\n media = this._mediaCall();\n if (!media) {\n return this._interrupted.done; // attach 됐는데도 WS 가 없다 — fence 불가.\n }\n }\n\n // send_mark 는 즉시 나가지만 send_audio 는 로컬 큐에 쌓인다. flush 로 큐를 비우지\n // 않으면 mark 가 오디오를 추월한다.\n await media._flushTransport!();\n const markName = `lk-${this._markSeq++}`;\n media._sendMark!(markName);\n const timeout = Math.round(this._pushedDurationMs) + MARK_TIMEOUT_MARGIN_MS;\n // mark echo(재생 완료)와 barge-in 중 먼저 오는 것을 기다린다.\n return !(await this._race(media._waitForMark!(markName, timeout)));\n }\n }\n\n _cached = { ClawOpsAudioInput, ClawOpsAudioOutput };\n return _cached;\n}\n","/**\n * ClawOps 전화 내장 도구를 LiveKit Toolset 으로 노출한다.\n *\n * `hang_up` / `collect_dtmf` / `send_dtmf` / `transfer_call` — 스키마와 동작을\n * `pipeline/builtin-tool-schemas.ts` 에서 그대로 재사용한다 (핸들러 중복 없음).\n *\n * 설계 메모:\n * - LiveKit 이 파는 전화 도구(`end_call`/`send_dtmf`)는 room(`get_job_context()`)을\n * 잡아 room 없이는 죽으므로 쓰지 않는다. 패턴만 가져오고 실행은 우리 것을 쓴다.\n * - `CallSession` 은 클로저 상태로 들고 간다 (prewarm -> attach 시 setCall 로 교체).\n */\n\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport { getBuiltinToolSchemas, executeBuiltinTool } from '../pipeline/builtin-tool-schemas.js';\nimport type { CallSession } from '../session.js';\n\n/** 인사말 도중 모델이 통화를 끊거나 전환하는 사고를 막는다 (ToolFlag.IGNORE_ON_ENTER). */\nconst IGNORE_ON_ENTER_TOOLS = new Set(['hang_up', 'transfer_call']);\n\nexport interface ClawOpsPhoneTools {\n /** LiveKit Toolset 인스턴스 — `agent.updateTools([...])` 에 넣는 ToolContextEntry. */\n toolset: unknown;\n /** prewarm -> attach 시 실제 통화로 교체한다. */\n setCall(call: CallSession): void;\n}\n\n/**\n * ClawOps 통화 제어 도구 묶음을 만든다.\n *\n * @param llm 동적 로드한 `@livekit/agents` 의 `llm` 네임스페이스 (`tool`/`Toolset`/`ToolFlag`).\n * @param options.enabled 활성화할 내장 도구 (`ClawOpsAgent(builtinTools=...)` 와 동일).\n * @param options.excludeNames 유저/registry 도구와 이름이 겹쳐 제외할 내장 도구 이름.\n */\nexport function createClawOpsPhoneTools(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n llm: any,\n options: { enabled: Set<BuiltinTool>; excludeNames?: Set<string> },\n): ClawOpsPhoneTools {\n const state: { call: CallSession | null } = { call: null };\n const requireCall = (): CallSession => {\n if (!state.call) {\n throw new Error('통화가 아직 연결되지 않았습니다');\n }\n return state.call;\n };\n\n const exclude = options.excludeNames ?? new Set<string>();\n const ignoreFlag = llm.ToolFlag?.IGNORE_ON_ENTER;\n\n // 활성화된 내장 도구의 정규 스키마(name/description/parameters)를 가져온다.\n const schemas = getBuiltinToolSchemas(options.enabled, 'chat') as Array<{\n function: { name: string; description: string; parameters: Record<string, unknown> };\n }>;\n\n const tools: unknown[] = [];\n for (const schema of schemas) {\n const { name, description, parameters } = schema.function;\n if (exclude.has(name)) continue; // 유저/registry 도구와 충돌 — 내장 쪽을 뺀다.\n tools.push(\n llm.tool({\n name,\n description,\n parameters, // raw JSON schema (agents-js 가 JSONSchema7 을 그대로 받는다).\n flags: IGNORE_ON_ENTER_TOOLS.has(name) ? ignoreFlag : undefined,\n // raw schema tool 의 execute 는 파싱된 args 를 그대로 받는다.\n execute: async (args: Record<string, unknown>) =>\n (await executeBuiltinTool(name, args ?? {}, requireCall())) ?? '',\n }),\n );\n }\n\n const toolset = llm.Toolset.create({ id: 'clawops_phone', tools });\n\n return {\n toolset,\n setCall: (call: CallSession) => {\n state.call = call;\n },\n };\n}\n","/**\n * LiveKitSession — 유저의 LiveKit AgentSession 을 ClawOps `Session` 계약으로 감싼다.\n *\n * `ClawOpsAgent` 는 `Session`(pipeline/base.ts)만 알고 통화를 굴린다. 이 클래스가 그\n * 계약을 구현하면서 내부적으로 LiveKit AgentSession 을 돌린다 — room 없이.\n *\n * 유저는 관용적인 LiveKit 코드를 그대로 쓴다. `Agent` 서브클래스와 `AgentSession` 설정은\n * 손대지 않아도 되고, 유일한 차이는 `session.start({ room })` 을 우리가 room 없이\n * 대신 불러준다는 것이다.\n *\n * ⚠️ v0 은 동시통화 1건이다 (`ClawOpsAgent` 가 세션 인스턴스를 통화 간 공유한다).\n * `create` 를 팩토리로 받아두었으므로, 추후 통화당 격리로 넘어갈 때 유저 코드는 그대로 둔다.\n */\n\nimport type { Logger } from 'pino';\n\nimport { NOOP_LOGGER } from '../logger.js';\nimport type { CallSession } from '../session.js';\nimport type { Session } from '../pipeline/base.js';\nimport type { ToolRegistry } from '../tool.js';\nimport type { BuiltinTool } from '../builtin-tool.js';\nimport type { SessionTelemetry } from '../telemetry.js';\nimport { BufferingCall, attachBuffered } from '../pipeline/buffering-call.js';\nimport { loadClawOpsIO } from './io.js';\nimport type { ClawOpsAudioInputInstance, ClawOpsAudioOutputInstance } from './io.js';\nimport { createClawOpsPhoneTools } from './toolset.js';\nimport type { ClawOpsPhoneTools } from './toolset.js';\n\n/**\n * 통화당 1회 호출되는 팩토리. `[AgentSession, Agent]` 를 반환한다.\n *\n * `call` 은 착신/일반 시작에서는 실제 CallSession, 발신 prewarm 중에는 null 이다\n * (prewarm 은 응답 전에 도는데 `Session.prewarm()` 이 call 을 넘겨주지 않는다).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type LiveKitCreateFn = (call: CallSession | null) => Promise<[any, any]>;\n\nexport class LiveKitSession implements Session {\n private readonly _create: LiveKitCreateFn;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _session: any = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _agent: any = null;\n private _input: ClawOpsAudioInputInstance | null = null;\n private _output: ClawOpsAudioOutputInstance | null = null;\n private _phone: ClawOpsPhoneTools | null = null;\n private _target: CallSession | BufferingCall | null = null;\n /** 유저 원본 도구 스냅샷 (우리 도구 추가 전). attach 재적용 시 누적 방지. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _userToolCtx: any = null;\n\n private _toolRegistry: ToolRegistry | null = null;\n private _builtinTools: Set<BuiltinTool> | null = null;\n private _log: Logger = NOOP_LOGGER;\n\n // 동적 로드한 agents-js 네임스페이스 (boot 시 캐시).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _llm: any = null;\n\n constructor(create: LiveKitCreateFn) {\n this._create = create;\n }\n\n // ── ClawOpsAgent 가 duck-typing 으로 주입하는 setter ──────────\n\n setToolRegistry(registry: ToolRegistry): void {\n this._toolRegistry = registry;\n }\n\n setBuiltinTools(tools: Set<BuiltinTool>): void {\n this._builtinTools = tools;\n }\n\n setLogger(logger: Logger): void {\n this._log = logger;\n }\n\n getTelemetry(): SessionTelemetry | null {\n // 녹음/텔레메트리는 미디어 레이어에서 세션 무관하게 동작하므로 여기선 null.\n return null;\n }\n\n // ── Session 계약 ────────────────────────────────────────────\n\n async start(callSession: CallSession, tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(callSession);\n }\n\n async prewarm(tools?: ToolRegistry): Promise<void> {\n if (tools) this._toolRegistry = tools;\n await this._boot(null);\n }\n\n async attach(callSession: CallSession): Promise<void> {\n // boot 성공 신호는 _session (부분 완료된 boot 은 _output 만 set 돼 있을 수 있다).\n if (!this._session || !this._output || !this._agent) {\n await this._boot(callSession);\n return;\n }\n const prev = this._target;\n this._output.setCall(callSession);\n this._target = callSession;\n // ⚠️ 아웃바운드 prewarm 은 _boot(null) 이 setter 실행 전에 돌아 도구를 비운 채\n // 시작한다. setter 는 이제 실행됐으므로 여기서 registry/builtin 도구를 다시 붙인다.\n await this._applyTools(callSession);\n attachBuffered(prev, callSession);\n }\n\n feedAudio(audio: Buffer, _timestamp?: number): void {\n this._input?.pushUlaw(audio);\n }\n\n async feedDtmf(digits: string): Promise<void> {\n if (!this._session) return;\n // generateReply 는 동기 메서드다. teardown 중에는 세션 activity 가 이미 정리돼\n // 던질 수 있으므로 삼킨다.\n try {\n this._session.generateReply({ userInput: `[전화 키패드 입력] ${digits}` });\n } catch (err) {\n this._log.debug({ err }, 'feedDtmf skipped (session not running)');\n }\n }\n\n async stop(): Promise<void> {\n this._input?.endInput();\n await this._closeAgentSession();\n }\n\n // ── 내부 ────────────────────────────────────────────────────\n\n private async _boot(call: CallSession | null): Promise<void> {\n // 재진입(attach 실패 폴백) 시 이전 세션이 새지 않도록 먼저 닫는다.\n await this._closeAgentSession();\n\n // agents import 와 IO 로드는 독립적이라 병렬로 (boot 1회, 이후 loadClawOpsIO 는 캐시).\n const [agents, io] = await Promise.all([\n import('@livekit/agents') as Promise<{ llm: unknown }>,\n loadClawOpsIO(),\n ]);\n this._llm = (agents as { llm: unknown }).llm;\n const { ClawOpsAudioInput, ClawOpsAudioOutput } = io;\n\n const target: CallSession | BufferingCall = call ?? new BufferingCall();\n\n const [session, agent] = await this._create(call);\n this._validate(session, agent);\n\n const input = new ClawOpsAudioInput();\n const output = new ClawOpsAudioOutput(target as unknown as {\n sendAudio(audio: Buffer): void | Promise<void>;\n clearAudio(): void;\n });\n this._input = input;\n this._output = output;\n\n session.input.audio = input;\n session.output.audio = output;\n // agents-js 의 room-less 레퍼런스(AgentsConsole)도 transcription sync 를 생략한다.\n // (transcript 는 conversation_item_added 로 별도 브리지한다 — _wireTranscripts.)\n session.output.transcription = null;\n\n this._wireTranscripts(session);\n\n this._agent = agent;\n // 우리 도구를 붙이기 전 유저 원본 도구를 스냅샷한다 (attach 재적용 시 누적 방지).\n this._userToolCtx = agent.toolCtx;\n await this._applyTools(call);\n\n // room 을 넘기지 않는 것이 이 통합의 전부다.\n await session.start({ agent });\n\n this._session = session;\n this._target = target;\n }\n\n /**\n * 유저 도구 + 브리지된 registry 도구 + 내장 전화 도구를 이름 충돌 없이 붙인다.\n *\n * 아웃바운드 prewarm(_boot(null))은 setter 실행 전에 돌아 registry/builtin 이 비어\n * 있고, 그 사이엔 통화가 연결되지 않아 도구를 쓸 일도 없다. 그래서 이때는 유저 도구만\n * 올리고, attach() 가 setter 실행 후 실제 도구를 붙이는 유일한 조립 지점이 된다.\n */\n private async _applyTools(call: CallSession | null): Promise<void> {\n if (!this._agent) return;\n const agent = this._agent;\n const llm = this._llm;\n\n if (call === null) {\n // prewarm — 아직 registry/builtin 이 없다. 유저 원본 ToolContext 를 그대로 재적용.\n await agent.updateTools(this._userToolCtx);\n return;\n }\n\n // 유저 도구(중첩 Toolset 포함)의 실제 함수 이름을 모은다 — 내장 도구와 겹칠 때\n // ToolContext.flatten() 이 \"duplicate function name\" 으로 통화를 떨구는 걸 막는다.\n const taken = new Set<string>(llm.sortedToolNames(this._userToolCtx));\n const bridged = this._bridgeRegistry(this._toolRegistry, taken);\n\n const enabled = this._builtinTools ?? new Set<BuiltinTool>();\n this._phone = createClawOpsPhoneTools(llm, { enabled, excludeNames: taken });\n this._phone.setCall(call);\n\n // ⚠️ sortedToolEntries 는 `[name, tool]` 쌍을 준다. updateTools/ToolContext 는 bare\n // tool 배열(ToolContextEntry[])을 받으므로 tool 만 추출해 넘긴다 — 쌍을 그대로 펼치면\n // 유저 도구가 유효하지 않은 엔트리로 취급돼 전부 유실된다.\n const userTools = (llm.sortedToolEntries(this._userToolCtx) as Array<[string, unknown]>).map(\n (entry) => entry[1],\n );\n await agent.updateTools([...userTools, ...bridged, this._phone.toolset]);\n }\n\n /** `@tool` / MCP 도구(ToolRegistry)를 LiveKit function tool 로 노출한다. */\n private _bridgeRegistry(registry: ToolRegistry | null, taken: Set<string>): unknown[] {\n if (!registry) return [];\n const llm = this._llm;\n const out: unknown[] = [];\n for (const def of registry.toOpenAITools()) {\n const name = def.function.name;\n if (taken.has(name)) continue; // 유저 도구가 우선.\n taken.add(name);\n out.push(\n llm.tool({\n name,\n description: def.function.description,\n parameters: def.function.parameters,\n execute: async (args: Record<string, unknown>) => {\n const result = await registry.call(name, args ?? {});\n return typeof result === 'string' ? result : JSON.stringify(result);\n },\n }),\n );\n }\n return out;\n }\n\n /**\n * LiveKit 의 최종 대화 항목을 ClawOps `transcript` 훅으로 흘려보낸다.\n *\n * 네이티브 세션(OpenAI/Gemini/Pipeline)은 `call._emit('transcript', role, text)` 를\n * 부른다. `@on('transcript')` 로 트랜스크립트를 모으는 기존 앱이 세션만 바꿔도 그대로\n * 돌게 하려면 여기서 같은 계약을 재현한다.\n *\n * `conversation_item_added` 는 user·assistant 최종 메시지가 히스토리에 커밋될 때 한\n * 번씩 뜬다 — 부분(interim) 중복 없이 최종만 얻는다. item 이 handoff 등 ChatMessage 가\n * 아니면 role/textContent 가 없어 자연히 걸러진다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _wireTranscripts(session: any): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n session.on('conversation_item_added', (ev: any) => {\n const item = ev?.item;\n const role = item?.role;\n if (role !== 'user' && role !== 'assistant') return;\n const text = item?.textContent;\n if (!text) return;\n const target = this._target as unknown as { _emit?: (event: string, ...args: unknown[]) => void };\n // Node 의 _emit 은 동기다 — Python 처럼 fire-and-forget task 를 추적할 필요 없다.\n // prewarm 중이면 _target 이 BufferingCall 이라 _emit 이 드롭 카운트로 흡수한다.\n target?._emit?.('transcript', role, text);\n });\n }\n\n private async _closeAgentSession(): Promise<void> {\n if (this._output) {\n this._output.close();\n this._output = null;\n }\n if (this._session) {\n const s = this._session;\n this._session = null;\n try {\n if (typeof s.aclose === 'function') {\n await s.aclose();\n } else if (typeof s.close === 'function') {\n await s.close();\n }\n } catch (err) {\n this._log.warn({ err }, 'AgentSession close failed');\n }\n }\n }\n\n /**\n * LiveKit 이 조용히 넘어가는 두 조합을 우리가 막는다 — 실수하면 \"말을 못 하는\n * 에이전트\"가 아무 예외 없이 배포된다.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _validate(session: any, agent: any): void {\n let audioOutput: unknown;\n let tts: unknown;\n try {\n const llm = agent?.llm ?? session?.llm;\n tts = agent?.tts ?? session?.tts;\n audioOutput = llm?.capabilities?.audioOutput;\n } catch {\n return; // capabilities 를 못 읽으면 검증 생략 (거짓 양성 방지).\n }\n if (audioOutput === undefined || audioOutput === null) {\n return; // realtime 모델이 아니다 (일반 LLM + TTS 파이프라인).\n }\n if (audioOutput === false && (tts === undefined || tts === null)) {\n throw new Error(\n \"RealtimeModel 이 modalities=['text'] 인데 tts 가 없습니다 — 에이전트가 소리를 \" +\n \"내지 못합니다. AgentSession(tts=...) 를 주거나 modalities 에 'audio' 를 넣으세요.\",\n );\n }\n if (audioOutput === true && tts !== undefined && tts !== null) {\n this._log.warn(\n 'RealtimeModel 이 오디오를 직접 출력하므로 tts 가 무시됩니다. TTS 를 쓰려면 ' +\n \"RealtimeModel(modalities=['text']) 로 설정하세요.\",\n );\n }\n }\n}\n"]}
|