@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/src/auth.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { getCliConfigPath, getBaseUrl, fetchOrThrow } from "./utils.ts";
|
|
3
|
+
import { getDefaultLogger } from "./logging.ts";
|
|
4
|
+
|
|
5
|
+
const logger = getDefaultLogger();
|
|
6
|
+
|
|
7
|
+
export interface AuthStrategy {
|
|
8
|
+
getHeaders(): Promise<Record<string, string>>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class APIKeyAuth implements AuthStrategy {
|
|
12
|
+
constructor(private readonly _apiKey: string) {}
|
|
13
|
+
|
|
14
|
+
async getHeaders(): Promise<Record<string, string>> {
|
|
15
|
+
return { Authorization: `Bearer ${this._apiKey}` };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const GUAVA_DEPLOY_TOKEN_PATH = "/var/run/secrets/guava/token";
|
|
20
|
+
const _GUAVA_DEPLOY_TOKEN_PREFIX = "gva-deploy2-";
|
|
21
|
+
|
|
22
|
+
export class GuavaDeploy implements AuthStrategy {
|
|
23
|
+
constructor(private readonly _tokenPath: string = GUAVA_DEPLOY_TOKEN_PATH) {}
|
|
24
|
+
|
|
25
|
+
async getHeaders(): Promise<Record<string, string>> {
|
|
26
|
+
const token = fs.readFileSync(this._tokenPath, "utf8").trim();
|
|
27
|
+
return { Authorization: `Bearer ${_GUAVA_DEPLOY_TOKEN_PREFIX}${token}` };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TOKEN_REFRESH_BUFFER_MS = 60_000;
|
|
32
|
+
|
|
33
|
+
interface CliConfig {
|
|
34
|
+
access_token: string;
|
|
35
|
+
expires_at: number;
|
|
36
|
+
refresh_token: string;
|
|
37
|
+
org_id: string;
|
|
38
|
+
base_url?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class CLIAuth implements AuthStrategy {
|
|
42
|
+
static exists(): boolean {
|
|
43
|
+
const configPath = getCliConfigPath();
|
|
44
|
+
if (!fs.existsSync(configPath)) return false;
|
|
45
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
|
|
46
|
+
return "refresh_token" in config;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private _accessToken: string;
|
|
50
|
+
private _expiresAt: number; // ms since epoch
|
|
51
|
+
private _refreshToken: string;
|
|
52
|
+
private _orgId: string;
|
|
53
|
+
private _baseUrl: string;
|
|
54
|
+
private _pendingRefresh: Promise<void> | null = null;
|
|
55
|
+
|
|
56
|
+
constructor() {
|
|
57
|
+
const config = JSON.parse(fs.readFileSync(getCliConfigPath(), "utf8")) as CliConfig;
|
|
58
|
+
this._accessToken = config.access_token;
|
|
59
|
+
this._expiresAt = config.expires_at * 1000;
|
|
60
|
+
this._refreshToken = config.refresh_token;
|
|
61
|
+
this._orgId = config.org_id;
|
|
62
|
+
this._baseUrl = config.base_url ?? getBaseUrl();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async _doRefresh(): Promise<void> {
|
|
66
|
+
logger.debug("Refreshing access token...");
|
|
67
|
+
const response = await fetchOrThrow(new URL("/oauth/token", this._baseUrl), {
|
|
68
|
+
method: "POST",
|
|
69
|
+
body: new URLSearchParams({
|
|
70
|
+
grant_type: "refresh_token",
|
|
71
|
+
refresh_token: this._refreshToken,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
const token = (await response.json()) as {
|
|
75
|
+
access_token: string;
|
|
76
|
+
expires_in: number;
|
|
77
|
+
refresh_token?: string;
|
|
78
|
+
};
|
|
79
|
+
this._accessToken = token.access_token;
|
|
80
|
+
this._expiresAt = Date.now() + token.expires_in * 1000;
|
|
81
|
+
if (token.refresh_token) {
|
|
82
|
+
logger.warn("Unexpected refresh token in response.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getHeaders(): Promise<Record<string, string>> {
|
|
87
|
+
if (Date.now() >= this._expiresAt - TOKEN_REFRESH_BUFFER_MS) {
|
|
88
|
+
if (!this._pendingRefresh) {
|
|
89
|
+
this._pendingRefresh = this._doRefresh().finally(() => {
|
|
90
|
+
this._pendingRefresh = null;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
await this._pendingRefresh;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
Authorization: `Bearer ${this._accessToken}`,
|
|
97
|
+
"x-guava-org-id": this._orgId,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let _cliAuthInstance: CLIAuth | null = null;
|
|
103
|
+
|
|
104
|
+
export function getCLIAuth(): CLIAuth {
|
|
105
|
+
if (!_cliAuthInstance) {
|
|
106
|
+
_cliAuthInstance = new CLIAuth();
|
|
107
|
+
}
|
|
108
|
+
return _cliAuthInstance;
|
|
109
|
+
}
|
package/src/call.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type ReachPersonOutcome = {
|
|
|
33
33
|
|
|
34
34
|
@telemetryClient.trackClass()
|
|
35
35
|
export class Call {
|
|
36
|
-
|
|
36
|
+
protected _commandQueue: Command[] = [];
|
|
37
37
|
private _variables: Record<string, any> = {};
|
|
38
38
|
protected logger: Logger;
|
|
39
39
|
|
|
@@ -272,11 +272,11 @@ TASK COMPLETION REQUIREMENTS:
|
|
|
272
272
|
async setVoicemailAction(action: { hangup: true } | { message: string }) {
|
|
273
273
|
if ("hangup" in action) {
|
|
274
274
|
await this.sendInstruction(
|
|
275
|
-
"If you encounter an answering machine, DO NOT leave a message. REMAIN SILENT AND HANG UP WITHOUT RESPONDING.",
|
|
275
|
+
"If you encounter an answering machine, DO NOT leave a message. REMAIN SILENT AND HANG UP WITHOUT RESPONDING. You should only do this when it's clear you are unable to reach the person.",
|
|
276
276
|
);
|
|
277
277
|
} else {
|
|
278
278
|
await this.sendInstruction(
|
|
279
|
-
`If you encounter an answering machine, say this message VERBATIM: ${action.message}
|
|
279
|
+
`If you encounter an answering machine, say this message VERBATIM: ${action.message}. You should only leave this message if it's clear you are unable to reach the person.`,
|
|
280
280
|
);
|
|
281
281
|
}
|
|
282
282
|
}
|
package/src/client.ts
CHANGED
|
@@ -9,9 +9,18 @@ import * as z from "zod";
|
|
|
9
9
|
import { ErrorEvent, SessionStartedEvent, decodeEvent, InboundTunnelEvent } from "./events.ts";
|
|
10
10
|
import { SDK_VERSION } from "./version.ts";
|
|
11
11
|
import os from "node:os";
|
|
12
|
+
import * as fs from "node:fs";
|
|
12
13
|
import { getBaseUrl, fetchOrThrow } from "./utils.ts";
|
|
13
14
|
import { telemetryClient } from "./telemetry.ts";
|
|
14
15
|
import type { CallController } from "./call-controller.ts";
|
|
16
|
+
import {
|
|
17
|
+
type AuthStrategy,
|
|
18
|
+
APIKeyAuth,
|
|
19
|
+
GuavaDeploy,
|
|
20
|
+
CLIAuth,
|
|
21
|
+
getCLIAuth,
|
|
22
|
+
GUAVA_DEPLOY_TOKEN_PATH,
|
|
23
|
+
} from "./auth.ts";
|
|
15
24
|
|
|
16
25
|
const SDK_NAME = "typescript-sdk";
|
|
17
26
|
|
|
@@ -28,7 +37,7 @@ const https_start = /^https:\/\//;
|
|
|
28
37
|
|
|
29
38
|
@telemetryClient.trackClass()
|
|
30
39
|
export class Client {
|
|
31
|
-
private
|
|
40
|
+
private _auth: AuthStrategy;
|
|
32
41
|
private _baseUrl: string;
|
|
33
42
|
private _logger: Logger;
|
|
34
43
|
private _ws?: WebSocket;
|
|
@@ -50,14 +59,18 @@ export class Client {
|
|
|
50
59
|
this._baseUrl = getBaseUrl();
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
// Resolve
|
|
62
|
+
// Resolve auth strategy.
|
|
54
63
|
if (apiKey) {
|
|
55
|
-
this.
|
|
64
|
+
this._auth = new APIKeyAuth(apiKey);
|
|
65
|
+
} else if (fs.existsSync(GUAVA_DEPLOY_TOKEN_PATH)) {
|
|
66
|
+
this._auth = new GuavaDeploy();
|
|
56
67
|
} else if (process.env.GUAVA_API_KEY) {
|
|
57
|
-
this.
|
|
68
|
+
this._auth = new APIKeyAuth(process.env.GUAVA_API_KEY);
|
|
69
|
+
} else if (CLIAuth.exists()) {
|
|
70
|
+
this._auth = getCLIAuth();
|
|
58
71
|
} else {
|
|
59
72
|
throw new Error(
|
|
60
|
-
"Guava
|
|
73
|
+
"Unable to authenticate to Guava. You must do one of the following:\n- Sign in using the Guava CLI.\n- Or, provide an API key using the GUAVA_API_KEY environment variable.\n- Or, provide the API key as an argument to the constructor.",
|
|
61
74
|
);
|
|
62
75
|
}
|
|
63
76
|
|
|
@@ -70,7 +83,7 @@ export class Client {
|
|
|
70
83
|
});
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
telemetryClient.
|
|
86
|
+
telemetryClient.setSdkClient(this);
|
|
74
87
|
this._checkSdkDeprecation();
|
|
75
88
|
}
|
|
76
89
|
}
|
|
@@ -89,9 +102,9 @@ export class Client {
|
|
|
89
102
|
return this._baseUrl;
|
|
90
103
|
}
|
|
91
104
|
|
|
92
|
-
headers() {
|
|
105
|
+
async headers(): Promise<Record<string, string>> {
|
|
93
106
|
return {
|
|
94
|
-
|
|
107
|
+
...(await this._auth.getHeaders()),
|
|
95
108
|
"x-guava-platform": os.platform(),
|
|
96
109
|
"x-guava-runtime": process.release.name,
|
|
97
110
|
"x-guava-runtime-version": process.version,
|
|
@@ -108,7 +121,7 @@ export class Client {
|
|
|
108
121
|
url.searchParams.set("sdk_version", SDK_VERSION);
|
|
109
122
|
const response = await fetchOrThrow(url, {
|
|
110
123
|
method: "POST",
|
|
111
|
-
headers: this.headers(),
|
|
124
|
+
headers: await this.headers(),
|
|
112
125
|
});
|
|
113
126
|
const body = (await response.json()) as { deprecation_status: string };
|
|
114
127
|
if (body.deprecation_status === "supported") {
|
|
@@ -136,7 +149,7 @@ export class Client {
|
|
|
136
149
|
}
|
|
137
150
|
const response = await fetchOrThrow(url, {
|
|
138
151
|
method: "POST",
|
|
139
|
-
headers: this.headers(),
|
|
152
|
+
headers: await this.headers(),
|
|
140
153
|
});
|
|
141
154
|
const body = (await response.json()) as { webrtc_code: string };
|
|
142
155
|
return body.webrtc_code;
|
|
@@ -145,10 +158,14 @@ export class Client {
|
|
|
145
158
|
/**
|
|
146
159
|
* @description use the Guava API to call out to a number
|
|
147
160
|
*/
|
|
148
|
-
createOutbound(
|
|
161
|
+
async createOutbound(
|
|
162
|
+
fromNumber: string | undefined,
|
|
163
|
+
toNumber: string,
|
|
164
|
+
callController: CallController,
|
|
165
|
+
) {
|
|
149
166
|
const url = new URL("v1/create-outbound", this.getWebsocketBase());
|
|
150
167
|
const ws = new WebSocket(url, {
|
|
151
|
-
headers: this.headers(),
|
|
168
|
+
headers: await this.headers(),
|
|
152
169
|
});
|
|
153
170
|
|
|
154
171
|
ws.addEventListener("open", async (_ev) => {
|
|
@@ -236,16 +253,16 @@ export class Client {
|
|
|
236
253
|
/**
|
|
237
254
|
* @description use the Guava API to receive calls at a given number
|
|
238
255
|
*/
|
|
239
|
-
listenInbound<U extends CallController>(
|
|
256
|
+
async listenInbound<U extends CallController>(
|
|
240
257
|
conn: InboundConnection,
|
|
241
258
|
controllerClassFactory: (logger: Logger) => U,
|
|
242
|
-
) {
|
|
259
|
+
): Promise<InboundListener> {
|
|
243
260
|
const callControllers: Record<string, U> = {};
|
|
244
261
|
|
|
245
262
|
// return a way to *stop* listening
|
|
246
263
|
const url = new URL("v1/listen-inbound", this.getWebsocketBase());
|
|
247
264
|
const ws = new WebSocket(url, {
|
|
248
|
-
headers: this.headers(),
|
|
265
|
+
headers: await this.headers(),
|
|
249
266
|
});
|
|
250
267
|
let agent_number: string | undefined;
|
|
251
268
|
let webrtc_code: string | undefined;
|
package/src/events.ts
CHANGED
|
@@ -89,6 +89,13 @@ export type OutboundCallFailed = z.infer<typeof OutboundCallFailed>;
|
|
|
89
89
|
|
|
90
90
|
export const BotSessionEnded = z.object({
|
|
91
91
|
event_type: z.literal("bot-session-ended"),
|
|
92
|
+
termination_reason: z.enum([
|
|
93
|
+
"user-hangup",
|
|
94
|
+
"bot-hangup",
|
|
95
|
+
"bot-failure",
|
|
96
|
+
"bot-transfer",
|
|
97
|
+
"voicemail",
|
|
98
|
+
]),
|
|
92
99
|
});
|
|
93
100
|
export type BotSessionEnded = z.infer<typeof BotSessionEnded>;
|
|
94
101
|
|
|
@@ -133,10 +140,25 @@ export const GuavaEvent = z.discriminatedUnion("event_type", [
|
|
|
133
140
|
]);
|
|
134
141
|
export type GuavaEvent = z.infer<typeof GuavaEvent>;
|
|
135
142
|
|
|
136
|
-
const _KNOWN_EVENT_TYPES = new Set(
|
|
143
|
+
const _KNOWN_EVENT_TYPES: Set<string> = new Set(
|
|
137
144
|
GuavaEvent.options.map((schema) => schema.shape.event_type.value),
|
|
138
145
|
);
|
|
139
146
|
|
|
147
|
+
export function decodeEventDict(data: Record<string, unknown>): GuavaEvent | null {
|
|
148
|
+
if (typeof data.event_type !== "string") {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Received event with non-string event_type: ${JSON.stringify(data.event_type)}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (!_KNOWN_EVENT_TYPES.has(data.event_type)) {
|
|
154
|
+
process.emitWarning(
|
|
155
|
+
`Received an unknown event type ${data.event_type}. Update to a newer version of this SDK.`,
|
|
156
|
+
);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return GuavaEvent.parse(data);
|
|
160
|
+
}
|
|
161
|
+
|
|
140
162
|
export function decodeEvent(
|
|
141
163
|
serialized_event: string | ArrayBuffer | Buffer | Buffer[],
|
|
142
164
|
): GuavaEvent | null {
|
|
@@ -154,15 +176,7 @@ export function decodeEvent(
|
|
|
154
176
|
} else {
|
|
155
177
|
data = JSON.parse(serialized_event.toString("utf8"));
|
|
156
178
|
}
|
|
157
|
-
|
|
158
|
-
if (!_KNOWN_EVENT_TYPES.has(data.event_type)) {
|
|
159
|
-
process.emitWarning(
|
|
160
|
-
`Received an unknown event type ${data.event_type}. Update to a newer version of this SDK.`,
|
|
161
|
-
);
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return GuavaEvent.parse(data);
|
|
179
|
+
return decodeEventDict(data);
|
|
166
180
|
}
|
|
167
181
|
|
|
168
182
|
export const InboundTunnelEvent = z.object({
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Client } from "../client.ts";
|
|
2
|
+
import { fetchOrThrow } from "../utils.ts";
|
|
3
|
+
|
|
4
|
+
export async function _generate(
|
|
5
|
+
client: Client,
|
|
6
|
+
prompt: string,
|
|
7
|
+
jsonSchema?: object,
|
|
8
|
+
): Promise<string> {
|
|
9
|
+
const url = new URL("v1/llm/generate", client.getHttpBase());
|
|
10
|
+
const body: Record<string, unknown> = { prompt };
|
|
11
|
+
if (jsonSchema !== undefined) body.json_schema = jsonSchema;
|
|
12
|
+
|
|
13
|
+
const response = await fetchOrThrow(url, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { ...(await client.headers()), "Content-Type": "application/json" },
|
|
16
|
+
body: JSON.stringify(body),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return ((await response.json()) as { text: string }).text;
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,3 +4,5 @@ export { Say, Field } from "./action-item.ts";
|
|
|
4
4
|
export { Logger, getConsoleLogger, getDefaultLogger } from "./logging.ts";
|
|
5
5
|
export { Agent, CallInfo } from "./agent.ts";
|
|
6
6
|
export { Call } from "./call.ts";
|
|
7
|
+
export { TestSession } from "./testing/session.ts";
|
|
8
|
+
export { MockCall } from "./testing/mocks.ts";
|
package/src/logging.ts
CHANGED
|
@@ -37,26 +37,34 @@ function shouldLog(messageLevel: LogLevel, loggerLevel: LogLevel) {
|
|
|
37
37
|
|
|
38
38
|
function noop(format: string, ...args: unknown[]) {}
|
|
39
39
|
|
|
40
|
+
type ConsoleLevel = "debug" | "info" | "warn" | "error";
|
|
41
|
+
|
|
40
42
|
function makeColoredMethod(
|
|
41
|
-
|
|
42
|
-
level: LogLevel,
|
|
43
|
+
level: ConsoleLevel,
|
|
43
44
|
useColor: boolean,
|
|
44
45
|
): (format: string, ...args: unknown[]) => void {
|
|
45
|
-
if (!useColor) return
|
|
46
|
-
return (format: string, ...args: unknown[]) =>
|
|
47
|
-
|
|
46
|
+
if (!useColor) return (format: string, ...args: unknown[]) => console[level](format, ...args);
|
|
47
|
+
return (format: string, ...args: unknown[]) => {
|
|
48
|
+
const now = new Date();
|
|
49
|
+
const time = now.toLocaleTimeString("en-US", {
|
|
50
|
+
hour12: false,
|
|
51
|
+
hour: "2-digit",
|
|
52
|
+
minute: "2-digit",
|
|
53
|
+
second: "2-digit",
|
|
54
|
+
});
|
|
55
|
+
console[level](
|
|
56
|
+
`${LEVEL_COLORS[level]}[${level.toLocaleUpperCase().padEnd(5)} ${time}] ${format}${ANSI_RESET}`,
|
|
57
|
+
...args,
|
|
58
|
+
);
|
|
59
|
+
};
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
export function getConsoleLogger(loggerLevel: LogLevel, useColor = false): Logger {
|
|
51
63
|
return {
|
|
52
|
-
debug: shouldLog("debug", loggerLevel)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
warn: shouldLog("warn", loggerLevel) ? makeColoredMethod(console.warn, "warn", useColor) : noop,
|
|
57
|
-
error: shouldLog("error", loggerLevel)
|
|
58
|
-
? makeColoredMethod(console.error, "error", useColor)
|
|
59
|
-
: noop,
|
|
64
|
+
debug: shouldLog("debug", loggerLevel) ? makeColoredMethod("debug", useColor) : noop,
|
|
65
|
+
info: shouldLog("info", loggerLevel) ? makeColoredMethod("info", useColor) : noop,
|
|
66
|
+
warn: shouldLog("warn", loggerLevel) ? makeColoredMethod("warn", useColor) : noop,
|
|
67
|
+
error: shouldLog("error", loggerLevel) ? makeColoredMethod("error", useColor) : noop,
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
70
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
export const PSTNCallInfo = z.object({
|
|
4
|
+
call_type: z.literal("pstn"),
|
|
5
|
+
from_number: z.string().nullable(),
|
|
6
|
+
to_number: z.string(),
|
|
7
|
+
caller_id: z.string().nullable(),
|
|
8
|
+
});
|
|
9
|
+
export type PSTNCallInfo = z.infer<typeof PSTNCallInfo>;
|
|
10
|
+
|
|
11
|
+
export const WebRTCCallInfo = z.object({
|
|
12
|
+
call_type: z.literal("webrtc"),
|
|
13
|
+
webrtc_code: z.string(),
|
|
14
|
+
});
|
|
15
|
+
export type WebRTCCallInfo = z.infer<typeof WebRTCCallInfo>;
|
|
16
|
+
|
|
17
|
+
export const SIPCallInfo = z.object({
|
|
18
|
+
call_type: z.literal("sip"),
|
|
19
|
+
from_aor: z.string(),
|
|
20
|
+
sip_code: z.string().optional(),
|
|
21
|
+
sip_headers: z.record(z.string(), z.string()).default({}),
|
|
22
|
+
});
|
|
23
|
+
export type SIPCallInfo = z.infer<typeof SIPCallInfo>;
|
|
24
|
+
|
|
25
|
+
export const CallInfo = z.discriminatedUnion("call_type", [
|
|
26
|
+
PSTNCallInfo,
|
|
27
|
+
WebRTCCallInfo,
|
|
28
|
+
SIPCallInfo,
|
|
29
|
+
]);
|
|
30
|
+
export type CallInfo = z.infer<typeof CallInfo>;
|