@teamlearners/clawops 0.36.0 → 0.38.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-XL2VWU36.js → chunk-35XJNMFU.js} +3 -6
- package/dist/chunk-35XJNMFU.js.map +1 -0
- package/dist/{chunk-QD2ZY4UP.cjs → chunk-ASQU6NA2.cjs} +375 -2
- package/dist/chunk-ASQU6NA2.cjs.map +1 -0
- package/dist/{chunk-4YLSDMLL.js → chunk-EO25KTL4.js} +372 -3
- package/dist/chunk-EO25KTL4.js.map +1 -0
- package/dist/chunk-J7UIB4EA.js +6 -0
- package/dist/chunk-J7UIB4EA.js.map +1 -0
- package/dist/{chunk-LKFNV2GI.cjs → chunk-LLPMUDNE.cjs} +2 -6
- package/dist/chunk-LLPMUDNE.cjs.map +1 -0
- package/dist/chunk-NXCYAYFT.cjs +8 -0
- package/dist/chunk-NXCYAYFT.cjs.map +1 -0
- package/dist/{client-DKsEma11.d.cts → client-ZOgpzAPe.d.cts} +124 -24
- package/dist/{client-DKsEma11.d.ts → client-ZOgpzAPe.d.ts} +124 -24
- package/dist/index.cjs +35 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/solapi/index.d.cts +2 -2
- package/dist/solapi/index.d.ts +2 -2
- 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
|
@@ -120,6 +120,20 @@ interface SendDtmfFn {
|
|
|
120
120
|
interface TransferFn {
|
|
121
121
|
(params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* `collectDtmf` 가 이미 돌고 있는데 또 불렸다.
|
|
125
|
+
*
|
|
126
|
+
* 실시간 모델은 도구를 **중복으로 호출한다** — 앞 호출이 아직 자리를 기다리는 동안 같은
|
|
127
|
+
* 도구를 한 번 더 낸다(2026-09-03 실통화에서 관측). 이걸 맨 `Error` 로 던지면 도구 래퍼가
|
|
128
|
+
* `"Error: ..."` 로 감싸 모델에게 돌려주고, 모델은 뭔가 고장난 줄 알고 **그 뒤로 도구를
|
|
129
|
+
* 다시 부르지 않는다.** 그러면 발신자가 누르는 키는 아무도 받지 않는다.
|
|
130
|
+
*
|
|
131
|
+
* `Error` 를 상속하므로 기존에 이걸 잡던 코드는 그대로 돈다. 도구 경로만 이 타입을 따로
|
|
132
|
+
* 잡아 "고장"이 아니라 "기다리라"는 뜻으로 옮긴다.
|
|
133
|
+
*/
|
|
134
|
+
declare class DtmfCollectorBusyError extends Error {
|
|
135
|
+
constructor(message?: string);
|
|
136
|
+
}
|
|
123
137
|
declare class CallSession {
|
|
124
138
|
readonly callId: string;
|
|
125
139
|
readonly fromNumber: string;
|
|
@@ -205,12 +219,27 @@ declare class CallSession {
|
|
|
205
219
|
hangup(): Promise<void>;
|
|
206
220
|
/** @internal Route a received DTMF digit to an active collector or buffer. */
|
|
207
221
|
_routeDtmf(digit: string): void;
|
|
208
|
-
/**
|
|
222
|
+
/**
|
|
223
|
+
* Collect DTMF digits from the caller.
|
|
224
|
+
*
|
|
225
|
+
* `timeout` 은 **자리 사이** 대기다(inter-digit). `<Gather>` 를 비롯한 IVR 관행과 같은
|
|
226
|
+
* 의미이고, 자리가 들어올 때마다 다시 시작한다.
|
|
227
|
+
*
|
|
228
|
+
* `maxWait` 는 그 관행이 만드는 최악을 막는 **전체 상한**이다(기본 30초). 자리마다
|
|
229
|
+
* 리셋되는 타이머만 있으면 수집 하나가 최대 `maxDigits × timeout` 동안 산다 —
|
|
230
|
+
* 11자리·5초면 **55초**이고, 그동안 모델은 이 도구에 붙들려 아무 말도 못 한다.
|
|
231
|
+
* `null` 을 주면 상한 없이 종전대로 돈다.
|
|
232
|
+
*
|
|
233
|
+
* ⚠️ **수집값을 로그에 쓰지 않는다.** 키패드로 받는 값은 카드번호·주민번호일 수 있고,
|
|
234
|
+
* 실제로 그런 구성이 있다. `secure` 와 무관하게 **자릿수만** 남긴다 — 이 옵션은 호환을
|
|
235
|
+
* 위해 남아 있을 뿐 로깅을 좌우하지 않는다.
|
|
236
|
+
*/
|
|
209
237
|
collectDtmf(options: {
|
|
210
238
|
maxDigits: number;
|
|
211
239
|
finishOnKey?: string;
|
|
212
240
|
timeout?: number;
|
|
213
241
|
secure?: boolean;
|
|
242
|
+
maxWait?: number | null;
|
|
214
243
|
}): Promise<string>;
|
|
215
244
|
/** Send a sequence of DTMF digits. */
|
|
216
245
|
sendDtmfSequence(digits: string): Promise<void>;
|
|
@@ -378,4 +407,4 @@ interface TTS {
|
|
|
378
407
|
}): AsyncGenerator<Buffer>;
|
|
379
408
|
}
|
|
380
409
|
|
|
381
|
-
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 };
|
|
410
|
+
export { BuiltinTool as B, CallSession as C, DtmfCollectorBusyError as D, 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 };
|
|
@@ -120,6 +120,20 @@ interface SendDtmfFn {
|
|
|
120
120
|
interface TransferFn {
|
|
121
121
|
(params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* `collectDtmf` 가 이미 돌고 있는데 또 불렸다.
|
|
125
|
+
*
|
|
126
|
+
* 실시간 모델은 도구를 **중복으로 호출한다** — 앞 호출이 아직 자리를 기다리는 동안 같은
|
|
127
|
+
* 도구를 한 번 더 낸다(2026-09-03 실통화에서 관측). 이걸 맨 `Error` 로 던지면 도구 래퍼가
|
|
128
|
+
* `"Error: ..."` 로 감싸 모델에게 돌려주고, 모델은 뭔가 고장난 줄 알고 **그 뒤로 도구를
|
|
129
|
+
* 다시 부르지 않는다.** 그러면 발신자가 누르는 키는 아무도 받지 않는다.
|
|
130
|
+
*
|
|
131
|
+
* `Error` 를 상속하므로 기존에 이걸 잡던 코드는 그대로 돈다. 도구 경로만 이 타입을 따로
|
|
132
|
+
* 잡아 "고장"이 아니라 "기다리라"는 뜻으로 옮긴다.
|
|
133
|
+
*/
|
|
134
|
+
declare class DtmfCollectorBusyError extends Error {
|
|
135
|
+
constructor(message?: string);
|
|
136
|
+
}
|
|
123
137
|
declare class CallSession {
|
|
124
138
|
readonly callId: string;
|
|
125
139
|
readonly fromNumber: string;
|
|
@@ -205,12 +219,27 @@ declare class CallSession {
|
|
|
205
219
|
hangup(): Promise<void>;
|
|
206
220
|
/** @internal Route a received DTMF digit to an active collector or buffer. */
|
|
207
221
|
_routeDtmf(digit: string): void;
|
|
208
|
-
/**
|
|
222
|
+
/**
|
|
223
|
+
* Collect DTMF digits from the caller.
|
|
224
|
+
*
|
|
225
|
+
* `timeout` 은 **자리 사이** 대기다(inter-digit). `<Gather>` 를 비롯한 IVR 관행과 같은
|
|
226
|
+
* 의미이고, 자리가 들어올 때마다 다시 시작한다.
|
|
227
|
+
*
|
|
228
|
+
* `maxWait` 는 그 관행이 만드는 최악을 막는 **전체 상한**이다(기본 30초). 자리마다
|
|
229
|
+
* 리셋되는 타이머만 있으면 수집 하나가 최대 `maxDigits × timeout` 동안 산다 —
|
|
230
|
+
* 11자리·5초면 **55초**이고, 그동안 모델은 이 도구에 붙들려 아무 말도 못 한다.
|
|
231
|
+
* `null` 을 주면 상한 없이 종전대로 돈다.
|
|
232
|
+
*
|
|
233
|
+
* ⚠️ **수집값을 로그에 쓰지 않는다.** 키패드로 받는 값은 카드번호·주민번호일 수 있고,
|
|
234
|
+
* 실제로 그런 구성이 있다. `secure` 와 무관하게 **자릿수만** 남긴다 — 이 옵션은 호환을
|
|
235
|
+
* 위해 남아 있을 뿐 로깅을 좌우하지 않는다.
|
|
236
|
+
*/
|
|
209
237
|
collectDtmf(options: {
|
|
210
238
|
maxDigits: number;
|
|
211
239
|
finishOnKey?: string;
|
|
212
240
|
timeout?: number;
|
|
213
241
|
secure?: boolean;
|
|
242
|
+
maxWait?: number | null;
|
|
214
243
|
}): Promise<string>;
|
|
215
244
|
/** Send a sequence of DTMF digits. */
|
|
216
245
|
sendDtmfSequence(digits: string): Promise<void>;
|
|
@@ -378,4 +407,4 @@ interface TTS {
|
|
|
378
407
|
}): AsyncGenerator<Buffer>;
|
|
379
408
|
}
|
|
380
409
|
|
|
381
|
-
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 };
|
|
410
|
+
export { BuiltinTool as B, CallSession as C, DtmfCollectorBusyError as D, 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 };
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/version.ts
|
|
2
|
-
var VERSION = "0.36.0" ;
|
|
3
|
-
|
|
4
1
|
// src/constants.ts
|
|
5
2
|
var DEFAULT_BASE_URL = "https://api.claw-ops.com";
|
|
6
3
|
var DEFAULT_MAX_RETRIES = 2;
|
|
@@ -8,6 +5,6 @@ var DEFAULT_TIMEOUT = 6e5;
|
|
|
8
5
|
var INITIAL_RETRY_DELAY = 500;
|
|
9
6
|
var MAX_RETRY_DELAY = 8e3;
|
|
10
7
|
|
|
11
|
-
export { DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, INITIAL_RETRY_DELAY, MAX_RETRY_DELAY
|
|
12
|
-
//# sourceMappingURL=chunk-
|
|
13
|
-
//# sourceMappingURL=chunk-
|
|
8
|
+
export { DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, INITIAL_RETRY_DELAY, MAX_RETRY_DELAY };
|
|
9
|
+
//# sourceMappingURL=chunk-35XJNMFU.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-35XJNMFU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts"],"names":[],"mappings":";AAAO,IAAM,gBAAA,GAAmB;AACzB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB;AACxB,IAAM,mBAAA,GAAsB;AAC5B,IAAM,eAAA,GAAkB","file":"chunk-35XJNMFU.js","sourcesContent":["export const DEFAULT_BASE_URL = 'https://api.claw-ops.com';\nexport const DEFAULT_MAX_RETRIES = 2;\nexport const DEFAULT_TIMEOUT = 600_000; // ms\nexport const INITIAL_RETRY_DELAY = 500; // ms\nexport const MAX_RETRY_DELAY = 8_000; // ms\n"]}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkNXCYAYFT_cjs = require('./chunk-NXCYAYFT.cjs');
|
|
3
4
|
var pino = require('pino');
|
|
5
|
+
var os = require('os');
|
|
4
6
|
|
|
5
7
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
8
|
|
|
7
9
|
var pino__default = /*#__PURE__*/_interopDefault(pino);
|
|
10
|
+
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
8
11
|
|
|
9
12
|
// src/agent/audio.ts
|
|
10
13
|
var _BIAS = 132;
|
|
@@ -355,6 +358,370 @@ function createAgentLogger(userLogger) {
|
|
|
355
358
|
function createPipelineLogger(parent) {
|
|
356
359
|
return parent.child({ module: "pipeline" });
|
|
357
360
|
}
|
|
361
|
+
var MAX_ERRORS = 20;
|
|
362
|
+
var MAX_ERROR_MESSAGE_LENGTH = 200;
|
|
363
|
+
function getSdkInfo() {
|
|
364
|
+
return {
|
|
365
|
+
name: "clawops-node",
|
|
366
|
+
version: chunkNXCYAYFT_cjs.VERSION,
|
|
367
|
+
runtime: `node/${process.versions.node}`,
|
|
368
|
+
os: `${process.platform}/${os__default.default.arch()}`
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function createCallMetrics() {
|
|
372
|
+
return {
|
|
373
|
+
firstResponseMs: null,
|
|
374
|
+
turnCount: 0,
|
|
375
|
+
toolCallCount: 0,
|
|
376
|
+
toolErrorCount: 0,
|
|
377
|
+
bargeInCount: 0,
|
|
378
|
+
endReason: null,
|
|
379
|
+
errors: []
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function addMetricError(metrics, err) {
|
|
383
|
+
metrics.toolErrorCount++;
|
|
384
|
+
if (metrics.errors.length < MAX_ERRORS) {
|
|
385
|
+
metrics.errors.push({
|
|
386
|
+
type: err.name || "Error",
|
|
387
|
+
message: (err.message || "").slice(0, MAX_ERROR_MESSAGE_LENGTH)
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/agent/session.ts
|
|
393
|
+
var DtmfCollectorBusyError = class extends Error {
|
|
394
|
+
constructor(message = "\uC774\uBBF8 DTMF \uC218\uC9D1 \uC911\uC785\uB2C8\uB2E4") {
|
|
395
|
+
super(message);
|
|
396
|
+
this.name = "DtmfCollectorBusyError";
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
var CallSession = class {
|
|
400
|
+
callId;
|
|
401
|
+
fromNumber;
|
|
402
|
+
toNumber;
|
|
403
|
+
accountId;
|
|
404
|
+
direction;
|
|
405
|
+
startTime;
|
|
406
|
+
metadata;
|
|
407
|
+
_status;
|
|
408
|
+
_endedStatus = null;
|
|
409
|
+
_endedDuration = null;
|
|
410
|
+
_sendAudioFn = null;
|
|
411
|
+
_clearAudioFn = null;
|
|
412
|
+
_hangupFn = null;
|
|
413
|
+
/** @internal */
|
|
414
|
+
_sendDtmfFn = null;
|
|
415
|
+
/** @internal */
|
|
416
|
+
_transferFn = null;
|
|
417
|
+
/** @internal */
|
|
418
|
+
_isTransportConnected = null;
|
|
419
|
+
/**
|
|
420
|
+
* @internal Media WS playout 마커 훅. LiveKit(`ClawOpsAudioOutput`)이 재생 완료
|
|
421
|
+
* 판정(mark echo)과 barge-in 절단 위치 계산에 쓴다. 다른 세션 타입은 읽지 않는다.
|
|
422
|
+
* prewarm 중(BufferingCall)에는 바인딩되지 않아 null 이다.
|
|
423
|
+
*/
|
|
424
|
+
/** @internal */
|
|
425
|
+
_sendMark = null;
|
|
426
|
+
/** @internal */
|
|
427
|
+
_waitForMark = null;
|
|
428
|
+
/** @internal */
|
|
429
|
+
_flushTransport = null;
|
|
430
|
+
_dtmfCollectorActive = false;
|
|
431
|
+
_dtmfResolvers = [];
|
|
432
|
+
_dtmfBuffer = [];
|
|
433
|
+
_log = NOOP_LOGGER;
|
|
434
|
+
_handlers = /* @__PURE__ */ new Map();
|
|
435
|
+
_endedPromise;
|
|
436
|
+
_resolveEnded;
|
|
437
|
+
_metrics = createCallMetrics();
|
|
438
|
+
_firstResponseSent = false;
|
|
439
|
+
constructor(options) {
|
|
440
|
+
this.callId = options.callId;
|
|
441
|
+
this.fromNumber = options.fromNumber;
|
|
442
|
+
this.toNumber = options.toNumber;
|
|
443
|
+
this.accountId = options.accountId;
|
|
444
|
+
this.direction = options.direction;
|
|
445
|
+
this.metadata = options.metadata ?? {};
|
|
446
|
+
this._status = "ringing";
|
|
447
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
448
|
+
this._endedPromise = new Promise((resolve) => {
|
|
449
|
+
this._resolveEnded = resolve;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
setLogger(logger) {
|
|
453
|
+
this._log = logger;
|
|
454
|
+
}
|
|
455
|
+
get status() {
|
|
456
|
+
return this._status;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 서버가 통보한 최종 종료 상태. 통화가 끝나기 전에는 null.
|
|
460
|
+
*
|
|
461
|
+
* `completed`(성사) / `no-answer`(벨은 울렸으나 무응답) / `busy`(통화중) /
|
|
462
|
+
* `rejected`(수신 거절) / `canceled`(응답 전 발신 측 취소) / `failed`(시스템·망 오류).
|
|
463
|
+
* `status` 는 SDK 내부 수명주기(ringing→active→ended)이므로 통화 성사 여부는
|
|
464
|
+
* 이 값이나 `call_failed` 이벤트로 판단한다.
|
|
465
|
+
*/
|
|
466
|
+
get endedStatus() {
|
|
467
|
+
return this._endedStatus;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* 서버가 확정한 통화 시간(초). 통화가 끝나기 전에는 `null`.
|
|
471
|
+
*
|
|
472
|
+
* ⚠️ `duration` 과 다르다 — 그쪽은 SDK 가 로컬 시계로 재는 **경과 시간**이라 통화 중에도
|
|
473
|
+
* 읽히고, 세션이 붙기 전후의 오차를 포함한다. 기록·정산에 쓸 값은 이쪽이다.
|
|
474
|
+
*/
|
|
475
|
+
get endedDuration() {
|
|
476
|
+
return this._endedDuration;
|
|
477
|
+
}
|
|
478
|
+
get duration() {
|
|
479
|
+
return (Date.now() - this.startTime.getTime()) / 1e3;
|
|
480
|
+
}
|
|
481
|
+
/** @internal 서버가 통보한 통화 시간을 확정한다. */
|
|
482
|
+
_setEndedDuration(seconds) {
|
|
483
|
+
this._endedDuration = seconds;
|
|
484
|
+
}
|
|
485
|
+
get metrics() {
|
|
486
|
+
return this._metrics;
|
|
487
|
+
}
|
|
488
|
+
recordFirstResponse() {
|
|
489
|
+
if (!this._firstResponseSent) {
|
|
490
|
+
this._firstResponseSent = true;
|
|
491
|
+
this._metrics.firstResponseMs = Date.now() - this.startTime.getTime();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
recordTurn() {
|
|
495
|
+
this._metrics.turnCount++;
|
|
496
|
+
}
|
|
497
|
+
recordToolCall() {
|
|
498
|
+
this._metrics.toolCallCount++;
|
|
499
|
+
}
|
|
500
|
+
recordToolError(err) {
|
|
501
|
+
addMetricError(this._metrics, err);
|
|
502
|
+
}
|
|
503
|
+
recordBargeIn() {
|
|
504
|
+
this._metrics.bargeInCount++;
|
|
505
|
+
}
|
|
506
|
+
recordEndReason(reason) {
|
|
507
|
+
this._metrics.endReason = reason;
|
|
508
|
+
}
|
|
509
|
+
/** Bind transport functions (called internally by the agent). */
|
|
510
|
+
_bindTransport(send, clear, hangup, sendDtmf, isConnected) {
|
|
511
|
+
this._sendAudioFn = send;
|
|
512
|
+
this._clearAudioFn = clear;
|
|
513
|
+
this._hangupFn = hangup;
|
|
514
|
+
if (sendDtmf) this._sendDtmfFn = sendDtmf;
|
|
515
|
+
if (isConnected) this._isTransportConnected = isConnected;
|
|
516
|
+
this._status = "active";
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Send G.711 μ-law audio (8kHz, mono) to the caller.
|
|
520
|
+
*
|
|
521
|
+
* NOT PCM16. The transport applies μ-law gain and forwards the bytes as-is,
|
|
522
|
+
* so PCM16 input is reinterpreted as μ-law and plays as noise. Convert first.
|
|
523
|
+
*/
|
|
524
|
+
sendAudio(audio) {
|
|
525
|
+
if (this._sendAudioFn) {
|
|
526
|
+
this._sendAudioFn(audio);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/** Clear any queued outbound audio. */
|
|
530
|
+
clearAudio() {
|
|
531
|
+
if (this._clearAudioFn) {
|
|
532
|
+
this._clearAudioFn();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/** Hang up the call, waiting for pending audio to finish. */
|
|
536
|
+
async hangup() {
|
|
537
|
+
if (this._hangupFn) {
|
|
538
|
+
await this._hangupFn();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** @internal Route a received DTMF digit to an active collector or buffer. */
|
|
542
|
+
_routeDtmf(digit) {
|
|
543
|
+
if (this._dtmfCollectorActive && this._dtmfResolvers.length > 0) {
|
|
544
|
+
const resolve = this._dtmfResolvers.shift();
|
|
545
|
+
resolve(digit);
|
|
546
|
+
} else {
|
|
547
|
+
this._dtmfBuffer.push(digit);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Collect DTMF digits from the caller.
|
|
552
|
+
*
|
|
553
|
+
* `timeout` 은 **자리 사이** 대기다(inter-digit). `<Gather>` 를 비롯한 IVR 관행과 같은
|
|
554
|
+
* 의미이고, 자리가 들어올 때마다 다시 시작한다.
|
|
555
|
+
*
|
|
556
|
+
* `maxWait` 는 그 관행이 만드는 최악을 막는 **전체 상한**이다(기본 30초). 자리마다
|
|
557
|
+
* 리셋되는 타이머만 있으면 수집 하나가 최대 `maxDigits × timeout` 동안 산다 —
|
|
558
|
+
* 11자리·5초면 **55초**이고, 그동안 모델은 이 도구에 붙들려 아무 말도 못 한다.
|
|
559
|
+
* `null` 을 주면 상한 없이 종전대로 돈다.
|
|
560
|
+
*
|
|
561
|
+
* ⚠️ **수집값을 로그에 쓰지 않는다.** 키패드로 받는 값은 카드번호·주민번호일 수 있고,
|
|
562
|
+
* 실제로 그런 구성이 있다. `secure` 와 무관하게 **자릿수만** 남긴다 — 이 옵션은 호환을
|
|
563
|
+
* 위해 남아 있을 뿐 로깅을 좌우하지 않는다.
|
|
564
|
+
*/
|
|
565
|
+
async collectDtmf(options) {
|
|
566
|
+
if (this._dtmfCollectorActive) {
|
|
567
|
+
throw new DtmfCollectorBusyError();
|
|
568
|
+
}
|
|
569
|
+
const { maxDigits, finishOnKey = "#", timeout = 5, maxWait = 30 } = options;
|
|
570
|
+
this._dtmfCollectorActive = true;
|
|
571
|
+
const collected = [];
|
|
572
|
+
const deadline = maxWait === null ? null : Date.now() + maxWait * 1e3;
|
|
573
|
+
try {
|
|
574
|
+
while (collected.length < maxDigits) {
|
|
575
|
+
if (this._dtmfBuffer.length > 0) {
|
|
576
|
+
const digit2 = this._dtmfBuffer.shift();
|
|
577
|
+
if (digit2 === finishOnKey) break;
|
|
578
|
+
collected.push(digit2);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
let waitMs = timeout * 1e3;
|
|
582
|
+
if (deadline !== null) {
|
|
583
|
+
waitMs = Math.min(waitMs, deadline - Date.now());
|
|
584
|
+
if (waitMs <= 0) break;
|
|
585
|
+
}
|
|
586
|
+
let timer;
|
|
587
|
+
const digit = await Promise.race([
|
|
588
|
+
new Promise((resolve) => {
|
|
589
|
+
this._dtmfResolvers.push(resolve);
|
|
590
|
+
}),
|
|
591
|
+
new Promise((resolve) => {
|
|
592
|
+
timer = setTimeout(() => resolve(null), waitMs);
|
|
593
|
+
})
|
|
594
|
+
]).finally(() => {
|
|
595
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
596
|
+
});
|
|
597
|
+
if (digit === null) break;
|
|
598
|
+
if (digit === finishOnKey) break;
|
|
599
|
+
collected.push(digit);
|
|
600
|
+
}
|
|
601
|
+
} finally {
|
|
602
|
+
this._dtmfCollectorActive = false;
|
|
603
|
+
this._dtmfResolvers = [];
|
|
604
|
+
this._dtmfBuffer = [];
|
|
605
|
+
}
|
|
606
|
+
const result = collected.join("");
|
|
607
|
+
this._log.info("DTMF collected: %d digits", result.length);
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
/** Send a sequence of DTMF digits. */
|
|
611
|
+
async sendDtmfSequence(digits) {
|
|
612
|
+
if (!this._sendDtmfFn) {
|
|
613
|
+
throw new Error("DTMF \uC804\uC1A1 \uD568\uC218\uAC00 \uBC14\uC778\uB529\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
614
|
+
}
|
|
615
|
+
for (const ch of digits) {
|
|
616
|
+
if (this._isTransportConnected && !this._isTransportConnected()) {
|
|
617
|
+
throw new Error("DTMF \uC804\uC1A1 \uC911 \uC5F0\uACB0\uC774 \uB04A\uC5B4\uC84C\uC2B5\uB2C8\uB2E4");
|
|
618
|
+
}
|
|
619
|
+
if (ch === "w") {
|
|
620
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
621
|
+
} else if (ch === "W") {
|
|
622
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
623
|
+
} else if ("0123456789*#".includes(ch)) {
|
|
624
|
+
if (this._sendDtmfFn) {
|
|
625
|
+
await this._sendDtmfFn(ch);
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 DTMF \uBB38\uC790: ${ch}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Transfer the call to a phone number or SIP endpoint.
|
|
634
|
+
*
|
|
635
|
+
* destinationType='pstn' (default): `to` is a phone number dialed via carrier.
|
|
636
|
+
* destinationType='sip': `to` is a SIP URI (e.g. `sip:user@host`) connected
|
|
637
|
+
* directly to a SIP endpoint without going through the PSTN carrier. Requires the
|
|
638
|
+
* account to have an active `sip_trunk` add-on; otherwise the transfer fails and
|
|
639
|
+
* the call continues with the AI (result `{ status: 'failed', ... }`).
|
|
640
|
+
*
|
|
641
|
+
* 전환받는 쪽에 표시되는 발신번호는 기본이 **계정 보유번호**(인바운드면 착신 070)다.
|
|
642
|
+
*
|
|
643
|
+
* `callerIdMode: 'original'` 은 인바운드 통화의 **원 발신자 번호를 승계하려는 선호**다.
|
|
644
|
+
* 승계할 수 없는 통화(KCT 직결 인입이 아니거나 국내 번호로 정규화되지 않는 발신번호)면
|
|
645
|
+
* 조용히 계정 보유번호로 내려앉고 **전환은 그대로 성사된다**.
|
|
646
|
+
*
|
|
647
|
+
* `callerId` 는 번호를 직접 주는 **지시**라 성격이 다르다. 허용 범위(계정 보유번호 또는
|
|
648
|
+
* KCT 직결 인입의 원 발신자)를 벗어나면 전환 자체가 실패한다. 둘 다 주면 `callerId` 가
|
|
649
|
+
* 이기고 `callerIdMode` 는 무시된다 — 우선순위 판단은 서버가 한다.
|
|
650
|
+
*/
|
|
651
|
+
async transfer(to, options) {
|
|
652
|
+
if (!this._transferFn) {
|
|
653
|
+
throw new Error("transfer not available");
|
|
654
|
+
}
|
|
655
|
+
const callerIdMode = options?.callerIdMode;
|
|
656
|
+
if (callerIdMode !== void 0 && callerIdMode !== "account" && callerIdMode !== "original") {
|
|
657
|
+
throw new Error(
|
|
658
|
+
`callerIdMode must be 'account' or 'original', got ${JSON.stringify(callerIdMode)}`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
return this._transferFn({
|
|
662
|
+
to,
|
|
663
|
+
destinationType: options?.destinationType ?? "pstn",
|
|
664
|
+
mode: options?.mode ?? "blind",
|
|
665
|
+
afterTransfer: options?.afterTransfer ?? "terminate",
|
|
666
|
+
holdMedia: options?.holdMedia ?? "ringback",
|
|
667
|
+
whisper: options?.whisper ?? null,
|
|
668
|
+
context: options?.context ?? null,
|
|
669
|
+
callerId: options?.callerId ?? null,
|
|
670
|
+
timeout: options?.timeout ?? 30,
|
|
671
|
+
// 안 주면 키를 붙이지 않는다 — 구 서버와 기존 동작을 그대로 둔다(additive).
|
|
672
|
+
...callerIdMode !== void 0 ? { callerIdMode } : {}
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
/** Register an event handler. */
|
|
676
|
+
on(event, handler) {
|
|
677
|
+
let list = this._handlers.get(event);
|
|
678
|
+
if (!list) {
|
|
679
|
+
list = [];
|
|
680
|
+
this._handlers.set(event, list);
|
|
681
|
+
}
|
|
682
|
+
list.push(handler);
|
|
683
|
+
}
|
|
684
|
+
/** Wait for the call to end. */
|
|
685
|
+
async wait() {
|
|
686
|
+
return this._endedPromise;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Mark the session as ended (called internally).
|
|
690
|
+
*
|
|
691
|
+
* @param status 서버가 통보한 최종 상태(completed/no-answer/busy/rejected/canceled/
|
|
692
|
+
* failed). 주어지면 `endedStatus` 로 확정한다 — 상대가 받지 않은 통화를 성사된 통화와
|
|
693
|
+
* 구분하기 위함이다. 생략하면 미디어 세션 정리 경로에서의 호출이므로, 아직 종료 전일
|
|
694
|
+
* 때만 'completed' 로 채우고 이미 확정된 서버 상태는 덮어쓰지 않는다.
|
|
695
|
+
*/
|
|
696
|
+
_markEnded(status) {
|
|
697
|
+
if (status !== void 0) {
|
|
698
|
+
this._endedStatus = status;
|
|
699
|
+
} else if (this._status !== "ended") {
|
|
700
|
+
this._endedStatus = "completed";
|
|
701
|
+
}
|
|
702
|
+
this._status = "ended";
|
|
703
|
+
this._resolveEnded();
|
|
704
|
+
}
|
|
705
|
+
/** Emit an event to registered handlers. Matches Python SDK: _emit(event, ...args) */
|
|
706
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
707
|
+
_emit(event, ...args) {
|
|
708
|
+
const handlers = this._handlers.get(event);
|
|
709
|
+
if (handlers) {
|
|
710
|
+
for (const handler of handlers) {
|
|
711
|
+
try {
|
|
712
|
+
const result = handler(this, ...args);
|
|
713
|
+
if (result && typeof result.catch === "function") {
|
|
714
|
+
result.catch((err) => {
|
|
715
|
+
this._log.error({ err }, "CallSession handler error: %s", event);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
} catch (err) {
|
|
719
|
+
this._log.error({ err }, "CallSession handler error: %s", event);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
};
|
|
358
725
|
|
|
359
726
|
// src/agent/builtin-tool.ts
|
|
360
727
|
var BuiltinTool = /* @__PURE__ */ ((BuiltinTool2) => {
|
|
@@ -504,6 +871,9 @@ async function executeBuiltinTool(funcName, args, call) {
|
|
|
504
871
|
});
|
|
505
872
|
return result || "(\uD0C0\uC784\uC544\uC6C3 - \uC785\uB825 \uC5C6\uC74C)";
|
|
506
873
|
} catch (e) {
|
|
874
|
+
if (e instanceof DtmfCollectorBusyError) {
|
|
875
|
+
return "(\uC774\uBBF8 \uC785\uB825\uC744 \uBC1B\uB294 \uC911\uC785\uB2C8\uB2E4. \uACB0\uACFC\uB97C \uAE30\uB2E4\uB9AC\uC138\uC694.)";
|
|
876
|
+
}
|
|
507
877
|
return `Error: ${e}`;
|
|
508
878
|
}
|
|
509
879
|
}
|
|
@@ -622,7 +992,9 @@ exports.BUILTIN_TOOL_NAMES = BUILTIN_TOOL_NAMES;
|
|
|
622
992
|
exports.BufferingCall = BufferingCall;
|
|
623
993
|
exports.BuiltinTool = BuiltinTool;
|
|
624
994
|
exports.CALL_NOT_READY_RESULT = CALL_NOT_READY_RESULT;
|
|
995
|
+
exports.CallSession = CallSession;
|
|
625
996
|
exports.DECODE_TABLE = DECODE_TABLE;
|
|
997
|
+
exports.DtmfCollectorBusyError = DtmfCollectorBusyError;
|
|
626
998
|
exports.NOOP_LOGGER = NOOP_LOGGER;
|
|
627
999
|
exports.applyUlawGain = applyUlawGain;
|
|
628
1000
|
exports.attachBuffered = attachBuffered;
|
|
@@ -630,10 +1002,11 @@ exports.createAgentLogger = createAgentLogger;
|
|
|
630
1002
|
exports.createPipelineLogger = createPipelineLogger;
|
|
631
1003
|
exports.executeBuiltinTool = executeBuiltinTool;
|
|
632
1004
|
exports.getBuiltinToolSchemas = getBuiltinToolSchemas;
|
|
1005
|
+
exports.getSdkInfo = getSdkInfo;
|
|
633
1006
|
exports.isBuiltinTool = isBuiltinTool;
|
|
634
1007
|
exports.pcm16ToUlaw = pcm16ToUlaw;
|
|
635
1008
|
exports.resamplePcm16 = resamplePcm16;
|
|
636
1009
|
exports.resolveBuiltinTools = resolveBuiltinTools;
|
|
637
1010
|
exports.ulawToPcm16 = ulawToPcm16;
|
|
638
|
-
//# sourceMappingURL=chunk-
|
|
639
|
-
//# sourceMappingURL=chunk-
|
|
1011
|
+
//# sourceMappingURL=chunk-ASQU6NA2.cjs.map
|
|
1012
|
+
//# sourceMappingURL=chunk-ASQU6NA2.cjs.map
|