@guava-ai/guava-sdk 0.27.1 → 0.29.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/help-desk.js +6 -6
- package/dist/examples/help-desk.js.map +1 -1
- package/dist/src/action-item.d.ts +8 -4
- package/dist/src/action-item.js +22 -3
- package/dist/src/action-item.js.map +1 -1
- package/dist/src/agent.js +1 -5
- package/dist/src/agent.js.map +1 -1
- package/dist/src/call.d.ts +2 -0
- package/dist/src/call.js +7 -0
- package/dist/src/call.js.map +1 -1
- package/dist/src/campaigns.d.ts +79 -0
- package/dist/src/campaigns.js +195 -0
- package/dist/src/campaigns.js.map +1 -0
- package/dist/src/client.d.ts +10 -0
- package/dist/src/client.js +28 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/commands.d.ts +6 -3
- package/dist/src/health.d.ts +4 -1
- package/dist/src/health.js +14 -2
- package/dist/src/health.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/examples/help-desk.ts +9 -6
- package/package.json +1 -1
- package/src/action-item.ts +28 -5
- package/src/agent.ts +1 -5
- package/src/call.ts +9 -0
- package/src/campaigns.ts +156 -0
- package/src/client.ts +35 -0
- package/src/health.ts +17 -3
- package/src/index.ts +8 -0
- package/src/version.ts +1 -1
package/src/campaigns.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { inspect } from "node:util";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { fetchOrThrow } from "./utils.ts";
|
|
4
|
+
import { telemetryClient } from "./telemetry.ts";
|
|
5
|
+
import type { Client } from "./client.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Modality used for campaign agentic outreach.
|
|
9
|
+
*
|
|
10
|
+
* Currently only SMS is available.
|
|
11
|
+
*/
|
|
12
|
+
export const OutreachModality = z.enum(["sms"]);
|
|
13
|
+
export type OutreachModality = z.infer<typeof OutreachModality>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A contact to upload to a campaign.
|
|
17
|
+
*/
|
|
18
|
+
export const Contact = z.object({
|
|
19
|
+
/** The contact's phone number in E.164 format (e.g. `"+14155550123"`). */
|
|
20
|
+
phoneNumber: z.e164(),
|
|
21
|
+
/**
|
|
22
|
+
* Arbitrary per-contact data. This is delivered to the agent when the call is
|
|
23
|
+
* initiated and can be read during the call (e.g. via `call.getVariable`).
|
|
24
|
+
*/
|
|
25
|
+
data: z.record(z.string(), z.unknown()).default({}),
|
|
26
|
+
/**
|
|
27
|
+
* Modalities used to reach this contact. When omitted, falls back to the
|
|
28
|
+
* `outreachModalities` passed to {@link Campaign.uploadContacts}.
|
|
29
|
+
*/
|
|
30
|
+
outreachModalities: z.array(OutreachModality).optional(),
|
|
31
|
+
});
|
|
32
|
+
export type Contact = z.input<typeof Contact>;
|
|
33
|
+
|
|
34
|
+
/** Result of uploading contacts to a campaign. */
|
|
35
|
+
export interface UploadContactsResult {
|
|
36
|
+
/** The number of contacts that were inserted. */
|
|
37
|
+
created: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Status of a campaign. */
|
|
41
|
+
export interface CampaignStatus {
|
|
42
|
+
/**
|
|
43
|
+
* Mapping of contact status (e.g. `"trying"`, `"failed"`, `"completed"`,
|
|
44
|
+
* `"partially_completed"`, `"do_not_call"`) to the number of contacts in that
|
|
45
|
+
* status. Only statuses with at least one contact are included.
|
|
46
|
+
*/
|
|
47
|
+
statusCounts: Record<string, number>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Options for {@link Campaign.uploadContacts}. */
|
|
51
|
+
export interface UploadContactsOptions {
|
|
52
|
+
/** Whether to insert contacts whose phone number is already in the campaign. */
|
|
53
|
+
allowDuplicates?: boolean;
|
|
54
|
+
/** Confirms you have consent to contact these numbers. Required by the server. */
|
|
55
|
+
acceptedTermsOfService?: boolean;
|
|
56
|
+
/** Default modalities applied to any contact that doesn't specify its own. */
|
|
57
|
+
outreachModalities?: OutreachModality[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Wire-format schemas for parsing responses (snake_case, as returned by the API).
|
|
61
|
+
const UploadContactsResultWire = z.object({ created: z.number().int() });
|
|
62
|
+
const CampaignStatusWire = z.object({
|
|
63
|
+
status_counts: z.record(z.string(), z.number()).default({}),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A handle to an existing Guava campaign.
|
|
68
|
+
*
|
|
69
|
+
* Obtain instances via {@link Client.getCampaign} or {@link Client.listCampaigns};
|
|
70
|
+
* do not construct directly. Campaigns are created via the Guava dashboard or CLI.
|
|
71
|
+
*/
|
|
72
|
+
@telemetryClient.trackClass()
|
|
73
|
+
export class Campaign {
|
|
74
|
+
private _client: Client;
|
|
75
|
+
private _id: string;
|
|
76
|
+
private _code: string;
|
|
77
|
+
// Snapshot of the name at the time this handle was fetched. Used for
|
|
78
|
+
// display/logging only; identity is keyed off id/code, never name.
|
|
79
|
+
private _name: string;
|
|
80
|
+
|
|
81
|
+
constructor(client: Client, id: string, code: string, name: string) {
|
|
82
|
+
this._client = client;
|
|
83
|
+
this._id = id;
|
|
84
|
+
this._code = code;
|
|
85
|
+
this._name = name;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get id(): string {
|
|
89
|
+
return this._id;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get name(): string {
|
|
93
|
+
return this._name;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Upload contacts to this campaign.
|
|
98
|
+
*
|
|
99
|
+
* @param contacts - The contacts to upload.
|
|
100
|
+
* @param options.allowDuplicates - Insert contacts whose number already
|
|
101
|
+
* exists in the campaign. Defaults to `false`.
|
|
102
|
+
* @param options.acceptedTermsOfService - Confirms you have consent to
|
|
103
|
+
* contact these numbers. Defaults to `false`.
|
|
104
|
+
* @param options.outreachModalities - Default modalities for any contact that
|
|
105
|
+
* doesn't set its own {@link Contact.outreachModalities}.
|
|
106
|
+
* @returns The number of contacts that were inserted.
|
|
107
|
+
*/
|
|
108
|
+
async uploadContacts(
|
|
109
|
+
contacts: Contact[],
|
|
110
|
+
options: UploadContactsOptions = {},
|
|
111
|
+
): Promise<UploadContactsResult> {
|
|
112
|
+
const { allowDuplicates = false, acceptedTermsOfService = false, outreachModalities } = options;
|
|
113
|
+
|
|
114
|
+
const wireContacts = contacts.map((contact) => {
|
|
115
|
+
const parsed = Contact.parse(contact);
|
|
116
|
+
return {
|
|
117
|
+
phone_number: parsed.phoneNumber,
|
|
118
|
+
data: parsed.data,
|
|
119
|
+
// Per-contact modalities take precedence; fall back to the method-level
|
|
120
|
+
// default, otherwise leave unset (server applies the campaign default).
|
|
121
|
+
outreach_modalities: parsed.outreachModalities ?? outreachModalities ?? null,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const url = new URL(`v2/campaigns/${this._code}/contacts`, this._client.getHttpBase());
|
|
126
|
+
url.searchParams.set("allow_duplicates", String(allowDuplicates));
|
|
127
|
+
url.searchParams.set("accepted_terms_of_service", String(acceptedTermsOfService));
|
|
128
|
+
|
|
129
|
+
const response = await fetchOrThrow(url, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { ...(await this._client.headers()), "Content-Type": "application/json" },
|
|
132
|
+
body: JSON.stringify({ contacts: wireContacts }),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const { created } = UploadContactsResultWire.parse(await response.json());
|
|
136
|
+
return { created };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Fetch the current status of this campaign. */
|
|
140
|
+
async getStatus(): Promise<CampaignStatus> {
|
|
141
|
+
const url = new URL(`v1/campaigns/${this._id}/status`, this._client.getHttpBase());
|
|
142
|
+
const response = await fetchOrThrow(url, { headers: await this._client.headers() });
|
|
143
|
+
const { status_counts } = CampaignStatusWire.parse(await response.json());
|
|
144
|
+
return { statusCounts: status_counts };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Permanently delete this campaign. */
|
|
148
|
+
async delete(): Promise<void> {
|
|
149
|
+
const url = new URL(`v1/campaigns/${this._id}`, this._client.getHttpBase());
|
|
150
|
+
await fetchOrThrow(url, { method: "DELETE", headers: await this._client.headers() });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
[inspect.custom](): string {
|
|
154
|
+
return `Campaign { name: '${this._name}', code: '${this._code}' }`;
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import { getBaseUrl, fetchOrThrow, sleep } from "./utils.ts";
|
|
6
6
|
import { SmsMessage } from "./sms.ts";
|
|
7
|
+
import { Campaign } from "./campaigns.ts";
|
|
7
8
|
import { telemetryClient } from "./telemetry.ts";
|
|
8
9
|
import {
|
|
9
10
|
type AuthStrategy,
|
|
@@ -156,6 +157,40 @@ export class Client {
|
|
|
156
157
|
return body.webrtc_code;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
async createSipAgent(): Promise<string> {
|
|
161
|
+
const url = new URL("v1/sip-agents", this.getHttpBase());
|
|
162
|
+
const response = await fetchOrThrow(url, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: await this.headers(),
|
|
165
|
+
});
|
|
166
|
+
const body = (await response.json()) as { sip_code: string };
|
|
167
|
+
return body.sip_code;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Fetch an existing campaign by its code.
|
|
172
|
+
*
|
|
173
|
+
* @param campaignCode - The campaign code (e.g. `gcmp-...`).
|
|
174
|
+
*/
|
|
175
|
+
async getCampaign(campaignCode: string): Promise<Campaign> {
|
|
176
|
+
const url = new URL(`v1/campaigns/${campaignCode}`, this.getHttpBase());
|
|
177
|
+
const response = await fetchOrThrow(url, { headers: await this.headers() });
|
|
178
|
+
const body = (await response.json()) as { id: string; campaign_code: string; name: string };
|
|
179
|
+
return new Campaign(this, body.id, body.campaign_code, body.name);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** List all campaigns in your organization. */
|
|
183
|
+
async listCampaigns(): Promise<Campaign[]> {
|
|
184
|
+
const url = new URL("v1/campaigns", this.getHttpBase());
|
|
185
|
+
const response = await fetchOrThrow(url, { headers: await this.headers() });
|
|
186
|
+
const items = (await response.json()) as {
|
|
187
|
+
id: string;
|
|
188
|
+
campaign_code: string;
|
|
189
|
+
name: string;
|
|
190
|
+
}[];
|
|
191
|
+
return items.map((c) => new Campaign(this, c.id, c.campaign_code, c.name));
|
|
192
|
+
}
|
|
193
|
+
|
|
159
194
|
async listNumbers(): Promise<{ phoneNumber: string }[]> {
|
|
160
195
|
const url = new URL("v1/phone-numbers", this.getHttpBase());
|
|
161
196
|
const response = await fetchOrThrow(url, { headers: await this.headers() });
|
package/src/health.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Server, createServer } from "node:http";
|
|
2
2
|
import { getDefaultLogger, type Logger } from "./logging.ts";
|
|
3
3
|
|
|
4
|
-
export type HealthState = "starting" | "
|
|
4
|
+
export type HealthState = "starting" | "running" | "draining" | "stopped";
|
|
5
5
|
|
|
6
6
|
export class HealthContext {
|
|
7
7
|
private _state: HealthState = "starting";
|
|
@@ -11,7 +11,11 @@ export class HealthContext {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
ready(): void {
|
|
14
|
-
this.setState("
|
|
14
|
+
this.setState("running");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
draining(): void {
|
|
18
|
+
this.setState("draining");
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
stopped(): void {
|
|
@@ -19,7 +23,11 @@ export class HealthContext {
|
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
isLive(): boolean {
|
|
22
|
-
return this._state
|
|
26
|
+
return this._state !== "stopped";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
isReady(): boolean {
|
|
30
|
+
return this._state === "running";
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -35,6 +43,10 @@ export class MultiHealthContext {
|
|
|
35
43
|
isLive(): boolean {
|
|
36
44
|
return this._ctxs.length > 0 && this._ctxs.every((ctx) => ctx.isLive());
|
|
37
45
|
}
|
|
46
|
+
|
|
47
|
+
isReady(): boolean {
|
|
48
|
+
return this._ctxs.length > 0 && this._ctxs.every((ctx) => ctx.isReady());
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
export interface HealthServerOptions {
|
|
@@ -64,6 +76,8 @@ export class HealthServer implements AsyncDisposable {
|
|
|
64
76
|
const server = createServer((req, res) => {
|
|
65
77
|
if (req.method === "GET" && req.url === "/live") {
|
|
66
78
|
res.statusCode = healthCtx.isLive() ? 200 : 503;
|
|
79
|
+
} else if (req.method === "GET" && req.url === "/ready") {
|
|
80
|
+
res.statusCode = healthCtx.isReady() ? 200 : 503;
|
|
67
81
|
} else {
|
|
68
82
|
res.statusCode = 404;
|
|
69
83
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { Client, type InboundConnection } from "./client.ts";
|
|
2
2
|
export { Runner } from "./runner.ts";
|
|
3
3
|
export type { SmsMessage } from "./sms.ts";
|
|
4
|
+
export { Campaign } from "./campaigns.ts";
|
|
5
|
+
export type {
|
|
6
|
+
Contact,
|
|
7
|
+
CampaignStatus,
|
|
8
|
+
UploadContactsResult,
|
|
9
|
+
UploadContactsOptions,
|
|
10
|
+
OutreachModality,
|
|
11
|
+
} from "./campaigns.ts";
|
|
4
12
|
export { Say, Field } from "./action-item.ts";
|
|
5
13
|
export { Logger, getConsoleLogger, getDefaultLogger } from "./logging.ts";
|
|
6
14
|
export { Agent, CallInfo } from "./agent.ts";
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.
|
|
1
|
+
export const SDK_VERSION = "0.29.0";
|