@guava-ai/guava-sdk 0.2.0 → 0.4.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/README.md +2 -7
- package/bin/example-runner.js +16 -7
- package/dist/examples/credit-card-activation.js +94 -112
- package/dist/examples/credit-card-activation.js.map +1 -1
- package/dist/examples/property-insurance.js +12 -26
- package/dist/examples/property-insurance.js.map +1 -1
- package/dist/examples/scheduling-outbound.d.ts +1 -0
- package/dist/examples/scheduling-outbound.js +62 -0
- package/dist/examples/scheduling-outbound.js.map +1 -0
- package/dist/examples/thai-palace.js +61 -0
- package/dist/examples/thai-palace.js.map +1 -0
- package/dist/package.json +7 -5
- package/dist/src/action_item.d.ts +34 -12
- package/dist/src/action_item.js +34 -7
- package/dist/src/action_item.js.map +1 -1
- package/dist/src/call-controller.d.ts +137 -0
- package/dist/src/call-controller.js +433 -0
- package/dist/src/call-controller.js.map +1 -0
- package/dist/src/commands.d.ts +67 -27
- package/dist/src/commands.js +41 -27
- package/dist/src/commands.js.map +1 -1
- package/dist/src/events.d.ts +47 -30
- package/dist/src/events.js +42 -36
- package/dist/src/events.js.map +1 -1
- package/dist/src/example_data.d.ts +1 -0
- package/dist/src/example_data.js +33 -0
- package/dist/src/example_data.js.map +1 -1
- package/dist/src/helpers/openai.d.ts +12 -1
- package/dist/src/helpers/openai.js +168 -68
- package/dist/src/helpers/openai.js.map +1 -1
- package/dist/src/index.d.ts +6 -121
- package/dist/src/index.js +249 -483
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging.d.ts +2 -1
- package/dist/src/logging.js +32 -7
- package/dist/src/logging.js.map +1 -1
- package/dist/src/telemetry.d.ts +23 -0
- package/dist/src/telemetry.js +98 -0
- package/dist/src/telemetry.js.map +1 -0
- package/dist/src/utils.d.ts +3 -0
- package/dist/src/utils.js +28 -0
- package/dist/src/utils.js.map +1 -0
- package/examples/biome.json +5 -0
- package/examples/credit-card-activation.ts +20 -26
- package/examples/property-insurance.ts +6 -16
- package/examples/scheduling-outbound.ts +80 -0
- package/examples/{thai_palace.ts → thai-palace.ts} +10 -13
- package/package.json +7 -5
- package/src/action_item.ts +53 -13
- package/src/call-controller.ts +451 -0
- package/src/commands.ts +58 -42
- package/src/events.ts +66 -51
- package/src/example_data.ts +42 -0
- package/src/helpers/openai.ts +73 -18
- package/src/index.ts +81 -403
- package/src/logging.ts +39 -7
- package/src/telemetry.ts +125 -0
- package/src/utils.ts +32 -0
- package/dist/examples/thai_palace.js +0 -79
- package/dist/examples/thai_palace.js.map +0 -1
- /package/dist/examples/{thai_palace.d.ts → thai-palace.d.ts} +0 -0
package/dist/src/index.js
CHANGED
|
@@ -1,447 +1,226 @@
|
|
|
1
|
-
var
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
9
34
|
};
|
|
10
35
|
import WebSocket from "ws";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
36
|
+
import { getDefaultLogger } from "./logging.js";
|
|
37
|
+
import { StartOutboundCallCommand, ListenInboundCommand, InboundTunnelCommand, } from "./commands.js";
|
|
13
38
|
import * as z from "zod";
|
|
14
|
-
import {
|
|
39
|
+
import { ErrorEvent, SessionStartedEvent, decodeEvent, InboundTunnelEvent } from "./events.js";
|
|
15
40
|
import pkgdata from "../package.json" with { type: "json" };
|
|
16
41
|
import os from "node:os";
|
|
17
|
-
|
|
18
|
-
|
|
42
|
+
import { getBaseUrl, fetchOrThrow } from "./utils.js";
|
|
43
|
+
import { telemetryClient } from "./telemetry.js";
|
|
44
|
+
export { CallController } from "./call-controller.js";
|
|
45
|
+
export { Say, Field } from "./action_item.js";
|
|
46
|
+
const SDK_NAME = "typescript-sdk";
|
|
47
|
+
let firstClient = false;
|
|
19
48
|
/**
|
|
20
49
|
* @description convenience function for stringifying data according to a schema
|
|
21
50
|
*/
|
|
22
51
|
function stringifyZod(schema, data) {
|
|
23
52
|
return JSON.stringify(schema.parse(data));
|
|
24
53
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*/
|
|
41
|
-
setDrain(newDrain) {
|
|
42
|
-
this._drain = newDrain;
|
|
43
|
-
this.flush();
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* @description [inbound] receive a call, and process further.
|
|
47
|
-
*/
|
|
48
|
-
acceptCall() {
|
|
49
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
-
yield this.sendCommand(acceptInboundCallCommand, {
|
|
51
|
-
command_type: "accept-inbound",
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* @description read a span of text verbatim
|
|
57
|
-
*/
|
|
58
|
-
readScript(script) {
|
|
59
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
-
yield this.sendCommand(readScriptCommand, {
|
|
61
|
-
command_type: "read-script",
|
|
62
|
-
script: script,
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* @description [inbound] reject a call
|
|
68
|
-
*/
|
|
69
|
-
rejectCall() {
|
|
70
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
-
yield this.sendCommand(rejectInboundCallCommand, {
|
|
72
|
-
command_type: "reject-inbound",
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
addInfo(_info) {
|
|
77
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
-
throw new Error("not implemeneted");
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* @description read a span of text non-verbatim
|
|
83
|
-
*/
|
|
84
|
-
sendInstruction(instruction) {
|
|
85
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
-
yield this.sendCommand(sendInstructionCommand, {
|
|
87
|
-
command_type: "send-instruction",
|
|
88
|
-
instruction: instruction,
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* @description provide identifiers the agent will use to identify the virtual agent
|
|
94
|
-
*/
|
|
95
|
-
setPersona(args) {
|
|
96
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
yield this.sendCommand(setPersona, {
|
|
98
|
-
command_type: "set-persona",
|
|
99
|
-
organization_name: args.organizationName,
|
|
100
|
-
agent_name: args.agentName,
|
|
101
|
-
agent_purpose: args.agentPurpose,
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* @description direct the agent to collect information
|
|
107
|
-
* @param goal {} an objective string and/or a checklist of information to collect
|
|
108
|
-
* @param on_complete {} a callback to call once the information is available from the agent
|
|
109
|
-
* @param args {} arguments to pass through to the `on_complete` callback
|
|
110
|
-
*/
|
|
111
|
-
setTask(goal, on_complete = () => { }, ...args) {
|
|
112
|
-
var _a;
|
|
113
|
-
this._current_task_id = Math.random().toString(16).substring(2, 8);
|
|
114
|
-
this._on_complete_current_task = on_complete.bind(this, ...args);
|
|
115
|
-
if (!("checklist" in goal)) {
|
|
116
|
-
this.sendCommand(setTaskCommand, {
|
|
117
|
-
command_type: "set-task",
|
|
118
|
-
task_id: this._current_task_id,
|
|
119
|
-
objective: goal.objective,
|
|
120
|
-
action_items: [],
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const action_items = goal.checklist.map((item) => typeof item == "string"
|
|
125
|
-
? {
|
|
126
|
-
item_type: "todo",
|
|
127
|
-
description: item,
|
|
128
|
-
}
|
|
129
|
-
: item);
|
|
130
|
-
this.sendCommand(setTaskCommand, {
|
|
131
|
-
command_type: "set-task",
|
|
132
|
-
task_id: this._current_task_id,
|
|
133
|
-
objective: (_a = goal.objective) !== null && _a !== void 0 ? _a : "",
|
|
134
|
-
action_items,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* @description direct the agent to collect information, continuing execution once the agent has collected the information
|
|
140
|
-
* @param goal {} an objective string and/or a checklist of information to collect
|
|
141
|
-
*/
|
|
142
|
-
awaitTask(goal) {
|
|
143
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
-
return new Promise((resolve) => {
|
|
145
|
-
this.setTask(goal, (_args) => {
|
|
146
|
-
resolve();
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* @description retrieve a piece of information that the agent has collected
|
|
153
|
-
* @param key {string} key of the field checklist item
|
|
154
|
-
*/
|
|
155
|
-
getField(key) {
|
|
156
|
-
if (key in this._fieldValues) {
|
|
157
|
-
return this._fieldValues[key];
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
return null;
|
|
54
|
+
const http_start = /^http:\/\//;
|
|
55
|
+
const https_start = /^https:\/\//;
|
|
56
|
+
let Client = (() => {
|
|
57
|
+
let _classDecorators = [telemetryClient.trackClass()];
|
|
58
|
+
let _classDescriptor;
|
|
59
|
+
let _classExtraInitializers = [];
|
|
60
|
+
let _classThis;
|
|
61
|
+
var Client = class {
|
|
62
|
+
static { _classThis = this; }
|
|
63
|
+
static {
|
|
64
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
65
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
66
|
+
Client = _classThis = _classDescriptor.value;
|
|
67
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
68
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
161
69
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
70
|
+
_apiKey;
|
|
71
|
+
_baseUrl;
|
|
72
|
+
_logger;
|
|
73
|
+
_ws;
|
|
74
|
+
_controller;
|
|
75
|
+
messageHandler;
|
|
76
|
+
constructor(apiKey, baseUrl, logger, captureWarnings = true) {
|
|
77
|
+
// Set up the default logger.
|
|
78
|
+
if (logger) {
|
|
79
|
+
this._logger = logger;
|
|
171
80
|
}
|
|
172
81
|
else {
|
|
173
|
-
|
|
82
|
+
this._logger = getDefaultLogger();
|
|
174
83
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* @description transfer an accepted call
|
|
180
|
-
*/
|
|
181
|
-
transfer(to_number, transfer_message) {
|
|
182
|
-
const message = transfer_message !== null && transfer_message !== void 0 ? transfer_message : "I'm transferring you now";
|
|
183
|
-
this.sendCommand(transferCommand, {
|
|
184
|
-
command_type: "transfer-call",
|
|
185
|
-
transfer_message: message,
|
|
186
|
-
to_number: to_number,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
sendCommand(schema, data) {
|
|
190
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
191
|
-
const command = schema.parse(data);
|
|
192
|
-
this._commandQueue.push(command);
|
|
193
|
-
this.logger.debug(`Command queued: ${JSON.stringify(command)}`);
|
|
194
|
-
yield this.flush();
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
flush() {
|
|
198
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
199
|
-
var _a;
|
|
200
|
-
yield ((_a = this._drain) === null || _a === void 0 ? void 0 : _a.call(this, this._commandQueue));
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
onEvent(event) {
|
|
204
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
205
|
-
if (event.event_type == "caller-speech") {
|
|
206
|
-
this.onCallerSpeech(event);
|
|
84
|
+
// Resolve the API base URL.
|
|
85
|
+
if (baseUrl) {
|
|
86
|
+
this._baseUrl = baseUrl;
|
|
207
87
|
}
|
|
208
|
-
else
|
|
209
|
-
this.
|
|
88
|
+
else {
|
|
89
|
+
this._baseUrl = getBaseUrl();
|
|
210
90
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const answer = yield this.onQuestion(event.question);
|
|
215
|
-
yield this.sendCommand(answerQuestionCommand, {
|
|
216
|
-
command_type: "answer-question",
|
|
217
|
-
question_id: event.question_id,
|
|
218
|
-
answer: answer,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
catch (e) {
|
|
222
|
-
this.logger.error("Error occured while answering question.");
|
|
223
|
-
yield this.sendCommand(answerQuestionCommand, {
|
|
224
|
-
command_type: "answer-question",
|
|
225
|
-
question_id: event.question_id,
|
|
226
|
-
answer: "An error occured and the question could not be answered.",
|
|
227
|
-
});
|
|
228
|
-
}
|
|
91
|
+
// Resolve the API key.
|
|
92
|
+
if (apiKey) {
|
|
93
|
+
this._apiKey = apiKey;
|
|
229
94
|
}
|
|
230
|
-
else if (
|
|
231
|
-
this.
|
|
232
|
-
const intent_response = yield this.onIntent(event.intent_summary);
|
|
233
|
-
if (intent_response) {
|
|
234
|
-
const response_str = `Responding to intent ${event.intent_id}: ${intent_response}`;
|
|
235
|
-
this.logger.info(response_str);
|
|
236
|
-
this.sendInstruction(intent_response);
|
|
237
|
-
}
|
|
95
|
+
else if (process.env.GUAVA_API_KEY) {
|
|
96
|
+
this._apiKey = process.env.GUAVA_API_KEY;
|
|
238
97
|
}
|
|
239
|
-
else
|
|
240
|
-
|
|
241
|
-
if (event.task_id == this._current_task_id) {
|
|
242
|
-
// assertion is implied
|
|
243
|
-
const on_complete = this._on_complete_current_task;
|
|
244
|
-
this._on_complete_current_task = undefined;
|
|
245
|
-
if (on_complete) {
|
|
246
|
-
on_complete();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
98
|
+
else {
|
|
99
|
+
throw new Error("Guava API key must be provided either as argument to client constructor, or in environment variable GUAVA_API_KEY.");
|
|
249
100
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
101
|
+
if (!firstClient) {
|
|
102
|
+
firstClient = true;
|
|
103
|
+
if (captureWarnings) {
|
|
104
|
+
process.on("warning", (warning) => {
|
|
105
|
+
this._logger.warn(warning.toString());
|
|
106
|
+
});
|
|
254
107
|
}
|
|
108
|
+
telemetryClient.setSdkHeaders(this.headers());
|
|
109
|
+
this._checkSdkDeprecation();
|
|
255
110
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
event.event_type == "bot-session-ended") {
|
|
261
|
-
// no-op, don't warn
|
|
111
|
+
}
|
|
112
|
+
getWebsocketBase() {
|
|
113
|
+
if (http_start.test(this._baseUrl)) {
|
|
114
|
+
return `ws://${this._baseUrl.substring("ws://".length)}`;
|
|
262
115
|
}
|
|
263
|
-
else if (
|
|
264
|
-
this.
|
|
116
|
+
else if (https_start.test(this._baseUrl)) {
|
|
117
|
+
return `wss://${this._baseUrl.substring("wss://".length)}`;
|
|
265
118
|
}
|
|
266
119
|
else {
|
|
267
|
-
|
|
120
|
+
throw new Error(`Invalid base URL: ${this._baseUrl}}`);
|
|
268
121
|
}
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
// callbacks
|
|
272
|
-
/**
|
|
273
|
-
* @abstract
|
|
274
|
-
* @description called when an inbound call is received. The overriding function must start
|
|
275
|
-
* with `await super.onIncomingCall(from_number)`
|
|
276
|
-
*/
|
|
277
|
-
onIncomingCall(from_number) {
|
|
278
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
279
|
-
yield this.onCallStart();
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* @abstract
|
|
284
|
-
* @description called when a call is connected by the API, whether inbound or outbound
|
|
285
|
-
*/
|
|
286
|
-
onCallStart() {
|
|
287
|
-
return __awaiter(this, void 0, void 0, function* () { });
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* @abstract
|
|
291
|
-
* @description called when the caller speaks to the agent.
|
|
292
|
-
*/
|
|
293
|
-
onCallerSpeech(event) {
|
|
294
|
-
return __awaiter(this, void 0, void 0, function* () { });
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* @abstract
|
|
298
|
-
* @description called when the agent speaks to the caller.
|
|
299
|
-
*/
|
|
300
|
-
onAgentSpeech(event) {
|
|
301
|
-
return __awaiter(this, void 0, void 0, function* () { });
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* @abstract
|
|
305
|
-
* @description called when the caller expresses a task they wish to execute
|
|
306
|
-
*/
|
|
307
|
-
onIntent(intent) {
|
|
308
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
309
|
-
return "Unfortunately I'm not able to help with that.";
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* @abstract
|
|
314
|
-
* @description called when the agent needs to respond to a question that it doesn't know
|
|
315
|
-
* the answer to.
|
|
316
|
-
*/
|
|
317
|
-
onQuestion(question) {
|
|
318
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
319
|
-
return "I don't have an answer to that question.";
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const http_start = /^http:\/\//;
|
|
324
|
-
const https_start = /^https:\/\//;
|
|
325
|
-
export class Client {
|
|
326
|
-
constructor(apiKey, baseUrl, logger) {
|
|
327
|
-
// Set up the default logger.
|
|
328
|
-
if (logger) {
|
|
329
|
-
this._logger = logger;
|
|
330
122
|
}
|
|
331
|
-
|
|
332
|
-
this.
|
|
123
|
+
getHttpBase() {
|
|
124
|
+
return this._baseUrl;
|
|
333
125
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
126
|
+
headers() {
|
|
127
|
+
return {
|
|
128
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
129
|
+
"x-guava-platform": os.platform(),
|
|
130
|
+
"x-guava-runtime": process.release.name,
|
|
131
|
+
"x-guava-runtime-version": process.version,
|
|
132
|
+
"x-guava-sdk": SDK_NAME,
|
|
133
|
+
"x-guava-sdk-version": pkgdata.version,
|
|
134
|
+
};
|
|
337
135
|
}
|
|
338
|
-
|
|
339
|
-
this.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
throw new Error(`Invalid base URL: ${this._baseUrl}}`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
getHttpBase() {
|
|
367
|
-
return this._baseUrl;
|
|
368
|
-
}
|
|
369
|
-
headers() {
|
|
370
|
-
return {
|
|
371
|
-
Authorization: `Bearer ${this._apiKey}`,
|
|
372
|
-
"x-guava-platform": os.platform(),
|
|
373
|
-
"x-guava-runtime": process.release.name,
|
|
374
|
-
"x-guava-runtime-version": process.version,
|
|
375
|
-
"x-guava-sdk": "typescript-sdk",
|
|
376
|
-
"x-guava-sdk-version": pkgdata.version,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* @description use the Guava API to call out to a number
|
|
381
|
-
*/
|
|
382
|
-
createOutbound(fromNumber, toNumber, callControllerFactory) {
|
|
383
|
-
const url = new URL("v1/create-outbound", this.getWebsocketBase());
|
|
384
|
-
const ws = new WebSocket(url, {
|
|
385
|
-
headers: this.headers(),
|
|
386
|
-
});
|
|
387
|
-
const callController = (callControllerFactory !== null && callControllerFactory !== void 0 ? callControllerFactory : ((_) => undefined))(this._logger);
|
|
388
|
-
ws.addEventListener("open", (_ev) => __awaiter(this, void 0, void 0, function* () {
|
|
389
|
-
ws.send(stringifyZod(startOutboundCallCommand, {
|
|
390
|
-
command_type: "start-outbound",
|
|
391
|
-
to_number: toNumber,
|
|
392
|
-
from_number: fromNumber,
|
|
393
|
-
}));
|
|
394
|
-
yield (callController === null || callController === void 0 ? void 0 : callController.onCallStart());
|
|
395
|
-
}));
|
|
396
|
-
ws.addEventListener("close", (_ev) => {
|
|
397
|
-
// we are closing the socket, so don't trigger any other listeners
|
|
398
|
-
ws.removeAllListeners();
|
|
399
|
-
this._ws = undefined;
|
|
400
|
-
this._controller = undefined;
|
|
401
|
-
});
|
|
402
|
-
this._ws = ws;
|
|
403
|
-
this._controller = callController;
|
|
404
|
-
this.replaceHandler(this.uninitializedOutbound.bind(this));
|
|
405
|
-
// set the callController drain function to send all commands
|
|
406
|
-
// through the websocket
|
|
407
|
-
callController === null || callController === void 0 ? void 0 : callController.setDrain((commands) => __awaiter(this, void 0, void 0, function* () {
|
|
408
|
-
for (const command of commands.splice(0)) {
|
|
409
|
-
this._logger.debug(`Sending command ${JSON.stringify(command)}`);
|
|
410
|
-
ws.send(JSON.stringify(command));
|
|
136
|
+
async _checkSdkDeprecation() {
|
|
137
|
+
this._logger.debug(`Checking deprecation for SDK ${SDK_NAME}, ${pkgdata.version}.`);
|
|
138
|
+
try {
|
|
139
|
+
const url = new URL("v1/check-sdk-deprecation", this.getHttpBase());
|
|
140
|
+
url.searchParams.set("sdk_name", SDK_NAME);
|
|
141
|
+
url.searchParams.set("sdk_version", pkgdata.version);
|
|
142
|
+
const response = await fetchOrThrow(url, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: this.headers(),
|
|
145
|
+
});
|
|
146
|
+
const body = (await response.json());
|
|
147
|
+
if (body.deprecation_status === "supported") {
|
|
148
|
+
this._logger.info("SDK version still supported.");
|
|
149
|
+
}
|
|
150
|
+
else if (body.deprecation_status === "deprecated") {
|
|
151
|
+
process.emitWarning("This SDK version is deprecated. Please update to a newer version of the SDK.");
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this._logger.warn("SDK deprecation status unknown.");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
this._logger.error("Encountered issue while checking for deprecation.");
|
|
411
159
|
}
|
|
412
|
-
}));
|
|
413
|
-
}
|
|
414
|
-
replaceHandler(newHandler) {
|
|
415
|
-
var _a, _b;
|
|
416
|
-
if (this.messageHandler) {
|
|
417
|
-
(_a = this._ws) === null || _a === void 0 ? void 0 : _a.removeEventListener("message", this.messageHandler);
|
|
418
|
-
}
|
|
419
|
-
if (newHandler) {
|
|
420
|
-
(_b = this._ws) === null || _b === void 0 ? void 0 : _b.addEventListener("message", newHandler);
|
|
421
160
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
161
|
+
/**
|
|
162
|
+
* @description use the Guava API to call out to a number
|
|
163
|
+
*/
|
|
164
|
+
createOutbound(fromNumber, toNumber, callController) {
|
|
165
|
+
const url = new URL("v1/create-outbound", this.getWebsocketBase());
|
|
166
|
+
const ws = new WebSocket(url, {
|
|
167
|
+
headers: this.headers(),
|
|
168
|
+
});
|
|
169
|
+
ws.addEventListener("open", async (_ev) => {
|
|
170
|
+
ws.send(stringifyZod(StartOutboundCallCommand, {
|
|
171
|
+
command_type: "start-outbound",
|
|
172
|
+
to_number: toNumber,
|
|
173
|
+
from_number: fromNumber,
|
|
174
|
+
}));
|
|
175
|
+
// set the callController drain function to send all commands
|
|
176
|
+
// through the now open websocket
|
|
177
|
+
callController.setDrain(async (commands) => {
|
|
178
|
+
for (const command of commands.splice(0)) {
|
|
179
|
+
this._logger.debug(`Sending command ${JSON.stringify(command)}`);
|
|
180
|
+
ws.send(JSON.stringify(command));
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await callController.onCallStart();
|
|
184
|
+
});
|
|
185
|
+
ws.addEventListener("close", (_ev) => {
|
|
186
|
+
// we are closing the socket, so don't trigger any other listeners
|
|
187
|
+
ws.removeAllListeners();
|
|
188
|
+
this._ws = undefined;
|
|
189
|
+
this._controller = undefined;
|
|
190
|
+
});
|
|
191
|
+
this._ws = ws;
|
|
192
|
+
this._controller = callController;
|
|
193
|
+
this.replaceHandler(this.uninitializedOutbound.bind(this));
|
|
430
194
|
}
|
|
431
|
-
|
|
432
|
-
.
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
195
|
+
replaceHandler(newHandler) {
|
|
196
|
+
if (this.messageHandler) {
|
|
197
|
+
this._ws?.removeEventListener("message", this.messageHandler);
|
|
198
|
+
}
|
|
199
|
+
if (newHandler) {
|
|
200
|
+
this._ws?.addEventListener("message", newHandler);
|
|
201
|
+
}
|
|
202
|
+
this.messageHandler = newHandler;
|
|
436
203
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
204
|
+
// eventlistener handlers for server events
|
|
205
|
+
// (a state machine in functions)
|
|
206
|
+
uninitializedOutbound(ev) {
|
|
207
|
+
// for correctness (and type correctness)
|
|
208
|
+
if (!this._ws) {
|
|
209
|
+
throw new Error("[internal] Uninitialized WebSocket");
|
|
210
|
+
}
|
|
211
|
+
const session_started = z
|
|
212
|
+
.union([SessionStartedEvent, ErrorEvent])
|
|
213
|
+
.parse(JSON.parse(ev.data.toString("utf8")));
|
|
214
|
+
if (session_started.event_type === "error") {
|
|
215
|
+
throw new Error(`Outbound call failed: ${session_started.content}`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this._logger.info(`Started session with ID: ${session_started.session_id}`);
|
|
219
|
+
// move to next state
|
|
220
|
+
this.replaceHandler(this.initializedOutbound.bind(this));
|
|
221
|
+
}
|
|
441
222
|
}
|
|
442
|
-
|
|
443
|
-
initializedOutbound(ev) {
|
|
444
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
223
|
+
async initializedOutbound(ev) {
|
|
445
224
|
// for correctness (and type correctness)
|
|
446
225
|
if (!this._ws) {
|
|
447
226
|
throw new Error("[internal] Uninitialized WebSocket");
|
|
@@ -450,90 +229,77 @@ export class Client {
|
|
|
450
229
|
const event = decodeEvent(ev.data);
|
|
451
230
|
if (event) {
|
|
452
231
|
if (this._controller) {
|
|
453
|
-
|
|
232
|
+
await this._controller.onEvent(event);
|
|
454
233
|
}
|
|
455
|
-
if (event.event_type
|
|
456
|
-
event.event_type == "bot-session-ended") {
|
|
234
|
+
if (event.event_type === "outbound-call-failed" || event.event_type === "bot-session-ended") {
|
|
457
235
|
// shutdown the websocket
|
|
458
236
|
this._ws.close();
|
|
459
237
|
}
|
|
460
238
|
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* @description use the Guava API to receive calls at a given number
|
|
242
|
+
*/
|
|
243
|
+
listenInbound(conn, controllerClassFactory) {
|
|
244
|
+
const callControllers = {};
|
|
245
|
+
// return a way to *stop* listening
|
|
246
|
+
const url = new URL("v1/listen-inbound", this.getWebsocketBase());
|
|
247
|
+
const ws = new WebSocket(url, {
|
|
467
248
|
headers: this.headers(),
|
|
468
|
-
body: JSON.stringify({
|
|
469
|
-
handler_url: public_url,
|
|
470
|
-
handler_token: inbound_token,
|
|
471
|
-
}),
|
|
472
249
|
});
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* @description use the Guava API to receive calls at a given number
|
|
480
|
-
*/
|
|
481
|
-
listenInbound(conn, controllerClassFactory) {
|
|
482
|
-
const callControllers = {};
|
|
483
|
-
// return a way to *stop* listening
|
|
484
|
-
const url = new URL("v1/listen-inbound", this.getWebsocketBase());
|
|
485
|
-
const ws = new WebSocket(url, {
|
|
486
|
-
headers: this.headers(),
|
|
487
|
-
});
|
|
488
|
-
let agent_number;
|
|
489
|
-
let webrtc_code;
|
|
490
|
-
if ("agent_number" in conn) {
|
|
491
|
-
agent_number = conn.agent_number;
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
webrtc_code = conn.webrtc_code;
|
|
495
|
-
}
|
|
496
|
-
this._logger.info(`Listening for calls to ${agent_number !== null && agent_number !== void 0 ? agent_number : webrtc_code}`);
|
|
497
|
-
if (webrtc_code) {
|
|
498
|
-
const debugurl = new URL(`debug-webrtc?webrtc_code=${webrtc_code}`, this.getHttpBase());
|
|
499
|
-
this._logger.debug(`WebRTC DebugURL: ${debugurl}`);
|
|
500
|
-
}
|
|
501
|
-
ws.addEventListener("open", (_ev) => {
|
|
502
|
-
ws.send(stringifyZod(listenInboundCommand, {
|
|
503
|
-
command_type: "listen-inbound",
|
|
504
|
-
agent_number: agent_number,
|
|
505
|
-
webrtc_code: webrtc_code,
|
|
506
|
-
}));
|
|
507
|
-
});
|
|
508
|
-
ws.addEventListener("close", (_ev) => {
|
|
509
|
-
ws.removeAllListeners();
|
|
510
|
-
});
|
|
511
|
-
ws.addEventListener("message", (ev) => {
|
|
512
|
-
const tunnel_event = inboundTunnelEvent.parse(JSON.parse(ev.data.toString("utf8")));
|
|
513
|
-
if (!(tunnel_event.call_id in callControllers)) {
|
|
514
|
-
this._logger.info(`Received tunnel event for new call ID: ${tunnel_event.call_id}. Creating call controller.`);
|
|
515
|
-
const newController = controllerClassFactory(this._logger);
|
|
516
|
-
newController.setDrain((commands) => __awaiter(this, void 0, void 0, function* () {
|
|
517
|
-
for (const command of commands.splice(0)) {
|
|
518
|
-
this._logger.debug(`Sending command: ${JSON.stringify(command)} for call ID: ${tunnel_event.call_id}`);
|
|
519
|
-
ws.send(stringifyZod(inboundTunnelCommand, {
|
|
520
|
-
call_id: tunnel_event.call_id,
|
|
521
|
-
command,
|
|
522
|
-
}));
|
|
523
|
-
}
|
|
524
|
-
}));
|
|
525
|
-
callControllers[tunnel_event.call_id] = newController;
|
|
526
|
-
newController.onEvent(tunnel_event.event);
|
|
250
|
+
let agent_number;
|
|
251
|
+
let webrtc_code;
|
|
252
|
+
if ("agent_number" in conn) {
|
|
253
|
+
agent_number = conn.agent_number;
|
|
527
254
|
}
|
|
528
255
|
else {
|
|
529
|
-
|
|
530
|
-
callControllers[tunnel_event.call_id].onEvent(tunnel_event.event);
|
|
256
|
+
webrtc_code = conn.webrtc_code;
|
|
531
257
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
258
|
+
this._logger.info(`Listening for calls to ${agent_number ?? webrtc_code}`);
|
|
259
|
+
if (webrtc_code) {
|
|
260
|
+
const debugurl = new URL(`debug-webrtc?webrtc_code=${webrtc_code}`, this.getHttpBase());
|
|
261
|
+
this._logger.debug(`WebRTC DebugURL: ${debugurl}`);
|
|
262
|
+
}
|
|
263
|
+
ws.addEventListener("open", (_ev) => {
|
|
264
|
+
ws.send(stringifyZod(ListenInboundCommand, {
|
|
265
|
+
command_type: "listen-inbound",
|
|
266
|
+
agent_number: agent_number,
|
|
267
|
+
webrtc_code: webrtc_code,
|
|
268
|
+
}));
|
|
269
|
+
});
|
|
270
|
+
ws.addEventListener("close", (_ev) => {
|
|
271
|
+
ws.removeAllListeners();
|
|
272
|
+
});
|
|
273
|
+
ws.addEventListener("message", (ev) => {
|
|
274
|
+
const tunnel_event = InboundTunnelEvent.parse(JSON.parse(ev.data.toString("utf8")));
|
|
275
|
+
if (!(tunnel_event.call_id in callControllers)) {
|
|
276
|
+
this._logger.info(`Received tunnel event for new call ID: ${tunnel_event.call_id}. Creating call controller.`);
|
|
277
|
+
const newController = controllerClassFactory(this._logger);
|
|
278
|
+
newController.setDrain(async (commands) => {
|
|
279
|
+
for (const command of commands.splice(0)) {
|
|
280
|
+
this._logger.debug(`Sending command: ${JSON.stringify(command)} for call ID: ${tunnel_event.call_id}`);
|
|
281
|
+
ws.send(stringifyZod(InboundTunnelCommand, {
|
|
282
|
+
call_id: tunnel_event.call_id,
|
|
283
|
+
command,
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
callControllers[tunnel_event.call_id] = newController;
|
|
288
|
+
newController.onEvent(tunnel_event.event);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// no threading, so manually forward to onEvent!
|
|
292
|
+
callControllers[tunnel_event.call_id].onEvent(tunnel_event.event);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
return new InboundListener(ws);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
return Client = _classThis;
|
|
299
|
+
})();
|
|
300
|
+
export { Client };
|
|
536
301
|
class InboundListener {
|
|
302
|
+
ws;
|
|
537
303
|
constructor(ws) {
|
|
538
304
|
this.ws = ws;
|
|
539
305
|
}
|