@teamlearners/clawops 0.23.0 → 0.24.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 +114 -673
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -328
- package/dist/agent/index.d.ts +4 -328
- package/dist/agent/index.js +18 -608
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/livekit/index.cjs +496 -0
- package/dist/agent/livekit/index.cjs.map +1 -0
- package/dist/agent/livekit/index.d.cts +172 -0
- package/dist/agent/livekit/index.d.ts +172 -0
- package/dist/agent/livekit/index.js +490 -0
- package/dist/agent/livekit/index.js.map +1 -0
- package/dist/base-Z-uZdd27.d.cts +338 -0
- package/dist/base-Z-uZdd27.d.ts +338 -0
- package/dist/chunk-2BAYUQ7O.js +610 -0
- package/dist/chunk-2BAYUQ7O.js.map +1 -0
- package/dist/{chunk-JU36ASTU.js → chunk-7P2HGIPS.js} +3 -3
- package/dist/{chunk-JU36ASTU.js.map → chunk-7P2HGIPS.js.map} +1 -1
- package/dist/{chunk-NXMTLS2U.cjs → chunk-LUA53NPZ.cjs} +3 -3
- package/dist/{chunk-NXMTLS2U.cjs.map → chunk-LUA53NPZ.cjs.map} +1 -1
- package/dist/chunk-U4UFTVLX.cjs +631 -0
- package/dist/chunk-U4UFTVLX.cjs.map +1 -0
- package/dist/index.cjs +35 -35
- package/dist/index.js +2 -2
- package/package.json +14 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Logger } from 'pino';
|
|
2
|
+
import { C as CallSession, S as Session, b as ToolRegistry, B as BuiltinTool, c as SessionTelemetry } from '../../base-Z-uZdd27.cjs';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LiveKitSession — 유저의 LiveKit AgentSession 을 ClawOps `Session` 계약으로 감싼다.
|
|
7
|
+
*
|
|
8
|
+
* `ClawOpsAgent` 는 `Session`(pipeline/base.ts)만 알고 통화를 굴린다. 이 클래스가 그
|
|
9
|
+
* 계약을 구현하면서 내부적으로 LiveKit AgentSession 을 돌린다 — room 없이.
|
|
10
|
+
*
|
|
11
|
+
* 유저는 관용적인 LiveKit 코드를 그대로 쓴다. `Agent` 서브클래스와 `AgentSession` 설정은
|
|
12
|
+
* 손대지 않아도 되고, 유일한 차이는 `session.start({ room })` 을 우리가 room 없이
|
|
13
|
+
* 대신 불러준다는 것이다.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ v0 은 동시통화 1건이다 (`ClawOpsAgent` 가 세션 인스턴스를 통화 간 공유한다).
|
|
16
|
+
* `create` 를 팩토리로 받아두었으므로, 추후 통화당 격리로 넘어갈 때 유저 코드는 그대로 둔다.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 통화당 1회 호출되는 팩토리. `[AgentSession, Agent]` 를 반환한다.
|
|
21
|
+
*
|
|
22
|
+
* `call` 은 착신/일반 시작에서는 실제 CallSession, 발신 prewarm 중에는 null 이다
|
|
23
|
+
* (prewarm 은 응답 전에 도는데 `Session.prewarm()` 이 call 을 넘겨주지 않는다).
|
|
24
|
+
*/
|
|
25
|
+
type LiveKitCreateFn = (call: CallSession | null) => Promise<[any, any]>;
|
|
26
|
+
declare class LiveKitSession implements Session {
|
|
27
|
+
private readonly _create;
|
|
28
|
+
private _session;
|
|
29
|
+
private _agent;
|
|
30
|
+
private _input;
|
|
31
|
+
private _output;
|
|
32
|
+
private _phone;
|
|
33
|
+
private _target;
|
|
34
|
+
/** 유저 원본 도구 스냅샷 (우리 도구 추가 전). attach 재적용 시 누적 방지. */
|
|
35
|
+
private _userToolCtx;
|
|
36
|
+
private _toolRegistry;
|
|
37
|
+
private _builtinTools;
|
|
38
|
+
private _log;
|
|
39
|
+
private _llm;
|
|
40
|
+
constructor(create: LiveKitCreateFn);
|
|
41
|
+
setToolRegistry(registry: ToolRegistry): void;
|
|
42
|
+
setBuiltinTools(tools: Set<BuiltinTool>): void;
|
|
43
|
+
setLogger(logger: Logger): void;
|
|
44
|
+
getTelemetry(): SessionTelemetry | null;
|
|
45
|
+
start(callSession: CallSession, tools?: ToolRegistry): Promise<void>;
|
|
46
|
+
prewarm(tools?: ToolRegistry): Promise<void>;
|
|
47
|
+
attach(callSession: CallSession): Promise<void>;
|
|
48
|
+
feedAudio(audio: Buffer, _timestamp?: number): void;
|
|
49
|
+
feedDtmf(digits: string): Promise<void>;
|
|
50
|
+
stop(): Promise<void>;
|
|
51
|
+
private _boot;
|
|
52
|
+
/**
|
|
53
|
+
* 유저 도구 + 브리지된 registry 도구 + 내장 전화 도구를 이름 충돌 없이 붙인다.
|
|
54
|
+
*
|
|
55
|
+
* 아웃바운드 prewarm(_boot(null))은 setter 실행 전에 돌아 registry/builtin 이 비어
|
|
56
|
+
* 있고, 그 사이엔 통화가 연결되지 않아 도구를 쓸 일도 없다. 그래서 이때는 유저 도구만
|
|
57
|
+
* 올리고, attach() 가 setter 실행 후 실제 도구를 붙이는 유일한 조립 지점이 된다.
|
|
58
|
+
*/
|
|
59
|
+
private _applyTools;
|
|
60
|
+
/** `@tool` / MCP 도구(ToolRegistry)를 LiveKit function tool 로 노출한다. */
|
|
61
|
+
private _bridgeRegistry;
|
|
62
|
+
/**
|
|
63
|
+
* LiveKit 의 최종 대화 항목을 ClawOps `transcript` 훅으로 흘려보낸다.
|
|
64
|
+
*
|
|
65
|
+
* 네이티브 세션(OpenAI/Gemini/Pipeline)은 `call._emit('transcript', role, text)` 를
|
|
66
|
+
* 부른다. `@on('transcript')` 로 트랜스크립트를 모으는 기존 앱이 세션만 바꿔도 그대로
|
|
67
|
+
* 돌게 하려면 여기서 같은 계약을 재현한다.
|
|
68
|
+
*
|
|
69
|
+
* `conversation_item_added` 는 user·assistant 최종 메시지가 히스토리에 커밋될 때 한
|
|
70
|
+
* 번씩 뜬다 — 부분(interim) 중복 없이 최종만 얻는다. item 이 handoff 등 ChatMessage 가
|
|
71
|
+
* 아니면 role/textContent 가 없어 자연히 걸러진다.
|
|
72
|
+
*/
|
|
73
|
+
private _wireTranscripts;
|
|
74
|
+
private _closeAgentSession;
|
|
75
|
+
/**
|
|
76
|
+
* LiveKit 이 조용히 넘어가는 두 조합을 우리가 막는다 — 실수하면 "말을 못 하는
|
|
77
|
+
* 에이전트"가 아무 예외 없이 배포된다.
|
|
78
|
+
*/
|
|
79
|
+
private _validate;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* ClawOps 전화 내장 도구를 LiveKit Toolset 으로 노출한다.
|
|
84
|
+
*
|
|
85
|
+
* `hang_up` / `collect_dtmf` / `send_dtmf` / `transfer_call` — 스키마와 동작을
|
|
86
|
+
* `pipeline/builtin-tool-schemas.ts` 에서 그대로 재사용한다 (핸들러 중복 없음).
|
|
87
|
+
*
|
|
88
|
+
* 설계 메모:
|
|
89
|
+
* - LiveKit 이 파는 전화 도구(`end_call`/`send_dtmf`)는 room(`get_job_context()`)을
|
|
90
|
+
* 잡아 room 없이는 죽으므로 쓰지 않는다. 패턴만 가져오고 실행은 우리 것을 쓴다.
|
|
91
|
+
* - `CallSession` 은 클로저 상태로 들고 간다 (prewarm -> attach 시 setCall 로 교체).
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
interface ClawOpsPhoneTools {
|
|
95
|
+
/** LiveKit Toolset 인스턴스 — `agent.updateTools([...])` 에 넣는 ToolContextEntry. */
|
|
96
|
+
toolset: unknown;
|
|
97
|
+
/** prewarm -> attach 시 실제 통화로 교체한다. */
|
|
98
|
+
setCall(call: CallSession): void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* ClawOps 통화 제어 도구 묶음을 만든다.
|
|
102
|
+
*
|
|
103
|
+
* @param llm 동적 로드한 `@livekit/agents` 의 `llm` 네임스페이스 (`tool`/`Toolset`/`ToolFlag`).
|
|
104
|
+
* @param options.enabled 활성화할 내장 도구 (`ClawOpsAgent(builtinTools=...)` 와 동일).
|
|
105
|
+
* @param options.excludeNames 유저/registry 도구와 이름이 겹쳐 제외할 내장 도구 이름.
|
|
106
|
+
*/
|
|
107
|
+
declare function createClawOpsPhoneTools(llm: any, options: {
|
|
108
|
+
enabled: Set<BuiltinTool>;
|
|
109
|
+
excludeNames?: Set<string>;
|
|
110
|
+
}): ClawOpsPhoneTools;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* LiveKit AudioInput/AudioOutput <-> ClawOps Media WS 브리지.
|
|
114
|
+
*
|
|
115
|
+
* LiveKit 의 AgentSession 은 room 없이도 동작한다 — `session.input.audio` /
|
|
116
|
+
* `session.output.audio` 에 커스텀 구현을 꽂으면 된다. 이 모듈이 그 구현이다.
|
|
117
|
+
*
|
|
118
|
+
* 전화망 wire format 은 G.711 μ-law 8kHz, 160바이트(20ms) 프레임이다. AudioOutput 이
|
|
119
|
+
* `sampleRate=8000` 을 선언하므로 프레임워크가 TTS 출력을 8kHz 로 리샘플해준다 —
|
|
120
|
+
* 우리는 μ-law 인코딩만 한다.
|
|
121
|
+
*
|
|
122
|
+
* 참조 구현: agents-js `voice/console_io.ts` 의 `TcpAudioInput`/`TcpAudioOutput` —
|
|
123
|
+
* 같은 문제(비-WebRTC transport 를 AgentSession 에 물리기)를 푸는 LiveKit 자신의 코드다.
|
|
124
|
+
* ⚠️ Python(clawops-python) 과 달리 agents-js 는 `AudioInput` 베이스와
|
|
125
|
+
* `TranscriptionSynchronizer` 를 public export 하지 않는다. 그래서
|
|
126
|
+
* - AudioInput 은 구조적으로 구현(ReadableStream 노출)하고,
|
|
127
|
+
* - 재생 완료(playout) 판정은 `TcpAudioOutput` 과 동일한 Future 핸드셰이크로 하되
|
|
128
|
+
* 완료 신호를 Media WS `mark` echo 로 받는다. (LiveKit 자신의 `AgentsConsole` 도
|
|
129
|
+
* room-less 경로에서 transcription sync 를 생략하므로 우리도 생략한다.)
|
|
130
|
+
*
|
|
131
|
+
* ⚠️ AudioOutput 계약 2가지 — 어기면 조용히 깨진다:
|
|
132
|
+
* 1. 첫 프레임에서 `onPlaybackStarted()` 를 반드시 호출한다. 빼면 assistant 메시지가
|
|
133
|
+
* 히스토리에서 통째로 사라진다.
|
|
134
|
+
* 2. `onPlaybackFinished()` 는 flush 세그먼트당 정확히 한 번, 취소되더라도 반드시
|
|
135
|
+
* 호출한다. 안 그러면 프레임워크의 재생 회계가 어긋나 wait_for_playout 이 영구 대기한다.
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
declare const SAMPLE_RATE = 8000;
|
|
139
|
+
/** 전화망 wire rate. AudioOutput 이 선언하면 프레임워크가 여기 맞춰 리샘플한다. */
|
|
140
|
+
declare const FRAME_BYTES = 160;
|
|
141
|
+
/** mark 대기 timeout = 밀어넣은 오디오 길이(ms) + 이 여유. */
|
|
142
|
+
/** ClawOpsAudioOutput 이 오디오를 흘려보내는 대상(실 통화 또는 prewarm 버퍼)의 최소 계약. */
|
|
143
|
+
interface AudioSink {
|
|
144
|
+
sendAudio(audio: Buffer): void | Promise<void>;
|
|
145
|
+
clearAudio(): void;
|
|
146
|
+
}
|
|
147
|
+
/** 외부에서 참조할 인스턴스 타입 (실제 클래스는 optional dep 로드 후 팩토리에서 정의된다). */
|
|
148
|
+
interface ClawOpsAudioInputInstance {
|
|
149
|
+
readonly stream: unknown;
|
|
150
|
+
pushUlaw(ulaw: Buffer): void;
|
|
151
|
+
endInput(): void;
|
|
152
|
+
close(): Promise<void>;
|
|
153
|
+
onAttached(): void;
|
|
154
|
+
onDetached(): void;
|
|
155
|
+
}
|
|
156
|
+
interface ClawOpsAudioOutputInstance {
|
|
157
|
+
setCall(call: CallSession): void;
|
|
158
|
+
close(): void;
|
|
159
|
+
}
|
|
160
|
+
interface ClawOpsIO {
|
|
161
|
+
ClawOpsAudioInput: new () => ClawOpsAudioInputInstance;
|
|
162
|
+
ClawOpsAudioOutput: new (call: AudioSink) => ClawOpsAudioOutputInstance;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* agents-js / rtc-node 를 동적 로드해 IO 클래스를 정의한다 (1회 캐시).
|
|
166
|
+
*
|
|
167
|
+
* 클래스가 `voice.AudioOutput`(optional dep)을 상속하므로 top-level 이 아니라
|
|
168
|
+
* import 성공 후 팩토리 안에서 정의해야 한다.
|
|
169
|
+
*/
|
|
170
|
+
declare function loadClawOpsIO(): Promise<ClawOpsIO>;
|
|
171
|
+
|
|
172
|
+
export { type ClawOpsAudioInputInstance, type ClawOpsAudioOutputInstance, type ClawOpsIO, type ClawOpsPhoneTools, FRAME_BYTES, type LiveKitCreateFn, LiveKitSession, SAMPLE_RATE, createClawOpsPhoneTools, loadClawOpsIO };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Logger } from 'pino';
|
|
2
|
+
import { C as CallSession, S as Session, b as ToolRegistry, B as BuiltinTool, c as SessionTelemetry } from '../../base-Z-uZdd27.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LiveKitSession — 유저의 LiveKit AgentSession 을 ClawOps `Session` 계약으로 감싼다.
|
|
7
|
+
*
|
|
8
|
+
* `ClawOpsAgent` 는 `Session`(pipeline/base.ts)만 알고 통화를 굴린다. 이 클래스가 그
|
|
9
|
+
* 계약을 구현하면서 내부적으로 LiveKit AgentSession 을 돌린다 — room 없이.
|
|
10
|
+
*
|
|
11
|
+
* 유저는 관용적인 LiveKit 코드를 그대로 쓴다. `Agent` 서브클래스와 `AgentSession` 설정은
|
|
12
|
+
* 손대지 않아도 되고, 유일한 차이는 `session.start({ room })` 을 우리가 room 없이
|
|
13
|
+
* 대신 불러준다는 것이다.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ v0 은 동시통화 1건이다 (`ClawOpsAgent` 가 세션 인스턴스를 통화 간 공유한다).
|
|
16
|
+
* `create` 를 팩토리로 받아두었으므로, 추후 통화당 격리로 넘어갈 때 유저 코드는 그대로 둔다.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 통화당 1회 호출되는 팩토리. `[AgentSession, Agent]` 를 반환한다.
|
|
21
|
+
*
|
|
22
|
+
* `call` 은 착신/일반 시작에서는 실제 CallSession, 발신 prewarm 중에는 null 이다
|
|
23
|
+
* (prewarm 은 응답 전에 도는데 `Session.prewarm()` 이 call 을 넘겨주지 않는다).
|
|
24
|
+
*/
|
|
25
|
+
type LiveKitCreateFn = (call: CallSession | null) => Promise<[any, any]>;
|
|
26
|
+
declare class LiveKitSession implements Session {
|
|
27
|
+
private readonly _create;
|
|
28
|
+
private _session;
|
|
29
|
+
private _agent;
|
|
30
|
+
private _input;
|
|
31
|
+
private _output;
|
|
32
|
+
private _phone;
|
|
33
|
+
private _target;
|
|
34
|
+
/** 유저 원본 도구 스냅샷 (우리 도구 추가 전). attach 재적용 시 누적 방지. */
|
|
35
|
+
private _userToolCtx;
|
|
36
|
+
private _toolRegistry;
|
|
37
|
+
private _builtinTools;
|
|
38
|
+
private _log;
|
|
39
|
+
private _llm;
|
|
40
|
+
constructor(create: LiveKitCreateFn);
|
|
41
|
+
setToolRegistry(registry: ToolRegistry): void;
|
|
42
|
+
setBuiltinTools(tools: Set<BuiltinTool>): void;
|
|
43
|
+
setLogger(logger: Logger): void;
|
|
44
|
+
getTelemetry(): SessionTelemetry | null;
|
|
45
|
+
start(callSession: CallSession, tools?: ToolRegistry): Promise<void>;
|
|
46
|
+
prewarm(tools?: ToolRegistry): Promise<void>;
|
|
47
|
+
attach(callSession: CallSession): Promise<void>;
|
|
48
|
+
feedAudio(audio: Buffer, _timestamp?: number): void;
|
|
49
|
+
feedDtmf(digits: string): Promise<void>;
|
|
50
|
+
stop(): Promise<void>;
|
|
51
|
+
private _boot;
|
|
52
|
+
/**
|
|
53
|
+
* 유저 도구 + 브리지된 registry 도구 + 내장 전화 도구를 이름 충돌 없이 붙인다.
|
|
54
|
+
*
|
|
55
|
+
* 아웃바운드 prewarm(_boot(null))은 setter 실행 전에 돌아 registry/builtin 이 비어
|
|
56
|
+
* 있고, 그 사이엔 통화가 연결되지 않아 도구를 쓸 일도 없다. 그래서 이때는 유저 도구만
|
|
57
|
+
* 올리고, attach() 가 setter 실행 후 실제 도구를 붙이는 유일한 조립 지점이 된다.
|
|
58
|
+
*/
|
|
59
|
+
private _applyTools;
|
|
60
|
+
/** `@tool` / MCP 도구(ToolRegistry)를 LiveKit function tool 로 노출한다. */
|
|
61
|
+
private _bridgeRegistry;
|
|
62
|
+
/**
|
|
63
|
+
* LiveKit 의 최종 대화 항목을 ClawOps `transcript` 훅으로 흘려보낸다.
|
|
64
|
+
*
|
|
65
|
+
* 네이티브 세션(OpenAI/Gemini/Pipeline)은 `call._emit('transcript', role, text)` 를
|
|
66
|
+
* 부른다. `@on('transcript')` 로 트랜스크립트를 모으는 기존 앱이 세션만 바꿔도 그대로
|
|
67
|
+
* 돌게 하려면 여기서 같은 계약을 재현한다.
|
|
68
|
+
*
|
|
69
|
+
* `conversation_item_added` 는 user·assistant 최종 메시지가 히스토리에 커밋될 때 한
|
|
70
|
+
* 번씩 뜬다 — 부분(interim) 중복 없이 최종만 얻는다. item 이 handoff 등 ChatMessage 가
|
|
71
|
+
* 아니면 role/textContent 가 없어 자연히 걸러진다.
|
|
72
|
+
*/
|
|
73
|
+
private _wireTranscripts;
|
|
74
|
+
private _closeAgentSession;
|
|
75
|
+
/**
|
|
76
|
+
* LiveKit 이 조용히 넘어가는 두 조합을 우리가 막는다 — 실수하면 "말을 못 하는
|
|
77
|
+
* 에이전트"가 아무 예외 없이 배포된다.
|
|
78
|
+
*/
|
|
79
|
+
private _validate;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* ClawOps 전화 내장 도구를 LiveKit Toolset 으로 노출한다.
|
|
84
|
+
*
|
|
85
|
+
* `hang_up` / `collect_dtmf` / `send_dtmf` / `transfer_call` — 스키마와 동작을
|
|
86
|
+
* `pipeline/builtin-tool-schemas.ts` 에서 그대로 재사용한다 (핸들러 중복 없음).
|
|
87
|
+
*
|
|
88
|
+
* 설계 메모:
|
|
89
|
+
* - LiveKit 이 파는 전화 도구(`end_call`/`send_dtmf`)는 room(`get_job_context()`)을
|
|
90
|
+
* 잡아 room 없이는 죽으므로 쓰지 않는다. 패턴만 가져오고 실행은 우리 것을 쓴다.
|
|
91
|
+
* - `CallSession` 은 클로저 상태로 들고 간다 (prewarm -> attach 시 setCall 로 교체).
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
interface ClawOpsPhoneTools {
|
|
95
|
+
/** LiveKit Toolset 인스턴스 — `agent.updateTools([...])` 에 넣는 ToolContextEntry. */
|
|
96
|
+
toolset: unknown;
|
|
97
|
+
/** prewarm -> attach 시 실제 통화로 교체한다. */
|
|
98
|
+
setCall(call: CallSession): void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* ClawOps 통화 제어 도구 묶음을 만든다.
|
|
102
|
+
*
|
|
103
|
+
* @param llm 동적 로드한 `@livekit/agents` 의 `llm` 네임스페이스 (`tool`/`Toolset`/`ToolFlag`).
|
|
104
|
+
* @param options.enabled 활성화할 내장 도구 (`ClawOpsAgent(builtinTools=...)` 와 동일).
|
|
105
|
+
* @param options.excludeNames 유저/registry 도구와 이름이 겹쳐 제외할 내장 도구 이름.
|
|
106
|
+
*/
|
|
107
|
+
declare function createClawOpsPhoneTools(llm: any, options: {
|
|
108
|
+
enabled: Set<BuiltinTool>;
|
|
109
|
+
excludeNames?: Set<string>;
|
|
110
|
+
}): ClawOpsPhoneTools;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* LiveKit AudioInput/AudioOutput <-> ClawOps Media WS 브리지.
|
|
114
|
+
*
|
|
115
|
+
* LiveKit 의 AgentSession 은 room 없이도 동작한다 — `session.input.audio` /
|
|
116
|
+
* `session.output.audio` 에 커스텀 구현을 꽂으면 된다. 이 모듈이 그 구현이다.
|
|
117
|
+
*
|
|
118
|
+
* 전화망 wire format 은 G.711 μ-law 8kHz, 160바이트(20ms) 프레임이다. AudioOutput 이
|
|
119
|
+
* `sampleRate=8000` 을 선언하므로 프레임워크가 TTS 출력을 8kHz 로 리샘플해준다 —
|
|
120
|
+
* 우리는 μ-law 인코딩만 한다.
|
|
121
|
+
*
|
|
122
|
+
* 참조 구현: agents-js `voice/console_io.ts` 의 `TcpAudioInput`/`TcpAudioOutput` —
|
|
123
|
+
* 같은 문제(비-WebRTC transport 를 AgentSession 에 물리기)를 푸는 LiveKit 자신의 코드다.
|
|
124
|
+
* ⚠️ Python(clawops-python) 과 달리 agents-js 는 `AudioInput` 베이스와
|
|
125
|
+
* `TranscriptionSynchronizer` 를 public export 하지 않는다. 그래서
|
|
126
|
+
* - AudioInput 은 구조적으로 구현(ReadableStream 노출)하고,
|
|
127
|
+
* - 재생 완료(playout) 판정은 `TcpAudioOutput` 과 동일한 Future 핸드셰이크로 하되
|
|
128
|
+
* 완료 신호를 Media WS `mark` echo 로 받는다. (LiveKit 자신의 `AgentsConsole` 도
|
|
129
|
+
* room-less 경로에서 transcription sync 를 생략하므로 우리도 생략한다.)
|
|
130
|
+
*
|
|
131
|
+
* ⚠️ AudioOutput 계약 2가지 — 어기면 조용히 깨진다:
|
|
132
|
+
* 1. 첫 프레임에서 `onPlaybackStarted()` 를 반드시 호출한다. 빼면 assistant 메시지가
|
|
133
|
+
* 히스토리에서 통째로 사라진다.
|
|
134
|
+
* 2. `onPlaybackFinished()` 는 flush 세그먼트당 정확히 한 번, 취소되더라도 반드시
|
|
135
|
+
* 호출한다. 안 그러면 프레임워크의 재생 회계가 어긋나 wait_for_playout 이 영구 대기한다.
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
declare const SAMPLE_RATE = 8000;
|
|
139
|
+
/** 전화망 wire rate. AudioOutput 이 선언하면 프레임워크가 여기 맞춰 리샘플한다. */
|
|
140
|
+
declare const FRAME_BYTES = 160;
|
|
141
|
+
/** mark 대기 timeout = 밀어넣은 오디오 길이(ms) + 이 여유. */
|
|
142
|
+
/** ClawOpsAudioOutput 이 오디오를 흘려보내는 대상(실 통화 또는 prewarm 버퍼)의 최소 계약. */
|
|
143
|
+
interface AudioSink {
|
|
144
|
+
sendAudio(audio: Buffer): void | Promise<void>;
|
|
145
|
+
clearAudio(): void;
|
|
146
|
+
}
|
|
147
|
+
/** 외부에서 참조할 인스턴스 타입 (실제 클래스는 optional dep 로드 후 팩토리에서 정의된다). */
|
|
148
|
+
interface ClawOpsAudioInputInstance {
|
|
149
|
+
readonly stream: unknown;
|
|
150
|
+
pushUlaw(ulaw: Buffer): void;
|
|
151
|
+
endInput(): void;
|
|
152
|
+
close(): Promise<void>;
|
|
153
|
+
onAttached(): void;
|
|
154
|
+
onDetached(): void;
|
|
155
|
+
}
|
|
156
|
+
interface ClawOpsAudioOutputInstance {
|
|
157
|
+
setCall(call: CallSession): void;
|
|
158
|
+
close(): void;
|
|
159
|
+
}
|
|
160
|
+
interface ClawOpsIO {
|
|
161
|
+
ClawOpsAudioInput: new () => ClawOpsAudioInputInstance;
|
|
162
|
+
ClawOpsAudioOutput: new (call: AudioSink) => ClawOpsAudioOutputInstance;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* agents-js / rtc-node 를 동적 로드해 IO 클래스를 정의한다 (1회 캐시).
|
|
166
|
+
*
|
|
167
|
+
* 클래스가 `voice.AudioOutput`(optional dep)을 상속하므로 top-level 이 아니라
|
|
168
|
+
* import 성공 후 팩토리 안에서 정의해야 한다.
|
|
169
|
+
*/
|
|
170
|
+
declare function loadClawOpsIO(): Promise<ClawOpsIO>;
|
|
171
|
+
|
|
172
|
+
export { type ClawOpsAudioInputInstance, type ClawOpsAudioOutputInstance, type ClawOpsIO, type ClawOpsPhoneTools, FRAME_BYTES, type LiveKitCreateFn, LiveKitSession, SAMPLE_RATE, createClawOpsPhoneTools, loadClawOpsIO };
|