@guava-ai/guava-sdk 0.18.0 → 0.20.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/examples/example.test.d.ts +5 -0
- package/dist/examples/example.test.js +46 -0
- package/dist/examples/example.test.js.map +1 -0
- package/dist/examples/help-desk.d.ts +3 -1
- package/dist/examples/help-desk.js +25 -9
- package/dist/examples/help-desk.js.map +1 -1
- package/dist/examples/property-insurance.js +4 -1
- package/dist/examples/property-insurance.js.map +1 -1
- package/dist/examples/restaurant-waitlist.js +4 -1
- package/dist/examples/restaurant-waitlist.js.map +1 -1
- package/dist/examples/scheduling-outbound.js +6 -0
- package/dist/examples/scheduling-outbound.js.map +1 -1
- package/dist/src/action-item.d.ts +4 -4
- package/dist/src/agent.d.ts +85 -18
- package/dist/src/agent.js +404 -129
- package/dist/src/agent.js.map +1 -1
- package/dist/src/auth.d.ts +27 -0
- package/dist/src/auth.js +127 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/call.d.ts +1 -1
- package/dist/src/call.js +2 -2
- package/dist/src/call.js.map +1 -1
- package/dist/src/client.d.ts +38 -12
- package/dist/src/client.js +88 -16
- package/dist/src/client.js.map +1 -1
- package/dist/src/commands.d.ts +3 -3
- package/dist/src/events.d.ts +87 -0
- package/dist/src/events.js +25 -6
- package/dist/src/events.js.map +1 -1
- package/dist/src/helpers/llm.d.ts +2 -0
- package/dist/src/helpers/llm.js +17 -0
- package/dist/src/helpers/llm.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +5 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging.js +16 -11
- package/dist/src/logging.js.map +1 -1
- package/dist/src/sms.d.ts +19 -0
- package/dist/src/sms.js +52 -0
- package/dist/src/sms.js.map +1 -0
- package/dist/src/socket/call-info.d.ts +35 -0
- package/dist/src/socket/call-info.js +59 -0
- package/dist/src/socket/call-info.js.map +1 -0
- package/dist/src/socket/client.d.ts +51 -0
- package/dist/src/socket/client.js +455 -0
- package/dist/src/socket/client.js.map +1 -0
- package/dist/src/socket/listen-inbound.d.ts +83 -0
- package/dist/src/socket/listen-inbound.js +82 -0
- package/dist/src/socket/listen-inbound.js.map +1 -0
- package/dist/src/socket/protocol.d.ts +127 -0
- package/dist/src/socket/protocol.js +69 -0
- package/dist/src/socket/protocol.js.map +1 -0
- package/dist/src/socket/utils.d.ts +8 -0
- package/dist/src/socket/utils.js +26 -0
- package/dist/src/socket/utils.js.map +1 -0
- package/dist/src/telemetry.d.ts +3 -3
- package/dist/src/telemetry.js +9 -7
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/testing/chat.d.ts +2 -0
- package/dist/src/testing/chat.js +181 -0
- package/dist/src/testing/chat.js.map +1 -0
- package/dist/src/testing/mocks.d.ts +6 -0
- package/dist/src/testing/mocks.js +14 -0
- package/dist/src/testing/mocks.js.map +1 -0
- package/dist/src/testing/protocol.d.ts +46 -0
- package/dist/src/testing/protocol.js +61 -0
- package/dist/src/testing/protocol.js.map +1 -0
- package/dist/src/testing/session.d.ts +26 -0
- package/dist/src/testing/session.js +219 -0
- package/dist/src/testing/session.js.map +1 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.js +19 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/examples/example.test.ts +58 -0
- package/examples/help-desk.ts +14 -3
- package/examples/property-insurance.ts +3 -1
- package/examples/restaurant-waitlist.ts +3 -1
- package/examples/scheduling-outbound.ts +7 -0
- package/package.json +10 -2
- package/src/agent.ts +386 -166
- package/src/auth.ts +109 -0
- package/src/call.ts +3 -3
- package/src/client.ts +119 -18
- package/src/events.ts +52 -10
- package/src/helpers/llm.ts +20 -0
- package/src/index.ts +4 -0
- package/src/logging.ts +21 -13
- package/src/sms.ts +17 -0
- package/src/socket/call-info.ts +30 -0
- package/src/socket/client.ts +433 -0
- package/src/socket/listen-inbound.ts +62 -0
- package/src/socket/protocol.ts +89 -0
- package/src/socket/utils.ts +25 -0
- package/src/telemetry.ts +11 -8
- package/src/testing/chat.ts +196 -0
- package/src/testing/mocks.ts +12 -0
- package/src/testing/protocol.ts +40 -0
- package/src/testing/session.ts +218 -0
- package/src/utils.ts +19 -1
- package/src/version.ts +1 -1
package/dist/src/agent.js
CHANGED
|
@@ -66,6 +66,58 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
66
66
|
return result;
|
|
67
67
|
};
|
|
68
68
|
})();
|
|
69
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
70
|
+
if (value !== null && value !== void 0) {
|
|
71
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
72
|
+
var dispose, inner;
|
|
73
|
+
if (async) {
|
|
74
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
75
|
+
dispose = value[Symbol.asyncDispose];
|
|
76
|
+
}
|
|
77
|
+
if (dispose === void 0) {
|
|
78
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
79
|
+
dispose = value[Symbol.dispose];
|
|
80
|
+
if (async) inner = dispose;
|
|
81
|
+
}
|
|
82
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
83
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
84
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
85
|
+
}
|
|
86
|
+
else if (async) {
|
|
87
|
+
env.stack.push({ async: true });
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
};
|
|
91
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
92
|
+
return function (env) {
|
|
93
|
+
function fail(e) {
|
|
94
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
95
|
+
env.hasError = true;
|
|
96
|
+
}
|
|
97
|
+
var r, s = 0;
|
|
98
|
+
function next() {
|
|
99
|
+
while (r = env.stack.pop()) {
|
|
100
|
+
try {
|
|
101
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
102
|
+
if (r.dispose) {
|
|
103
|
+
var result = r.dispose.call(r.value);
|
|
104
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
105
|
+
}
|
|
106
|
+
else s |= 1;
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
fail(e);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
113
|
+
if (env.hasError) throw env.error;
|
|
114
|
+
}
|
|
115
|
+
return next();
|
|
116
|
+
};
|
|
117
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
118
|
+
var e = new Error(message);
|
|
119
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
120
|
+
});
|
|
69
121
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
70
122
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
71
123
|
};
|
|
@@ -76,16 +128,15 @@ const index_ts_1 = require("./index.js");
|
|
|
76
128
|
const logging_ts_1 = require("./logging.js");
|
|
77
129
|
const utils_ts_1 = require("./utils.js");
|
|
78
130
|
const webrtc_helper_ts_1 = require("./webrtc-helper.js");
|
|
79
|
-
const z = __importStar(require("zod"));
|
|
80
131
|
const commands_ts_1 = require("./commands.js");
|
|
81
132
|
const events_ts_1 = require("./events.js");
|
|
82
133
|
const telemetry_ts_1 = require("./telemetry.js");
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
const client_ts_1 = require("./socket/client.js");
|
|
135
|
+
const ListenInbound = __importStar(require("./socket/listen-inbound.js"));
|
|
136
|
+
const session_ts_1 = require("./testing/session.js");
|
|
137
|
+
const protocol_ts_1 = require("./testing/protocol.js");
|
|
138
|
+
const chat_ts_1 = require("./testing/chat.js");
|
|
139
|
+
const llm_ts_1 = require("./helpers/llm.js");
|
|
89
140
|
let Agent = (() => {
|
|
90
141
|
let _classDecorators = [telemetry_ts_1.telemetryClient.trackClass()];
|
|
91
142
|
let _classDescriptor;
|
|
@@ -119,6 +170,7 @@ let Agent = (() => {
|
|
|
119
170
|
_onActionGeneric;
|
|
120
171
|
_onActionHandlers = {};
|
|
121
172
|
_onSessionEnd;
|
|
173
|
+
_onDtmf;
|
|
122
174
|
constructor(args) {
|
|
123
175
|
this._name = args?.name;
|
|
124
176
|
this._organization = args?.organization;
|
|
@@ -175,16 +227,71 @@ let Agent = (() => {
|
|
|
175
227
|
onSessionEnd(callback) {
|
|
176
228
|
this._onSessionEnd = callback;
|
|
177
229
|
}
|
|
230
|
+
onDtmf(callback) {
|
|
231
|
+
this._onDtmf = callback;
|
|
232
|
+
}
|
|
233
|
+
get handlers() {
|
|
234
|
+
return {
|
|
235
|
+
onCallReceived: (callInfo) => this._onCallReceived(callInfo),
|
|
236
|
+
onCallStart: (call) => {
|
|
237
|
+
if (!this._onCallStart)
|
|
238
|
+
throw new Error("No onCallStart handler registered.");
|
|
239
|
+
return this._onCallStart(call);
|
|
240
|
+
},
|
|
241
|
+
onCallerSpeech: (call, event) => {
|
|
242
|
+
if (!this._onCallerSpeech)
|
|
243
|
+
throw new Error("No onCallerSpeech handler registered.");
|
|
244
|
+
return this._onCallerSpeech(call, event);
|
|
245
|
+
},
|
|
246
|
+
onAgentSpeech: (call, event) => {
|
|
247
|
+
if (!this._onAgentSpeech)
|
|
248
|
+
throw new Error("No onAgentSpeech handler registered.");
|
|
249
|
+
return this._onAgentSpeech(call, event);
|
|
250
|
+
},
|
|
251
|
+
onQuestion: (call, question) => {
|
|
252
|
+
if (!this._onQuestion)
|
|
253
|
+
throw new Error("No onQuestion handler registered.");
|
|
254
|
+
return this._onQuestion(call, question);
|
|
255
|
+
},
|
|
256
|
+
onTaskComplete: (taskId, call) => {
|
|
257
|
+
if (this._onTaskCompleteGeneric)
|
|
258
|
+
return this._onTaskCompleteGeneric(call, taskId);
|
|
259
|
+
if (taskId in this._onTaskCompleteHandlers)
|
|
260
|
+
return this._onTaskCompleteHandlers[taskId](call);
|
|
261
|
+
throw new Error(`No onTaskComplete handler registered for task '${taskId}'.`);
|
|
262
|
+
},
|
|
263
|
+
onSearchQuery: (fieldKey, call, query) => {
|
|
264
|
+
if (!(fieldKey in this._searchQueryHandlers))
|
|
265
|
+
throw new Error(`No onSearchQuery handler registered for field '${fieldKey}'.`);
|
|
266
|
+
return this._searchQueryHandlers[fieldKey](call, query);
|
|
267
|
+
},
|
|
268
|
+
onActionRequest: (call, intentSummary) => {
|
|
269
|
+
if (!this._onActionRequested)
|
|
270
|
+
throw new Error("No onActionRequest handler registered.");
|
|
271
|
+
return this._onActionRequested(call, intentSummary);
|
|
272
|
+
},
|
|
273
|
+
onAction: (actionKey, call) => {
|
|
274
|
+
if (this._onActionGeneric)
|
|
275
|
+
return this._onActionGeneric(call, actionKey);
|
|
276
|
+
if (actionKey in this._onActionHandlers)
|
|
277
|
+
return this._onActionHandlers[actionKey](call);
|
|
278
|
+
throw new Error(`No onAction handler registered for action '${actionKey}'.`);
|
|
279
|
+
},
|
|
280
|
+
onSessionEnd: (call, event) => {
|
|
281
|
+
if (!this._onSessionEnd)
|
|
282
|
+
throw new Error("No onSessionEnd handler registered.");
|
|
283
|
+
return this._onSessionEnd(call, event);
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
178
287
|
onReachPerson(callback) {
|
|
179
288
|
this.onTaskComplete("reach_person", async (call) => {
|
|
180
289
|
const availability = (await call.getField("contact_availability"));
|
|
181
290
|
await callback(call, availability);
|
|
182
291
|
});
|
|
183
292
|
}
|
|
184
|
-
listenPhone(phoneNumber) {
|
|
185
|
-
return this._listenInbound({
|
|
186
|
-
agent_number: phoneNumber,
|
|
187
|
-
});
|
|
293
|
+
async listenPhone(phoneNumber) {
|
|
294
|
+
return this._listenInbound({ agent_number: phoneNumber });
|
|
188
295
|
}
|
|
189
296
|
async listenWebrtc(webrtcCode) {
|
|
190
297
|
if (!webrtcCode) {
|
|
@@ -195,10 +302,12 @@ let Agent = (() => {
|
|
|
195
302
|
}
|
|
196
303
|
async callLocal() {
|
|
197
304
|
const webrtcCode = await this._client.createWebrtcAgent(300);
|
|
198
|
-
this._listenInbound({ webrtc_code: webrtcCode })
|
|
305
|
+
this._listenInbound({ webrtc_code: webrtcCode }).catch((err) => {
|
|
306
|
+
this._logger.error("Listen loop error: %s", err);
|
|
307
|
+
});
|
|
199
308
|
await (0, webrtc_helper_ts_1.runWebrtcHelper)(webrtcCode, (0, utils_ts_1.getBaseUrl)());
|
|
200
309
|
}
|
|
201
|
-
async _dispatchEvent(call, event) {
|
|
310
|
+
async _dispatchEvent(call, event, testSession) {
|
|
202
311
|
if (event.event_type === "caller-speech") {
|
|
203
312
|
if (this._onCallerSpeech !== undefined) {
|
|
204
313
|
await this._onCallerSpeech(call, event);
|
|
@@ -209,19 +318,6 @@ let Agent = (() => {
|
|
|
209
318
|
await this._onAgentSpeech(call, event);
|
|
210
319
|
}
|
|
211
320
|
}
|
|
212
|
-
else if (event.event_type === "inbound-call") {
|
|
213
|
-
this._logger.info(`Received inbound call from ${event.caller_number ?? "unknown"}`);
|
|
214
|
-
const action = await this._onCallReceived({
|
|
215
|
-
caller_number: event.caller_number,
|
|
216
|
-
agent_number: event.agent_number,
|
|
217
|
-
});
|
|
218
|
-
if (action.action === "accept") {
|
|
219
|
-
call.sendCommand(commands_ts_1.AcceptInboundCallCommand, { command_type: "accept-inbound" });
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
call.sendCommand(commands_ts_1.RejectInboundCallCommand, { command_type: "reject-inbound" });
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
321
|
else if (event.event_type === "task-done") {
|
|
226
322
|
this._logger.info(`Task ${event.task_id} completed.`);
|
|
227
323
|
if (this._onTaskCompleteGeneric !== undefined) {
|
|
@@ -245,7 +341,7 @@ let Agent = (() => {
|
|
|
245
341
|
this._logger.error(`Error occurred while answering question: ${err}`);
|
|
246
342
|
answer = "An error occurred and the question could not be answered.";
|
|
247
343
|
}
|
|
248
|
-
call.sendCommand(commands_ts_1.AnswerQuestionCommand, {
|
|
344
|
+
await call.sendCommand(commands_ts_1.AnswerQuestionCommand, {
|
|
249
345
|
command_type: "answer-question",
|
|
250
346
|
question_id: event.question_id,
|
|
251
347
|
answer,
|
|
@@ -253,7 +349,7 @@ let Agent = (() => {
|
|
|
253
349
|
}
|
|
254
350
|
else {
|
|
255
351
|
this._logger.warn(`Received question but no onQuestion handler is registered: ${event.question}`);
|
|
256
|
-
call.sendCommand(commands_ts_1.AnswerQuestionCommand, {
|
|
352
|
+
await call.sendCommand(commands_ts_1.AnswerQuestionCommand, {
|
|
257
353
|
command_type: "answer-question",
|
|
258
354
|
question_id: event.question_id,
|
|
259
355
|
answer: "I don't have an answer to that question.",
|
|
@@ -261,7 +357,6 @@ let Agent = (() => {
|
|
|
261
357
|
}
|
|
262
358
|
}
|
|
263
359
|
else if (event.event_type === "action-item-done") {
|
|
264
|
-
this._logger.info(`Action item '${event.key}' completed.`);
|
|
265
360
|
call._fieldValues[event.key] = event.payload;
|
|
266
361
|
}
|
|
267
362
|
else if (event.event_type === "choice-query") {
|
|
@@ -272,7 +367,7 @@ let Agent = (() => {
|
|
|
272
367
|
}
|
|
273
368
|
else {
|
|
274
369
|
const [matchedChoices, otherChoices] = await handler(call, event.query);
|
|
275
|
-
call.sendCommand(commands_ts_1.ChoiceResultCommand, {
|
|
370
|
+
await call.sendCommand(commands_ts_1.ChoiceResultCommand, {
|
|
276
371
|
command_type: "choice-query-result",
|
|
277
372
|
field_key: event.field_key,
|
|
278
373
|
query_id: event.query_id,
|
|
@@ -287,7 +382,7 @@ let Agent = (() => {
|
|
|
287
382
|
if (this._onActionRequested !== undefined) {
|
|
288
383
|
suggestion = await this._onActionRequested(call, event.intent_summary);
|
|
289
384
|
}
|
|
290
|
-
call.sendCommand(commands_ts_1.ActionSuggestionCommand, {
|
|
385
|
+
await call.sendCommand(commands_ts_1.ActionSuggestionCommand, {
|
|
291
386
|
command_type: "action-suggestion",
|
|
292
387
|
intent_id: event.intent_id,
|
|
293
388
|
action_key: suggestion?.key ?? null,
|
|
@@ -296,6 +391,9 @@ let Agent = (() => {
|
|
|
296
391
|
}
|
|
297
392
|
else if (event.event_type === "execute-action") {
|
|
298
393
|
this._logger.info(`Executing action '${event.action_key}'`);
|
|
394
|
+
if (testSession) {
|
|
395
|
+
testSession.executedActions.push(event.action_key);
|
|
396
|
+
}
|
|
299
397
|
let onActionFunc;
|
|
300
398
|
if (this._onActionGeneric !== undefined) {
|
|
301
399
|
onActionFunc = () => this._onActionGeneric(call, event.action_key);
|
|
@@ -311,9 +409,15 @@ let Agent = (() => {
|
|
|
311
409
|
}
|
|
312
410
|
}
|
|
313
411
|
else if (event.event_type === "bot-session-ended") {
|
|
314
|
-
this._logger.info(
|
|
315
|
-
if (
|
|
316
|
-
|
|
412
|
+
this._logger.info(`Session ended: ${event.termination_reason}`);
|
|
413
|
+
if (testSession) {
|
|
414
|
+
testSession.terminationReason = event.termination_reason;
|
|
415
|
+
}
|
|
416
|
+
await this._onSessionEnd?.(call, event);
|
|
417
|
+
}
|
|
418
|
+
else if (event.event_type === "dtmf") {
|
|
419
|
+
if (this._onDtmf !== undefined) {
|
|
420
|
+
await this._onDtmf(call, event);
|
|
317
421
|
}
|
|
318
422
|
}
|
|
319
423
|
else if (event.event_type === "error") {
|
|
@@ -323,14 +427,14 @@ let Agent = (() => {
|
|
|
323
427
|
this._logger.warn(`The Guava agent reported a warning: ${event.content}`);
|
|
324
428
|
}
|
|
325
429
|
}
|
|
326
|
-
async
|
|
430
|
+
async _initCall(variables = {}) {
|
|
327
431
|
const call = new index_ts_1.Call(variables);
|
|
328
|
-
call.setPersona({
|
|
432
|
+
await call.setPersona({
|
|
329
433
|
agentName: this._name,
|
|
330
434
|
agentPurpose: this._purpose,
|
|
331
435
|
organizationName: this._organization,
|
|
332
436
|
});
|
|
333
|
-
call.sendCommand(commands_ts_1.RegisteredHooksCommand, {
|
|
437
|
+
await call.sendCommand(commands_ts_1.RegisteredHooksCommand, {
|
|
334
438
|
command_type: "registered-hooks",
|
|
335
439
|
has_on_question: this._onQuestion !== undefined,
|
|
336
440
|
has_on_intent: false,
|
|
@@ -341,111 +445,291 @@ let Agent = (() => {
|
|
|
341
445
|
}
|
|
342
446
|
return call;
|
|
343
447
|
}
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
448
|
+
async _attachToCall(callId, initialVariables = {}, testSession) {
|
|
449
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
450
|
+
try {
|
|
451
|
+
const call = await this._initCall(initialVariables);
|
|
452
|
+
const url = new URL(`v2/connect-call/${callId}`, this._client.getWebsocketBase());
|
|
453
|
+
const socket = __addDisposableResource(env_1, await new client_ts_1.GuavaSocket(`call-connection-${callId}`, url.toString(), this._client, (cmd) => cmd, (payload) => (0, events_ts_1.decodeEventDict)(payload), 18000).connect(), true);
|
|
454
|
+
await call.setDrain(async (commands) => {
|
|
455
|
+
for (const command of commands.splice(0)) {
|
|
456
|
+
socket.send(command);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
try {
|
|
460
|
+
for await (const event of socket) {
|
|
461
|
+
if (event === null)
|
|
462
|
+
continue;
|
|
463
|
+
await this._dispatchEvent(call, event, testSession);
|
|
464
|
+
if (event.event_type === "bot-session-ended" ||
|
|
465
|
+
event.event_type === "outbound-call-failed") {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch (e) {
|
|
471
|
+
if (!(e instanceof client_ts_1.GuavaSocketClosedError))
|
|
472
|
+
throw e;
|
|
473
|
+
}
|
|
355
474
|
}
|
|
356
|
-
|
|
357
|
-
|
|
475
|
+
catch (e_1) {
|
|
476
|
+
env_1.error = e_1;
|
|
477
|
+
env_1.hasError = true;
|
|
358
478
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
479
|
+
finally {
|
|
480
|
+
const result_1 = __disposeResources(env_1);
|
|
481
|
+
if (result_1)
|
|
482
|
+
await result_1;
|
|
363
483
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
484
|
+
}
|
|
485
|
+
async _listenInbound(conn) {
|
|
486
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
487
|
+
try {
|
|
488
|
+
const url = new URL("v2/listen-inbound", this._client.getWebsocketBase());
|
|
489
|
+
if ("agent_number" in conn) {
|
|
490
|
+
url.searchParams.set("phone_number", conn.agent_number);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
url.searchParams.set("webrtc_code", conn.webrtc_code);
|
|
494
|
+
}
|
|
495
|
+
const socket = __addDisposableResource(env_2, await new client_ts_1.GuavaSocket("listen-inbound", url.toString(), this._client, (msg) => msg, ListenInbound.decodeServerMessage).connect(), true);
|
|
496
|
+
try {
|
|
497
|
+
for await (const msg of socket) {
|
|
498
|
+
switch (msg.message_type) {
|
|
499
|
+
case "listen-started":
|
|
500
|
+
if ("agent_number" in conn) {
|
|
501
|
+
this._logger.info("Listening on %s (%d other listeners).", conn.agent_number, msg.other_listeners);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
this._logger.info("Listening on WebRTC code %s (%d other listeners).", conn.webrtc_code, msg.other_listeners);
|
|
505
|
+
const debugUrl = new URL(`debug-webrtc?webrtc_code=${conn.webrtc_code}`, this._client.getHttpBase());
|
|
506
|
+
this._logger.info("Call your agent at: %s", debugUrl);
|
|
507
|
+
}
|
|
508
|
+
break;
|
|
509
|
+
case "incoming-call":
|
|
510
|
+
socket.send({ message_type: "claim-call", call_id: msg.call_id });
|
|
511
|
+
break;
|
|
512
|
+
case "assign-call": {
|
|
513
|
+
const { call_id, call_info } = msg;
|
|
514
|
+
this._logger.info("Received call (session ID: %s)", call_id);
|
|
515
|
+
this._handleAssignedCall(call_id, call_info, socket).catch((err) => {
|
|
516
|
+
this._logger.error("Error handling assigned call %s: %s", call_id, err);
|
|
517
|
+
});
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
387
520
|
}
|
|
388
|
-
}
|
|
389
|
-
calls[tunnel_event.call_id] = call;
|
|
521
|
+
}
|
|
390
522
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
523
|
+
catch (e) {
|
|
524
|
+
if (!(e instanceof client_ts_1.GuavaSocketClosedError))
|
|
525
|
+
throw e;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (e_2) {
|
|
529
|
+
env_2.error = e_2;
|
|
530
|
+
env_2.hasError = true;
|
|
531
|
+
}
|
|
532
|
+
finally {
|
|
533
|
+
const result_2 = __disposeResources(env_2);
|
|
534
|
+
if (result_2)
|
|
535
|
+
await result_2;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async _handleAssignedCall(callId, callInfo, socket, initialVariables = {}) {
|
|
539
|
+
const action = await this._onCallReceived(callInfo);
|
|
540
|
+
if (action.action === "decline") {
|
|
541
|
+
this._logger.info("Declining call %s.", callId);
|
|
542
|
+
socket.send({ message_type: "decline-call", call_id: callId });
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
this._logger.info("Accepting call %s.", callId);
|
|
546
|
+
socket.send({ message_type: "answer-call", call_id: callId });
|
|
547
|
+
await this._attachToCall(callId, initialVariables);
|
|
548
|
+
}
|
|
394
549
|
}
|
|
395
550
|
/**
|
|
396
551
|
* @description use the Guava API to call out to a number
|
|
397
552
|
*/
|
|
398
553
|
async callPhone(fromNumber, toNumber, variables = {}) {
|
|
399
|
-
const url = new URL("
|
|
400
|
-
|
|
401
|
-
|
|
554
|
+
const url = new URL("v2/create-outbound", this._client.getHttpBase());
|
|
555
|
+
if (fromNumber)
|
|
556
|
+
url.searchParams.set("from_number", fromNumber);
|
|
557
|
+
url.searchParams.set("to_number", toNumber);
|
|
558
|
+
const response = await (0, utils_ts_1.fetchOrThrow)(url, {
|
|
559
|
+
method: "POST",
|
|
560
|
+
headers: await this._client.headers(),
|
|
402
561
|
});
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
562
|
+
const { call_id } = (await response.json());
|
|
563
|
+
this._logger.info("Outbound call created with session ID: %s", call_id);
|
|
564
|
+
await this._attachToCall(call_id, variables);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Run the agent against a live test session.
|
|
568
|
+
*
|
|
569
|
+
* Connects to the Guava test endpoint, starts the agent's call handling, and
|
|
570
|
+
* calls `callback` with a TestSession for driving the conversation
|
|
571
|
+
* programmatically. Returns the completed TestSession after the callback and
|
|
572
|
+
* call handler both finish.
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* const session = await agent.test(async (session) => {
|
|
576
|
+
* await session.waitForTurn();
|
|
577
|
+
* session.say("Hi, I'd like to make a purchase.");
|
|
578
|
+
* await session.waitForEnd();
|
|
579
|
+
* });
|
|
580
|
+
* assert(session.executedActions.includes("sales"));
|
|
581
|
+
*/
|
|
582
|
+
async test(callback, variables = {}) {
|
|
583
|
+
const url = new URL("v1/test-agent", this._client.getWebsocketBase());
|
|
584
|
+
const headers = await this._client.headers();
|
|
585
|
+
const ws = new ws_1.default(url.toString(), { headers });
|
|
586
|
+
await new Promise((resolve, reject) => {
|
|
587
|
+
ws.once("open", resolve);
|
|
588
|
+
ws.once("error", reject);
|
|
589
|
+
});
|
|
590
|
+
const rawFirst = await new Promise((resolve, reject) => {
|
|
591
|
+
ws.once("message", (data) => resolve(data.toString()));
|
|
592
|
+
ws.once("error", reject);
|
|
419
593
|
});
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
594
|
+
const sessionStarted = protocol_ts_1.SessionStarted.parse(JSON.parse(rawFirst));
|
|
595
|
+
const testSession = new session_ts_1.TestSession(ws, this._client);
|
|
596
|
+
const attachPromise = this._attachToCall(sessionStarted.session_id, variables, testSession).catch((err) => {
|
|
597
|
+
this._logger.error("Error in _attachToCall during test: %s", err);
|
|
598
|
+
});
|
|
599
|
+
try {
|
|
600
|
+
await callback(testSession);
|
|
601
|
+
}
|
|
602
|
+
finally {
|
|
603
|
+
ws.close();
|
|
604
|
+
await attachPromise;
|
|
605
|
+
}
|
|
606
|
+
return testSession;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Run an automated test conversation where an LLM plays the caller.
|
|
610
|
+
*
|
|
611
|
+
* Connects to the Guava test endpoint, starts the agent, then drives a
|
|
612
|
+
* back-and-forth conversation by repeatedly asking the Guava LLM to decide
|
|
613
|
+
* whether to speak or hang up based on the transcript so far.
|
|
614
|
+
*
|
|
615
|
+
* @param roleplayPrompt - Instructions for the simulated caller, e.g.
|
|
616
|
+
* `"You are a frustrated customer trying to cancel your subscription."`
|
|
617
|
+
* @param variables - Optional initial call variables.
|
|
618
|
+
* @returns The completed TestSession. Call `session.evaluate()` to assert
|
|
619
|
+
* pass/fail criteria, or `session.getTranscript()` to inspect the conversation.
|
|
620
|
+
*
|
|
621
|
+
* @example
|
|
622
|
+
* const session = await agent.testRoleplay(
|
|
623
|
+
* "You are a caller trying to buy a new table.",
|
|
624
|
+
* );
|
|
625
|
+
* assert(session.executedActions.includes("sales"));
|
|
626
|
+
*/
|
|
627
|
+
async testRoleplay(roleplayPrompt, variables = {}) {
|
|
628
|
+
const roleplaySchema = {
|
|
629
|
+
type: "object",
|
|
630
|
+
properties: {
|
|
631
|
+
action: { type: "string", enum: ["speak", "hangup"] },
|
|
632
|
+
utterance: { type: "string" },
|
|
633
|
+
},
|
|
634
|
+
required: ["action"],
|
|
635
|
+
additionalProperties: false,
|
|
636
|
+
};
|
|
637
|
+
return this.test(async (session) => {
|
|
638
|
+
let snapshotLen = 0;
|
|
639
|
+
try {
|
|
640
|
+
while (true) {
|
|
641
|
+
await session.waitForTurn();
|
|
642
|
+
for (const event of session._events.slice(snapshotLen)) {
|
|
643
|
+
if (event.message_type === "bot-tts") {
|
|
644
|
+
this._logger.info("(Roleplay Session) [agent]: %s", event.transcript);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
snapshotLen = session._events.length;
|
|
648
|
+
const transcript = session.getTranscript();
|
|
649
|
+
const prompt = `${roleplayPrompt}
|
|
650
|
+
|
|
651
|
+
You are roleplaying as a caller on a phone call. Decide what to do next based on the conversation so far.
|
|
652
|
+
|
|
653
|
+
Conversation:
|
|
654
|
+
${transcript || "(The agent has not spoken yet)"}
|
|
655
|
+
|
|
656
|
+
Choose "speak" and provide your next utterance, or choose "hangup" if the conversation has naturally concluded.`;
|
|
657
|
+
const raw = await (0, llm_ts_1._generate)(this._client, prompt, roleplaySchema);
|
|
658
|
+
const action = JSON.parse(raw);
|
|
659
|
+
if (action.action === "hangup") {
|
|
660
|
+
this._logger.info("(Roleplay Session) [caller hangs up]");
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
if (action.action === "speak" && action.utterance) {
|
|
664
|
+
this._logger.info("(Roleplay Session) [caller]: %s", action.utterance);
|
|
665
|
+
session.say(action.utterance);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
if (err.message === "Test session WebSocket closed") {
|
|
671
|
+
this._logger.info("Roleplay session ended by server.");
|
|
427
672
|
}
|
|
428
673
|
else {
|
|
429
|
-
|
|
430
|
-
socketInitialized = true;
|
|
674
|
+
throw err;
|
|
431
675
|
}
|
|
432
676
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (event) {
|
|
437
|
-
this._dispatchEvent(call, event);
|
|
677
|
+
for (const event of session._events.slice(snapshotLen)) {
|
|
678
|
+
if (event.message_type === "bot-tts") {
|
|
679
|
+
this._logger.info("(Roleplay Session) [agent]: %s", event.transcript);
|
|
438
680
|
}
|
|
439
681
|
}
|
|
682
|
+
}, variables);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Start an interactive terminal chat session with the agent.
|
|
686
|
+
*
|
|
687
|
+
* Opens a TUI with a scrolling conversation panel and an input line.
|
|
688
|
+
* Agent responses appear in real time. Press Ctrl+C or let the agent
|
|
689
|
+
* end the session to exit.
|
|
690
|
+
*
|
|
691
|
+
* @param variables - Optional initial call variables.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* await agent.chat();
|
|
695
|
+
* // or: await agent.chat({ patient_name: "Benjamin Buttons" });
|
|
696
|
+
*/
|
|
697
|
+
async chat(variables = {}) {
|
|
698
|
+
await this.test(async (session) => {
|
|
699
|
+
await (0, chat_ts_1.runChat)(session);
|
|
700
|
+
}, variables);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Return a shallow copy of this agent with independently overridable
|
|
704
|
+
* callbacks.
|
|
705
|
+
*
|
|
706
|
+
* Use in tests to register alternative handlers on the clone without
|
|
707
|
+
* affecting the original agent.
|
|
708
|
+
*/
|
|
709
|
+
patch() {
|
|
710
|
+
const cloned = new Agent({
|
|
711
|
+
name: this._name,
|
|
712
|
+
organization: this._organization,
|
|
713
|
+
purpose: this._purpose,
|
|
440
714
|
});
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
715
|
+
cloned._onCallReceived = this._onCallReceived;
|
|
716
|
+
cloned._onCallStart = this._onCallStart;
|
|
717
|
+
cloned._onCallerSpeech = this._onCallerSpeech;
|
|
718
|
+
cloned._onAgentSpeech = this._onAgentSpeech;
|
|
719
|
+
cloned._onQuestion = this._onQuestion;
|
|
720
|
+
cloned._onTaskCompleteGeneric = this._onTaskCompleteGeneric;
|
|
721
|
+
cloned._onTaskCompleteHandlers = { ...this._onTaskCompleteHandlers };
|
|
722
|
+
cloned._searchQueryHandlers = { ...this._searchQueryHandlers };
|
|
723
|
+
cloned._onActionRequested = this._onActionRequested;
|
|
724
|
+
cloned._onActionGeneric = this._onActionGeneric;
|
|
725
|
+
cloned._onActionHandlers = { ...this._onActionHandlers };
|
|
726
|
+
cloned._onSessionEnd = this._onSessionEnd;
|
|
727
|
+
cloned._onDtmf = this._onDtmf;
|
|
728
|
+
return cloned;
|
|
445
729
|
}
|
|
446
730
|
/* ===== Aliases to be removed at some point. ===== */
|
|
447
731
|
/** @deprecated Use {@link listenPhone} instead. */
|
|
448
|
-
inboundPhone(phoneNumber) {
|
|
732
|
+
async inboundPhone(phoneNumber) {
|
|
449
733
|
return this.listenPhone(phoneNumber);
|
|
450
734
|
}
|
|
451
735
|
/** @deprecated Use {@link callPhone} instead. */
|
|
@@ -456,13 +740,4 @@ let Agent = (() => {
|
|
|
456
740
|
return Agent = _classThis;
|
|
457
741
|
})();
|
|
458
742
|
exports.Agent = Agent;
|
|
459
|
-
class InboundListener {
|
|
460
|
-
ws;
|
|
461
|
-
constructor(ws) {
|
|
462
|
-
this.ws = ws;
|
|
463
|
-
}
|
|
464
|
-
close() {
|
|
465
|
-
this.ws.close();
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
743
|
//# sourceMappingURL=agent.js.map
|