@guava-ai/guava-sdk 0.27.0 → 0.28.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.
Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/bin/example-runner.ts +67 -26
  3. package/dist/bin/example-runner.d.ts +1 -25
  4. package/dist/bin/example-runner.js +64 -24
  5. package/dist/bin/example-runner.js.map +1 -1
  6. package/dist/examples/help-desk.d.ts +1 -1
  7. package/dist/examples/help-desk.js +40 -29
  8. package/dist/examples/help-desk.js.map +1 -1
  9. package/dist/examples/multiple-agents.d.ts +1 -1
  10. package/dist/examples/multiple-agents.js +1 -1
  11. package/dist/examples/multiple-agents.js.map +1 -1
  12. package/dist/examples/polling-campaign.d.ts +1 -1
  13. package/dist/examples/polling-campaign.js +9 -7
  14. package/dist/examples/polling-campaign.js.map +1 -1
  15. package/dist/examples/property-insurance.d.ts +1 -1
  16. package/dist/examples/property-insurance.js +34 -23
  17. package/dist/examples/property-insurance.js.map +1 -1
  18. package/dist/examples/restaurant-waitlist.d.ts +1 -1
  19. package/dist/examples/restaurant-waitlist.js +34 -23
  20. package/dist/examples/restaurant-waitlist.js.map +1 -1
  21. package/dist/examples/scheduling-outbound.d.ts +1 -1
  22. package/dist/examples/scheduling-outbound.js +24 -14
  23. package/dist/examples/scheduling-outbound.js.map +1 -1
  24. package/dist/src/action-item.d.ts +8 -4
  25. package/dist/src/action-item.js +22 -3
  26. package/dist/src/action-item.js.map +1 -1
  27. package/dist/src/agent.d.ts +2 -2
  28. package/dist/src/agent.js +5 -9
  29. package/dist/src/agent.js.map +1 -1
  30. package/dist/src/call.d.ts +2 -0
  31. package/dist/src/call.js +7 -0
  32. package/dist/src/call.js.map +1 -1
  33. package/dist/src/campaigns.d.ts +79 -0
  34. package/dist/src/campaigns.js +195 -0
  35. package/dist/src/campaigns.js.map +1 -0
  36. package/dist/src/client.d.ts +10 -0
  37. package/dist/src/client.js +28 -0
  38. package/dist/src/client.js.map +1 -1
  39. package/dist/src/commands.d.ts +6 -3
  40. package/dist/src/index.d.ts +2 -0
  41. package/dist/src/index.js +3 -1
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/version.d.ts +1 -1
  44. package/dist/src/version.js +1 -1
  45. package/examples/help-desk.ts +51 -26
  46. package/examples/multiple-agents.ts +1 -1
  47. package/examples/polling-campaign.ts +12 -7
  48. package/examples/property-insurance.ts +42 -22
  49. package/examples/restaurant-waitlist.ts +42 -22
  50. package/examples/scheduling-outbound.ts +26 -15
  51. package/package.json +2 -1
  52. package/src/action-item.ts +28 -5
  53. package/src/agent.ts +17 -13
  54. package/src/call.ts +9 -0
  55. package/src/campaigns.ts +156 -0
  56. package/src/client.ts +35 -0
  57. package/src/index.ts +8 -0
  58. package/src/version.ts +1 -1
@@ -1,6 +1,8 @@
1
+ import { Command } from "commander";
1
2
  import * as guava from "@guava-ai/guava-sdk";
2
3
  import { DocumentQA } from "@guava-ai/guava-sdk/helpers";
3
4
  import { PROPERTY_INSURANCE_POLICY } from "@guava-ai/guava-sdk/example-data";
5
+ import { getAgentNumber } from "@guava-ai/guava-sdk/example-utils";
4
6
 
5
7
  const agent = new guava.Agent({
6
8
  organization: "Harper Valley Property Insurance",
@@ -21,26 +23,44 @@ agent.onQuestion(async (call: guava.Call, question: string) => {
21
23
  return await documentQA.ask(question);
22
24
  });
23
25
 
24
- export async function run(args: string[]) {
25
- if (args.includes("--webrtc")) {
26
- await agent.listenWebrtc();
27
- } else if (args.includes("--phone")) {
28
- agent.listenPhone(process.env.GUAVA_AGENT_NUMBER!);
29
- } else if (args.includes("--sip")) {
30
- const sipCode = args[args.indexOf("--sip") + 1];
31
- if (!sipCode) {
32
- console.error("Error: --sip requires a SIP code argument.");
33
- process.exit(1);
34
- }
35
- await agent.listenSip(sipCode);
36
- } else if (args.includes("--local")) {
37
- await agent.callLocal();
38
- } else if (args.includes("--chat")) {
39
- await agent.chat();
40
- } else {
41
- console.error(
42
- "Usage: guava-example property-insurance --phone | --webrtc | --sip | --local | --chat",
43
- );
44
- process.exit(1);
45
- }
26
+ export async function run(prog: string, args: string[]) {
27
+ const program = new Command().name(prog).showHelpAfterError();
28
+
29
+ // Every Agent can be attached to one of many different channels.
30
+ program
31
+ .command("phone [number]")
32
+ .description("Listen for phone calls.")
33
+ .action(async (number?: string) => {
34
+ await agent.listenPhone(number ?? (await getAgentNumber()));
35
+ });
36
+
37
+ program
38
+ .command("webrtc [code]")
39
+ .description("Listen on a WebRTC code.")
40
+ .action(async (code?: string) => {
41
+ await agent.listenWebrtc(code);
42
+ });
43
+
44
+ program
45
+ .command("sip <code>")
46
+ .description("Listen on a SIP code 'guavasip-...'.")
47
+ .action(async (code: string) => {
48
+ await agent.listenSip(code);
49
+ });
50
+
51
+ program
52
+ .command("local")
53
+ .description("Start a local call.")
54
+ .action(async () => {
55
+ await agent.callLocal();
56
+ });
57
+
58
+ program
59
+ .command("chat")
60
+ .description("Start an interactive terminal chat.")
61
+ .action(async () => {
62
+ await agent.chat();
63
+ });
64
+
65
+ await program.parseAsync(args, { from: "user" });
46
66
  }
@@ -1,5 +1,7 @@
1
+ import { Command } from "commander";
1
2
  import * as guava from "@guava-ai/guava-sdk";
2
3
  import { getDefaultLogger } from "@guava-ai/guava-sdk";
4
+ import { getAgentNumber } from "@guava-ai/guava-sdk/example-utils";
3
5
 
4
6
  const logger = getDefaultLogger();
5
7
 
@@ -42,26 +44,44 @@ agent.onTaskComplete("waitlist", async (call: guava.Call) => {
42
44
  await call.hangup("Thank the caller and let them know we'll text when their table is ready.");
43
45
  });
44
46
 
45
- export async function run(args: string[]) {
46
- if (args.includes("--webrtc")) {
47
- await agent.listenWebrtc();
48
- } else if (args.includes("--phone")) {
49
- agent.listenPhone(process.env.GUAVA_AGENT_NUMBER!);
50
- } else if (args.includes("--sip")) {
51
- const sipCode = args[args.indexOf("--sip") + 1];
52
- if (!sipCode) {
53
- console.error("Error: --sip requires a SIP code argument.");
54
- process.exit(1);
55
- }
56
- await agent.listenSip(sipCode);
57
- } else if (args.includes("--local")) {
58
- await agent.callLocal();
59
- } else if (args.includes("--chat")) {
60
- await agent.chat();
61
- } else {
62
- console.error(
63
- "Usage: guava-example restaurant-waitlist --phone | --webrtc | --sip | --local | --chat",
64
- );
65
- process.exit(1);
66
- }
47
+ export async function run(prog: string, args: string[]) {
48
+ const program = new Command().name(prog).showHelpAfterError();
49
+
50
+ // Every Agent can be attached to one of many different channels.
51
+ program
52
+ .command("phone [number]")
53
+ .description("Listen for phone calls.")
54
+ .action(async (number?: string) => {
55
+ await agent.listenPhone(number ?? (await getAgentNumber()));
56
+ });
57
+
58
+ program
59
+ .command("webrtc [code]")
60
+ .description("Listen on a WebRTC code.")
61
+ .action(async (code?: string) => {
62
+ await agent.listenWebrtc(code);
63
+ });
64
+
65
+ program
66
+ .command("sip <code>")
67
+ .description("Listen on a SIP code 'guavasip-...'.")
68
+ .action(async (code: string) => {
69
+ await agent.listenSip(code);
70
+ });
71
+
72
+ program
73
+ .command("local")
74
+ .description("Start a local call.")
75
+ .action(async () => {
76
+ await agent.callLocal();
77
+ });
78
+
79
+ program
80
+ .command("chat")
81
+ .description("Start an interactive terminal chat.")
82
+ .action(async () => {
83
+ await agent.chat();
84
+ });
85
+
86
+ await program.parseAsync(args, { from: "user" });
67
87
  }
@@ -1,3 +1,4 @@
1
+ import { Command } from "commander";
1
2
  import * as guava from "@guava-ai/guava-sdk";
2
3
  import { DatetimeFilter } from "@guava-ai/guava-sdk/helpers";
3
4
  import { mockAppointmentsForFuture } from "@guava-ai/guava-sdk/example-data";
@@ -42,22 +43,32 @@ agent.onTaskComplete("schedule_appointment", async (call) => {
42
43
  await call.hangup("Thank them for their time and hang up the call.");
43
44
  });
44
45
 
45
- export async function run(args: string[]) {
46
- if (args.includes("--chat")) {
47
- const patientName = args[args.indexOf("--chat") + 1] ?? "Benjamin Buttons";
48
- await agent.chat({ patientName });
49
- return;
50
- }
46
+ export async function run(prog: string, args: string[]) {
47
+ const program = new Command().name(prog).showHelpAfterError();
51
48
 
52
- const [toNumber, patientName = "Benjamin Buttons"] = args;
49
+ program
50
+ .command("phone <number>")
51
+ .description("Call a phone number.")
52
+ .option("-n, --name <name>", "Name of the patient.", "Benjamin Buttons")
53
+ .action(async (number: string, options: { name: string }) => {
54
+ await agent.callPhone(await getAgentNumber(), number, { patientName: options.name });
55
+ });
53
56
 
54
- if (!toNumber) {
55
- console.error("Usage: guava-example scheduling-outbound <phone> [name]");
56
- console.error(" guava-example scheduling-outbound --chat [name]");
57
- process.exit(1);
58
- }
57
+ program
58
+ .command("local")
59
+ .description("Start a local call (for testing).")
60
+ .option("-n, --name <name>", "Name of the patient.", "Benjamin Buttons")
61
+ .action(async (options: { name: string }) => {
62
+ await agent.callLocal({ patientName: options.name });
63
+ });
64
+
65
+ program
66
+ .command("chat")
67
+ .description("Start a local chat session (for testing).")
68
+ .option("-n, --name <name>", "Name of the patient.", "Benjamin Buttons")
69
+ .action(async (options: { name: string }) => {
70
+ await agent.chat({ patientName: options.name });
71
+ });
59
72
 
60
- agent.callPhone(await getAgentNumber(), toNumber, {
61
- patientName: patientName,
62
- });
73
+ await program.parseAsync(args, { from: "user" });
63
74
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@guava-ai/guava-sdk",
3
3
  "description": "The official TypeScript SDK for building Guava voice agents.",
4
4
  "homepage": "https://docs.goguava.ai/typescript-sdk/",
5
- "version": "0.27.0",
5
+ "version": "0.28.0",
6
6
  "type": "commonjs",
7
7
  "main": "dist/src/index.js",
8
8
  "types": "dist/src/index.d.ts",
@@ -46,6 +46,7 @@
46
46
  "guava-example": "dist/bin/example-runner.js"
47
47
  },
48
48
  "dependencies": {
49
+ "commander": "^15.0.0",
49
50
  "openai": "^6.16.0",
50
51
  "ws": "^8.19.0",
51
52
  "zod": "^4.3.5"
@@ -1,9 +1,17 @@
1
1
  import * as z from "zod";
2
2
 
3
3
  export const FieldItemType = z.union(
4
- (["text", "date", "integer", "digit_sequence", "multiple_choice", "calendar_slot"] as const).map(
5
- (val) => z.literal(val),
6
- ),
4
+ (
5
+ [
6
+ "text",
7
+ "date",
8
+ "integer",
9
+ "digit_sequence",
10
+ "multiple_choice",
11
+ "calendar_slot",
12
+ "cvv",
13
+ ] as const
14
+ ).map((val) => z.literal(val)),
7
15
  );
8
16
  export type FieldItemType = z.input<typeof FieldItemType>;
9
17
 
@@ -20,15 +28,27 @@ export const FieldItem = z
20
28
  choices: z.array(z.string()).default([]),
21
29
  choiceGenerator: z.custom<ChoiceGenerator>((val) => typeof val === "function").optional(),
22
30
  searchable: z.boolean().default(false),
31
+ sensitive: z.boolean().default(false),
23
32
  })
24
- .refine((field) => {
33
+ .superRefine((field, ctx) => {
34
+ const hasChoices = field.choices.length > 0 || field.choiceGenerator !== undefined;
35
+ if (
36
+ hasChoices &&
37
+ field.field_type !== "multiple_choice" &&
38
+ field.field_type !== "calendar_slot"
39
+ ) {
40
+ ctx.addIssue({
41
+ code: "custom",
42
+ message: `Field type ${field.field_type} does not support choices attribute.`,
43
+ });
44
+ }
45
+
25
46
  if (field.field_type === "multiple_choice" && field.choices.length > 10) {
26
47
  process.emitWarning(
27
48
  "Performance degrades with large number of choices for multiple choice field.",
28
49
  "ACTION_ITEM",
29
50
  );
30
51
  }
31
- return true;
32
52
  });
33
53
  export type FieldItem = z.input<typeof FieldItem>;
34
54
 
@@ -41,6 +61,7 @@ export const SerializableFieldItem = z.object({
41
61
  required: z.boolean().default(true),
42
62
  choices: z.array(z.string()).default([]),
43
63
  is_search_field: z.boolean().default(false),
64
+ sensitive: z.boolean().default(false),
44
65
  });
45
66
  export type SerializableFieldItem = z.input<typeof SerializableFieldItem>;
46
67
 
@@ -70,6 +91,7 @@ export function Field(options: {
70
91
  choices?: string[];
71
92
  choiceGenerator?: ChoiceGenerator;
72
93
  searchable?: boolean;
94
+ sensitive?: boolean;
73
95
  }): FieldItem {
74
96
  return FieldItem.parse({
75
97
  item_type: "field",
@@ -81,6 +103,7 @@ export function Field(options: {
81
103
  choices: options.choices,
82
104
  choiceGenerator: options.choiceGenerator,
83
105
  searchable: options.searchable,
106
+ sensitive: options.sensitive,
84
107
  });
85
108
  }
86
109
 
package/src/agent.ts CHANGED
@@ -276,12 +276,14 @@ export class Agent {
276
276
  return this._listenInbound(healthCtx, { sip_code: sipCode });
277
277
  }
278
278
 
279
- async callLocal(): Promise<void> {
279
+ async callLocal(variables: Record<string, any> = {}): Promise<void> {
280
280
  const webrtcCode = await this._client.createWebrtcAgent(300);
281
281
  // No health-server for call_local.
282
- this._listenInbound(new HealthContext(), { webrtc_code: webrtcCode }).catch((err) => {
283
- this._logger.error("Listen loop error: %s", err);
284
- });
282
+ this._listenInbound(new HealthContext(), { webrtc_code: webrtcCode }, variables).catch(
283
+ (err) => {
284
+ this._logger.error("Listen loop error: %s", err);
285
+ },
286
+ );
285
287
  await runWebrtcHelper(webrtcCode, getBaseUrl());
286
288
  }
287
289
 
@@ -574,7 +576,11 @@ export class Agent {
574
576
  }
575
577
  }
576
578
 
577
- async _listenInbound(healthCtx: HealthContext, conn: InboundConnection): Promise<void> {
579
+ async _listenInbound(
580
+ healthCtx: HealthContext,
581
+ conn: InboundConnection,
582
+ initialVariables: Record<string, any> = {},
583
+ ): Promise<void> {
578
584
  const url = new URL("v2/listen-inbound", this._client.getWebsocketBase());
579
585
  if ("agent_number" in conn) {
580
586
  url.searchParams.set("phone_number", conn.agent_number);
@@ -632,9 +638,11 @@ export class Agent {
632
638
  case "assign-call": {
633
639
  const { call_id, call_info } = msg;
634
640
  this._logger.info("Received call (session ID: %s)", call_id);
635
- this._handleAssignedCall(call_id, call_info, socket).catch((err) => {
636
- this._logger.error("Error handling assigned call %s: %s", call_id, err);
637
- });
641
+ this._handleAssignedCall(call_id, call_info, socket, initialVariables).catch(
642
+ (err) => {
643
+ this._logger.error("Error handling assigned call %s: %s", call_id, err);
644
+ },
645
+ );
638
646
  break;
639
647
  }
640
648
  }
@@ -892,11 +900,7 @@ Choose "speak" and provide your next utterance, or choose "hangup" if the conver
892
900
 
893
901
  async _serveCampaign(healthCtx: HealthContext, campaignCode: string): Promise<void> {
894
902
  try {
895
- const campaignUrl = new URL(`v1/campaigns/${campaignCode}`, this._client.getHttpBase());
896
- const campaignResponse = await fetchOrThrow(campaignUrl, {
897
- headers: await this._client.headers(),
898
- });
899
- const campaign = (await campaignResponse.json()) as { id: string; name: string };
903
+ const campaign = await this._client.getCampaign(campaignCode);
900
904
 
901
905
  const wsUrl = new URL(`v1/serve-campaign/${campaign.id}`, this._client.getWebsocketBase());
902
906
  this._logger.info("Connecting to campaign '%s' (id: %s).", campaign.name, campaign.id);
package/src/call.ts CHANGED
@@ -404,4 +404,13 @@ TASK COMPLETION REQUIREMENTS:
404
404
  async getVariable(variableName: string) {
405
405
  return this._variables[variableName] ?? null;
406
406
  }
407
+
408
+ // Aliases for setVariable / getVariable, matching the Python SDK.
409
+ async setVar(variableName: string, variableValue: any) {
410
+ return this.setVariable(variableName, variableValue);
411
+ }
412
+
413
+ async getVar(variableName: string) {
414
+ return this.getVariable(variableName);
415
+ }
407
416
  }
@@ -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/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.27.0";
1
+ export const SDK_VERSION = "0.28.0";