@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,338 @@
|
|
|
1
|
+
import { Logger } from 'pino';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tool registry for function-calling in voice agent pipelines.
|
|
6
|
+
* Uses Zod schemas for parameter validation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface FunctionTool {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
parameters: Record<string, unknown>;
|
|
13
|
+
required: string[];
|
|
14
|
+
handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
interface OpenAIToolDefinition {
|
|
17
|
+
type: 'function';
|
|
18
|
+
function: {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
parameters: {
|
|
22
|
+
type: 'object';
|
|
23
|
+
properties: Record<string, unknown>;
|
|
24
|
+
required: string[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decorator / identity function for marking a function as a tool handler.
|
|
30
|
+
* In TypeScript, this simply returns the function as-is. Use with ToolRegistry.register().
|
|
31
|
+
*/
|
|
32
|
+
declare function functionTool<T extends (...args: unknown[]) => unknown>(fn: T): T;
|
|
33
|
+
declare class ToolRegistry {
|
|
34
|
+
private _tools;
|
|
35
|
+
private _mcpTools;
|
|
36
|
+
/** Register a function tool. */
|
|
37
|
+
register(tool: FunctionTool): void;
|
|
38
|
+
/** Register tools discovered from MCP servers. */
|
|
39
|
+
registerMcpTools(tools: FunctionTool[]): void;
|
|
40
|
+
/** Clear all MCP-registered tools. */
|
|
41
|
+
clearMcpTools(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Create a fork (shallow copy) of this registry.
|
|
44
|
+
* Useful for per-session tool isolation.
|
|
45
|
+
*/
|
|
46
|
+
fork(): ToolRegistry;
|
|
47
|
+
/** Check if a tool is registered. */
|
|
48
|
+
has(name: string): boolean;
|
|
49
|
+
/** Get a tool by name. */
|
|
50
|
+
get(name: string): FunctionTool | undefined;
|
|
51
|
+
/** Convert all registered tools to OpenAI function-calling format. */
|
|
52
|
+
toOpenAITools(): OpenAIToolDefinition[];
|
|
53
|
+
/** Call a tool by name with the given arguments. */
|
|
54
|
+
call(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
55
|
+
/** Get the number of registered tools (including MCP). */
|
|
56
|
+
get size(): number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Helper to convert a Zod object schema into a JSON Schema properties + required list.
|
|
60
|
+
*/
|
|
61
|
+
declare function zodToToolParams(schema: z.ZodObject<z.ZodRawShape>): {
|
|
62
|
+
parameters: Record<string, unknown>;
|
|
63
|
+
required: string[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
interface ProviderInfo {
|
|
67
|
+
provider: string;
|
|
68
|
+
model: string;
|
|
69
|
+
}
|
|
70
|
+
interface SessionTelemetry {
|
|
71
|
+
sessionType: string;
|
|
72
|
+
llm: ProviderInfo | null;
|
|
73
|
+
stt: ProviderInfo | null;
|
|
74
|
+
tts: ProviderInfo | null;
|
|
75
|
+
voice: string | null;
|
|
76
|
+
language: string;
|
|
77
|
+
greetingEnabled: boolean;
|
|
78
|
+
recordingEnabled: boolean;
|
|
79
|
+
toolCount: number;
|
|
80
|
+
mcpServerCount: number;
|
|
81
|
+
builtinTools: string[];
|
|
82
|
+
}
|
|
83
|
+
interface CallMetrics {
|
|
84
|
+
firstResponseMs: number | null;
|
|
85
|
+
turnCount: number;
|
|
86
|
+
toolCallCount: number;
|
|
87
|
+
toolErrorCount: number;
|
|
88
|
+
bargeInCount: number;
|
|
89
|
+
endReason: string | null;
|
|
90
|
+
errors: Array<{
|
|
91
|
+
type: string;
|
|
92
|
+
message: string;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* CallSession represents an active phone call and provides methods to
|
|
98
|
+
* send audio, hang up, and listen for lifecycle events.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
type CallDirection = 'inbound' | 'outbound';
|
|
102
|
+
type CallStatus = 'ringing' | 'active' | 'ended';
|
|
103
|
+
/**
|
|
104
|
+
* Session event handler: receives (call, ...args) like the Python SDK.
|
|
105
|
+
* For example, 'transcript' events pass (call, role, text).
|
|
106
|
+
*/
|
|
107
|
+
type SessionEventHandler = (...args: any[]) => void | Promise<void>;
|
|
108
|
+
interface SendAudioFn {
|
|
109
|
+
(audio: Buffer): void;
|
|
110
|
+
}
|
|
111
|
+
interface ClearAudioFn {
|
|
112
|
+
(): void;
|
|
113
|
+
}
|
|
114
|
+
interface HangupFn {
|
|
115
|
+
(): void | Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
interface SendDtmfFn {
|
|
118
|
+
(digit: string): Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
interface TransferFn {
|
|
121
|
+
(params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
122
|
+
}
|
|
123
|
+
declare class CallSession {
|
|
124
|
+
readonly callId: string;
|
|
125
|
+
readonly fromNumber: string;
|
|
126
|
+
readonly toNumber: string;
|
|
127
|
+
readonly accountId: string;
|
|
128
|
+
readonly direction: CallDirection;
|
|
129
|
+
readonly startTime: Date;
|
|
130
|
+
readonly metadata: Record<string, unknown>;
|
|
131
|
+
private _status;
|
|
132
|
+
private _sendAudioFn;
|
|
133
|
+
private _clearAudioFn;
|
|
134
|
+
private _hangupFn;
|
|
135
|
+
/** @internal */ _sendDtmfFn: SendDtmfFn | null;
|
|
136
|
+
/** @internal */ _transferFn: TransferFn | null;
|
|
137
|
+
/** @internal */ _isTransportConnected: (() => boolean) | null;
|
|
138
|
+
/**
|
|
139
|
+
* @internal Media WS playout 마커 훅. LiveKit(`ClawOpsAudioOutput`)이 재생 완료
|
|
140
|
+
* 판정(mark echo)과 barge-in 절단 위치 계산에 쓴다. 다른 세션 타입은 읽지 않는다.
|
|
141
|
+
* prewarm 중(BufferingCall)에는 바인딩되지 않아 null 이다.
|
|
142
|
+
*/
|
|
143
|
+
/** @internal */ _sendMark: ((name: string) => void) | null;
|
|
144
|
+
/** @internal */ _waitForMark: ((name: string, timeoutMs: number) => Promise<void>) | null;
|
|
145
|
+
/** @internal */ _flushTransport: (() => Promise<void>) | null;
|
|
146
|
+
private _dtmfCollectorActive;
|
|
147
|
+
private _dtmfResolvers;
|
|
148
|
+
private _dtmfBuffer;
|
|
149
|
+
private _log;
|
|
150
|
+
private _handlers;
|
|
151
|
+
private _endedPromise;
|
|
152
|
+
private _resolveEnded;
|
|
153
|
+
private _metrics;
|
|
154
|
+
private _firstResponseSent;
|
|
155
|
+
constructor(options: {
|
|
156
|
+
callId: string;
|
|
157
|
+
fromNumber: string;
|
|
158
|
+
toNumber: string;
|
|
159
|
+
accountId: string;
|
|
160
|
+
direction: CallDirection;
|
|
161
|
+
metadata?: Record<string, unknown>;
|
|
162
|
+
});
|
|
163
|
+
setLogger(logger: Logger): void;
|
|
164
|
+
get status(): CallStatus;
|
|
165
|
+
get duration(): number;
|
|
166
|
+
get metrics(): Readonly<CallMetrics>;
|
|
167
|
+
recordFirstResponse(): void;
|
|
168
|
+
recordTurn(): void;
|
|
169
|
+
recordToolCall(): void;
|
|
170
|
+
recordToolError(err: Error): void;
|
|
171
|
+
recordBargeIn(): void;
|
|
172
|
+
recordEndReason(reason: string): void;
|
|
173
|
+
/** Bind transport functions (called internally by the agent). */
|
|
174
|
+
_bindTransport(send: SendAudioFn, clear: ClearAudioFn, hangup: HangupFn, sendDtmf?: SendDtmfFn, isConnected?: () => boolean): void;
|
|
175
|
+
/** Send PCM16 or ulaw audio to the caller. */
|
|
176
|
+
sendAudio(audio: Buffer): void;
|
|
177
|
+
/** Clear any queued outbound audio. */
|
|
178
|
+
clearAudio(): void;
|
|
179
|
+
/** Hang up the call, waiting for pending audio to finish. */
|
|
180
|
+
hangup(): Promise<void>;
|
|
181
|
+
/** @internal Route a received DTMF digit to an active collector or buffer. */
|
|
182
|
+
_routeDtmf(digit: string): void;
|
|
183
|
+
/** Collect DTMF digits from the caller. */
|
|
184
|
+
collectDtmf(options: {
|
|
185
|
+
maxDigits: number;
|
|
186
|
+
finishOnKey?: string;
|
|
187
|
+
timeout?: number;
|
|
188
|
+
secure?: boolean;
|
|
189
|
+
}): Promise<string>;
|
|
190
|
+
/** Send a sequence of DTMF digits. */
|
|
191
|
+
sendDtmfSequence(digits: string): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Transfer the call to a phone number or SIP endpoint.
|
|
194
|
+
*
|
|
195
|
+
* destinationType='pstn' (default): `to` is a phone number dialed via carrier.
|
|
196
|
+
* destinationType='sip': `to` is a SIP URI (e.g. `sip:user@host`) connected
|
|
197
|
+
* directly to a SIP endpoint without going through the PSTN carrier. Requires the
|
|
198
|
+
* account to have an active `sip_trunk` add-on; otherwise the transfer fails and
|
|
199
|
+
* the call continues with the AI (result `{ status: 'failed', ... }`).
|
|
200
|
+
*/
|
|
201
|
+
transfer(to: string, options?: {
|
|
202
|
+
destinationType?: 'pstn' | 'sip';
|
|
203
|
+
mode?: 'blind' | 'warm';
|
|
204
|
+
afterTransfer?: 'terminate' | 'return';
|
|
205
|
+
holdMedia?: string;
|
|
206
|
+
whisper?: string;
|
|
207
|
+
context?: Record<string, unknown>;
|
|
208
|
+
callerId?: string;
|
|
209
|
+
timeout?: number;
|
|
210
|
+
}): Promise<Record<string, unknown>>;
|
|
211
|
+
/** Register an event handler. */
|
|
212
|
+
on(event: string, handler: SessionEventHandler): void;
|
|
213
|
+
/** Wait for the call to end. */
|
|
214
|
+
wait(): Promise<void>;
|
|
215
|
+
/** Mark the session as ended (called internally). */
|
|
216
|
+
_markEnded(): void;
|
|
217
|
+
/** Emit an event to registered handlers. Matches Python SDK: _emit(event, ...args) */
|
|
218
|
+
_emit(event: string, ...args: any[]): void;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Built-in tool definitions and selection constants.
|
|
223
|
+
*/
|
|
224
|
+
declare enum BuiltinTool {
|
|
225
|
+
/** 전화 종료 도구. AI가 대화 완료 시 통화를 종료합니다. */
|
|
226
|
+
HANG_UP = "hang_up",
|
|
227
|
+
/** DTMF 수집 도구. 사용자의 키패드 입력을 수집합니다. */
|
|
228
|
+
COLLECT_DTMF = "collect_dtmf",
|
|
229
|
+
/** DTMF 전송 도구. ARS 탐색이나 내선번호 입력에 사용합니다. */
|
|
230
|
+
SEND_DTMF = "send_dtmf",
|
|
231
|
+
/** 통화 전환 도구. 현재 통화를 다른 번호로 전환합니다. */
|
|
232
|
+
TRANSFER_CALL = "transfer_call",
|
|
233
|
+
/** 모든 내장 도구를 활성화합니다. (기본값) */
|
|
234
|
+
ALL = "all",
|
|
235
|
+
/** 모든 내장 도구를 비활성화합니다. */
|
|
236
|
+
NONE = "none"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Base interfaces for the voice agent pipeline.
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
/** Speech recognition event. */
|
|
244
|
+
interface SpeechEvent {
|
|
245
|
+
type: 'interim' | 'final';
|
|
246
|
+
transcript: string;
|
|
247
|
+
}
|
|
248
|
+
/** Conversation message for LLM context. */
|
|
249
|
+
interface ConversationMessage {
|
|
250
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
251
|
+
content: string;
|
|
252
|
+
name?: string;
|
|
253
|
+
tool_call_id?: string;
|
|
254
|
+
}
|
|
255
|
+
/** Tool call request from an LLM. */
|
|
256
|
+
interface ToolCallRequest {
|
|
257
|
+
id: string;
|
|
258
|
+
name: string;
|
|
259
|
+
arguments: string;
|
|
260
|
+
}
|
|
261
|
+
/** LLM response chunk (for streaming). */
|
|
262
|
+
interface LLMChunk {
|
|
263
|
+
type: 'text' | 'tool_call' | 'done';
|
|
264
|
+
text?: string;
|
|
265
|
+
toolCall?: ToolCallRequest;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Session interface for realtime (speech-to-speech) providers.
|
|
269
|
+
* Implementations: OpenAIRealtime, GeminiRealtime
|
|
270
|
+
*/
|
|
271
|
+
interface Session {
|
|
272
|
+
/** Start the realtime session with the call session and tools. Thin wrapper over prewarm + attach. */
|
|
273
|
+
start(callSession: CallSession, tools?: ToolRegistry): Promise<void>;
|
|
274
|
+
/**
|
|
275
|
+
* Open LLM connection (and session.update + optional greeting trigger) without a CallSession.
|
|
276
|
+
*
|
|
277
|
+
* Audio deltas produced during the prewarm window accumulate in an internal BufferingCall.
|
|
278
|
+
* attach() then replaces the BufferingCall with the real CallSession and flushes the buffer.
|
|
279
|
+
*
|
|
280
|
+
* Tools may optionally be injected here (alternative to calling setToolRegistry separately);
|
|
281
|
+
* the Session is free to ignore the argument if tools are already set.
|
|
282
|
+
*/
|
|
283
|
+
prewarm(tools?: ToolRegistry): Promise<void>;
|
|
284
|
+
/** Attach a real CallSession to a prewarmed session and flush any buffered audio. */
|
|
285
|
+
attach(callSession: CallSession): Promise<void>;
|
|
286
|
+
/** Feed raw audio into the session. */
|
|
287
|
+
feedAudio(audio: Buffer, timestamp?: number): void;
|
|
288
|
+
/** Feed DTMF digits into the LLM context and trigger a response. */
|
|
289
|
+
feedDtmf?(digits: string): Promise<void>;
|
|
290
|
+
/** Stop the session. */
|
|
291
|
+
stop(): Promise<void>;
|
|
292
|
+
/** Set the logger instance for this session. */
|
|
293
|
+
setLogger?(logger: Logger): void;
|
|
294
|
+
/** Return session telemetry for reporting. */
|
|
295
|
+
getTelemetry?(): SessionTelemetry | null;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Speech-to-Text provider interface.
|
|
299
|
+
*/
|
|
300
|
+
interface STT {
|
|
301
|
+
/**
|
|
302
|
+
* Transcribe audio. Returns an async generator of speech events.
|
|
303
|
+
* The generator yields interim results and a final result.
|
|
304
|
+
*/
|
|
305
|
+
transcribe(audioStream: AsyncIterable<Buffer>, options?: {
|
|
306
|
+
language?: string;
|
|
307
|
+
sampleRate?: number;
|
|
308
|
+
}): AsyncGenerator<SpeechEvent>;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* LLM (Large Language Model) provider interface.
|
|
312
|
+
*/
|
|
313
|
+
interface LLM {
|
|
314
|
+
/**
|
|
315
|
+
* Generate a response from the LLM.
|
|
316
|
+
* Returns an async generator of response chunks.
|
|
317
|
+
*/
|
|
318
|
+
generate(messages: ConversationMessage[], options?: {
|
|
319
|
+
tools?: ToolRegistry;
|
|
320
|
+
temperature?: number;
|
|
321
|
+
maxTokens?: number;
|
|
322
|
+
}): AsyncGenerator<LLMChunk>;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Text-to-Speech provider interface.
|
|
326
|
+
*/
|
|
327
|
+
interface TTS {
|
|
328
|
+
/**
|
|
329
|
+
* Synthesize text to audio.
|
|
330
|
+
* Returns an async generator of PCM16 audio chunks.
|
|
331
|
+
*/
|
|
332
|
+
synthesize(text: string | AsyncIterable<string>, options?: {
|
|
333
|
+
voice?: string;
|
|
334
|
+
sampleRate?: number;
|
|
335
|
+
}): AsyncGenerator<Buffer>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export { BuiltinTool as B, CallSession as C, type FunctionTool as F, type LLM as L, type OpenAIToolDefinition as O, type Session as S, type TTS as T, type STT as a, ToolRegistry as b, type SessionTelemetry as c, type SpeechEvent as d, type ConversationMessage as e, type LLMChunk as f, type CallDirection as g, type CallStatus as h, type ToolCallRequest as i, functionTool as j, zodToToolParams as z };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { Logger } from 'pino';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tool registry for function-calling in voice agent pipelines.
|
|
6
|
+
* Uses Zod schemas for parameter validation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface FunctionTool {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
parameters: Record<string, unknown>;
|
|
13
|
+
required: string[];
|
|
14
|
+
handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
interface OpenAIToolDefinition {
|
|
17
|
+
type: 'function';
|
|
18
|
+
function: {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
parameters: {
|
|
22
|
+
type: 'object';
|
|
23
|
+
properties: Record<string, unknown>;
|
|
24
|
+
required: string[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decorator / identity function for marking a function as a tool handler.
|
|
30
|
+
* In TypeScript, this simply returns the function as-is. Use with ToolRegistry.register().
|
|
31
|
+
*/
|
|
32
|
+
declare function functionTool<T extends (...args: unknown[]) => unknown>(fn: T): T;
|
|
33
|
+
declare class ToolRegistry {
|
|
34
|
+
private _tools;
|
|
35
|
+
private _mcpTools;
|
|
36
|
+
/** Register a function tool. */
|
|
37
|
+
register(tool: FunctionTool): void;
|
|
38
|
+
/** Register tools discovered from MCP servers. */
|
|
39
|
+
registerMcpTools(tools: FunctionTool[]): void;
|
|
40
|
+
/** Clear all MCP-registered tools. */
|
|
41
|
+
clearMcpTools(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Create a fork (shallow copy) of this registry.
|
|
44
|
+
* Useful for per-session tool isolation.
|
|
45
|
+
*/
|
|
46
|
+
fork(): ToolRegistry;
|
|
47
|
+
/** Check if a tool is registered. */
|
|
48
|
+
has(name: string): boolean;
|
|
49
|
+
/** Get a tool by name. */
|
|
50
|
+
get(name: string): FunctionTool | undefined;
|
|
51
|
+
/** Convert all registered tools to OpenAI function-calling format. */
|
|
52
|
+
toOpenAITools(): OpenAIToolDefinition[];
|
|
53
|
+
/** Call a tool by name with the given arguments. */
|
|
54
|
+
call(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
55
|
+
/** Get the number of registered tools (including MCP). */
|
|
56
|
+
get size(): number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Helper to convert a Zod object schema into a JSON Schema properties + required list.
|
|
60
|
+
*/
|
|
61
|
+
declare function zodToToolParams(schema: z.ZodObject<z.ZodRawShape>): {
|
|
62
|
+
parameters: Record<string, unknown>;
|
|
63
|
+
required: string[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
interface ProviderInfo {
|
|
67
|
+
provider: string;
|
|
68
|
+
model: string;
|
|
69
|
+
}
|
|
70
|
+
interface SessionTelemetry {
|
|
71
|
+
sessionType: string;
|
|
72
|
+
llm: ProviderInfo | null;
|
|
73
|
+
stt: ProviderInfo | null;
|
|
74
|
+
tts: ProviderInfo | null;
|
|
75
|
+
voice: string | null;
|
|
76
|
+
language: string;
|
|
77
|
+
greetingEnabled: boolean;
|
|
78
|
+
recordingEnabled: boolean;
|
|
79
|
+
toolCount: number;
|
|
80
|
+
mcpServerCount: number;
|
|
81
|
+
builtinTools: string[];
|
|
82
|
+
}
|
|
83
|
+
interface CallMetrics {
|
|
84
|
+
firstResponseMs: number | null;
|
|
85
|
+
turnCount: number;
|
|
86
|
+
toolCallCount: number;
|
|
87
|
+
toolErrorCount: number;
|
|
88
|
+
bargeInCount: number;
|
|
89
|
+
endReason: string | null;
|
|
90
|
+
errors: Array<{
|
|
91
|
+
type: string;
|
|
92
|
+
message: string;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* CallSession represents an active phone call and provides methods to
|
|
98
|
+
* send audio, hang up, and listen for lifecycle events.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
type CallDirection = 'inbound' | 'outbound';
|
|
102
|
+
type CallStatus = 'ringing' | 'active' | 'ended';
|
|
103
|
+
/**
|
|
104
|
+
* Session event handler: receives (call, ...args) like the Python SDK.
|
|
105
|
+
* For example, 'transcript' events pass (call, role, text).
|
|
106
|
+
*/
|
|
107
|
+
type SessionEventHandler = (...args: any[]) => void | Promise<void>;
|
|
108
|
+
interface SendAudioFn {
|
|
109
|
+
(audio: Buffer): void;
|
|
110
|
+
}
|
|
111
|
+
interface ClearAudioFn {
|
|
112
|
+
(): void;
|
|
113
|
+
}
|
|
114
|
+
interface HangupFn {
|
|
115
|
+
(): void | Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
interface SendDtmfFn {
|
|
118
|
+
(digit: string): Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
interface TransferFn {
|
|
121
|
+
(params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
122
|
+
}
|
|
123
|
+
declare class CallSession {
|
|
124
|
+
readonly callId: string;
|
|
125
|
+
readonly fromNumber: string;
|
|
126
|
+
readonly toNumber: string;
|
|
127
|
+
readonly accountId: string;
|
|
128
|
+
readonly direction: CallDirection;
|
|
129
|
+
readonly startTime: Date;
|
|
130
|
+
readonly metadata: Record<string, unknown>;
|
|
131
|
+
private _status;
|
|
132
|
+
private _sendAudioFn;
|
|
133
|
+
private _clearAudioFn;
|
|
134
|
+
private _hangupFn;
|
|
135
|
+
/** @internal */ _sendDtmfFn: SendDtmfFn | null;
|
|
136
|
+
/** @internal */ _transferFn: TransferFn | null;
|
|
137
|
+
/** @internal */ _isTransportConnected: (() => boolean) | null;
|
|
138
|
+
/**
|
|
139
|
+
* @internal Media WS playout 마커 훅. LiveKit(`ClawOpsAudioOutput`)이 재생 완료
|
|
140
|
+
* 판정(mark echo)과 barge-in 절단 위치 계산에 쓴다. 다른 세션 타입은 읽지 않는다.
|
|
141
|
+
* prewarm 중(BufferingCall)에는 바인딩되지 않아 null 이다.
|
|
142
|
+
*/
|
|
143
|
+
/** @internal */ _sendMark: ((name: string) => void) | null;
|
|
144
|
+
/** @internal */ _waitForMark: ((name: string, timeoutMs: number) => Promise<void>) | null;
|
|
145
|
+
/** @internal */ _flushTransport: (() => Promise<void>) | null;
|
|
146
|
+
private _dtmfCollectorActive;
|
|
147
|
+
private _dtmfResolvers;
|
|
148
|
+
private _dtmfBuffer;
|
|
149
|
+
private _log;
|
|
150
|
+
private _handlers;
|
|
151
|
+
private _endedPromise;
|
|
152
|
+
private _resolveEnded;
|
|
153
|
+
private _metrics;
|
|
154
|
+
private _firstResponseSent;
|
|
155
|
+
constructor(options: {
|
|
156
|
+
callId: string;
|
|
157
|
+
fromNumber: string;
|
|
158
|
+
toNumber: string;
|
|
159
|
+
accountId: string;
|
|
160
|
+
direction: CallDirection;
|
|
161
|
+
metadata?: Record<string, unknown>;
|
|
162
|
+
});
|
|
163
|
+
setLogger(logger: Logger): void;
|
|
164
|
+
get status(): CallStatus;
|
|
165
|
+
get duration(): number;
|
|
166
|
+
get metrics(): Readonly<CallMetrics>;
|
|
167
|
+
recordFirstResponse(): void;
|
|
168
|
+
recordTurn(): void;
|
|
169
|
+
recordToolCall(): void;
|
|
170
|
+
recordToolError(err: Error): void;
|
|
171
|
+
recordBargeIn(): void;
|
|
172
|
+
recordEndReason(reason: string): void;
|
|
173
|
+
/** Bind transport functions (called internally by the agent). */
|
|
174
|
+
_bindTransport(send: SendAudioFn, clear: ClearAudioFn, hangup: HangupFn, sendDtmf?: SendDtmfFn, isConnected?: () => boolean): void;
|
|
175
|
+
/** Send PCM16 or ulaw audio to the caller. */
|
|
176
|
+
sendAudio(audio: Buffer): void;
|
|
177
|
+
/** Clear any queued outbound audio. */
|
|
178
|
+
clearAudio(): void;
|
|
179
|
+
/** Hang up the call, waiting for pending audio to finish. */
|
|
180
|
+
hangup(): Promise<void>;
|
|
181
|
+
/** @internal Route a received DTMF digit to an active collector or buffer. */
|
|
182
|
+
_routeDtmf(digit: string): void;
|
|
183
|
+
/** Collect DTMF digits from the caller. */
|
|
184
|
+
collectDtmf(options: {
|
|
185
|
+
maxDigits: number;
|
|
186
|
+
finishOnKey?: string;
|
|
187
|
+
timeout?: number;
|
|
188
|
+
secure?: boolean;
|
|
189
|
+
}): Promise<string>;
|
|
190
|
+
/** Send a sequence of DTMF digits. */
|
|
191
|
+
sendDtmfSequence(digits: string): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Transfer the call to a phone number or SIP endpoint.
|
|
194
|
+
*
|
|
195
|
+
* destinationType='pstn' (default): `to` is a phone number dialed via carrier.
|
|
196
|
+
* destinationType='sip': `to` is a SIP URI (e.g. `sip:user@host`) connected
|
|
197
|
+
* directly to a SIP endpoint without going through the PSTN carrier. Requires the
|
|
198
|
+
* account to have an active `sip_trunk` add-on; otherwise the transfer fails and
|
|
199
|
+
* the call continues with the AI (result `{ status: 'failed', ... }`).
|
|
200
|
+
*/
|
|
201
|
+
transfer(to: string, options?: {
|
|
202
|
+
destinationType?: 'pstn' | 'sip';
|
|
203
|
+
mode?: 'blind' | 'warm';
|
|
204
|
+
afterTransfer?: 'terminate' | 'return';
|
|
205
|
+
holdMedia?: string;
|
|
206
|
+
whisper?: string;
|
|
207
|
+
context?: Record<string, unknown>;
|
|
208
|
+
callerId?: string;
|
|
209
|
+
timeout?: number;
|
|
210
|
+
}): Promise<Record<string, unknown>>;
|
|
211
|
+
/** Register an event handler. */
|
|
212
|
+
on(event: string, handler: SessionEventHandler): void;
|
|
213
|
+
/** Wait for the call to end. */
|
|
214
|
+
wait(): Promise<void>;
|
|
215
|
+
/** Mark the session as ended (called internally). */
|
|
216
|
+
_markEnded(): void;
|
|
217
|
+
/** Emit an event to registered handlers. Matches Python SDK: _emit(event, ...args) */
|
|
218
|
+
_emit(event: string, ...args: any[]): void;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Built-in tool definitions and selection constants.
|
|
223
|
+
*/
|
|
224
|
+
declare enum BuiltinTool {
|
|
225
|
+
/** 전화 종료 도구. AI가 대화 완료 시 통화를 종료합니다. */
|
|
226
|
+
HANG_UP = "hang_up",
|
|
227
|
+
/** DTMF 수집 도구. 사용자의 키패드 입력을 수집합니다. */
|
|
228
|
+
COLLECT_DTMF = "collect_dtmf",
|
|
229
|
+
/** DTMF 전송 도구. ARS 탐색이나 내선번호 입력에 사용합니다. */
|
|
230
|
+
SEND_DTMF = "send_dtmf",
|
|
231
|
+
/** 통화 전환 도구. 현재 통화를 다른 번호로 전환합니다. */
|
|
232
|
+
TRANSFER_CALL = "transfer_call",
|
|
233
|
+
/** 모든 내장 도구를 활성화합니다. (기본값) */
|
|
234
|
+
ALL = "all",
|
|
235
|
+
/** 모든 내장 도구를 비활성화합니다. */
|
|
236
|
+
NONE = "none"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Base interfaces for the voice agent pipeline.
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
/** Speech recognition event. */
|
|
244
|
+
interface SpeechEvent {
|
|
245
|
+
type: 'interim' | 'final';
|
|
246
|
+
transcript: string;
|
|
247
|
+
}
|
|
248
|
+
/** Conversation message for LLM context. */
|
|
249
|
+
interface ConversationMessage {
|
|
250
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
251
|
+
content: string;
|
|
252
|
+
name?: string;
|
|
253
|
+
tool_call_id?: string;
|
|
254
|
+
}
|
|
255
|
+
/** Tool call request from an LLM. */
|
|
256
|
+
interface ToolCallRequest {
|
|
257
|
+
id: string;
|
|
258
|
+
name: string;
|
|
259
|
+
arguments: string;
|
|
260
|
+
}
|
|
261
|
+
/** LLM response chunk (for streaming). */
|
|
262
|
+
interface LLMChunk {
|
|
263
|
+
type: 'text' | 'tool_call' | 'done';
|
|
264
|
+
text?: string;
|
|
265
|
+
toolCall?: ToolCallRequest;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Session interface for realtime (speech-to-speech) providers.
|
|
269
|
+
* Implementations: OpenAIRealtime, GeminiRealtime
|
|
270
|
+
*/
|
|
271
|
+
interface Session {
|
|
272
|
+
/** Start the realtime session with the call session and tools. Thin wrapper over prewarm + attach. */
|
|
273
|
+
start(callSession: CallSession, tools?: ToolRegistry): Promise<void>;
|
|
274
|
+
/**
|
|
275
|
+
* Open LLM connection (and session.update + optional greeting trigger) without a CallSession.
|
|
276
|
+
*
|
|
277
|
+
* Audio deltas produced during the prewarm window accumulate in an internal BufferingCall.
|
|
278
|
+
* attach() then replaces the BufferingCall with the real CallSession and flushes the buffer.
|
|
279
|
+
*
|
|
280
|
+
* Tools may optionally be injected here (alternative to calling setToolRegistry separately);
|
|
281
|
+
* the Session is free to ignore the argument if tools are already set.
|
|
282
|
+
*/
|
|
283
|
+
prewarm(tools?: ToolRegistry): Promise<void>;
|
|
284
|
+
/** Attach a real CallSession to a prewarmed session and flush any buffered audio. */
|
|
285
|
+
attach(callSession: CallSession): Promise<void>;
|
|
286
|
+
/** Feed raw audio into the session. */
|
|
287
|
+
feedAudio(audio: Buffer, timestamp?: number): void;
|
|
288
|
+
/** Feed DTMF digits into the LLM context and trigger a response. */
|
|
289
|
+
feedDtmf?(digits: string): Promise<void>;
|
|
290
|
+
/** Stop the session. */
|
|
291
|
+
stop(): Promise<void>;
|
|
292
|
+
/** Set the logger instance for this session. */
|
|
293
|
+
setLogger?(logger: Logger): void;
|
|
294
|
+
/** Return session telemetry for reporting. */
|
|
295
|
+
getTelemetry?(): SessionTelemetry | null;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Speech-to-Text provider interface.
|
|
299
|
+
*/
|
|
300
|
+
interface STT {
|
|
301
|
+
/**
|
|
302
|
+
* Transcribe audio. Returns an async generator of speech events.
|
|
303
|
+
* The generator yields interim results and a final result.
|
|
304
|
+
*/
|
|
305
|
+
transcribe(audioStream: AsyncIterable<Buffer>, options?: {
|
|
306
|
+
language?: string;
|
|
307
|
+
sampleRate?: number;
|
|
308
|
+
}): AsyncGenerator<SpeechEvent>;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* LLM (Large Language Model) provider interface.
|
|
312
|
+
*/
|
|
313
|
+
interface LLM {
|
|
314
|
+
/**
|
|
315
|
+
* Generate a response from the LLM.
|
|
316
|
+
* Returns an async generator of response chunks.
|
|
317
|
+
*/
|
|
318
|
+
generate(messages: ConversationMessage[], options?: {
|
|
319
|
+
tools?: ToolRegistry;
|
|
320
|
+
temperature?: number;
|
|
321
|
+
maxTokens?: number;
|
|
322
|
+
}): AsyncGenerator<LLMChunk>;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Text-to-Speech provider interface.
|
|
326
|
+
*/
|
|
327
|
+
interface TTS {
|
|
328
|
+
/**
|
|
329
|
+
* Synthesize text to audio.
|
|
330
|
+
* Returns an async generator of PCM16 audio chunks.
|
|
331
|
+
*/
|
|
332
|
+
synthesize(text: string | AsyncIterable<string>, options?: {
|
|
333
|
+
voice?: string;
|
|
334
|
+
sampleRate?: number;
|
|
335
|
+
}): AsyncGenerator<Buffer>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export { BuiltinTool as B, CallSession as C, type FunctionTool as F, type LLM as L, type OpenAIToolDefinition as O, type Session as S, type TTS as T, type STT as a, ToolRegistry as b, type SessionTelemetry as c, type SpeechEvent as d, type ConversationMessage as e, type LLMChunk as f, type CallDirection as g, type CallStatus as h, type ToolCallRequest as i, functionTool as j, zodToToolParams as z };
|