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