@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.
Files changed (61) hide show
  1. package/README.md +2 -7
  2. package/bin/example-runner.js +16 -7
  3. package/dist/examples/credit-card-activation.js +94 -112
  4. package/dist/examples/credit-card-activation.js.map +1 -1
  5. package/dist/examples/property-insurance.js +12 -26
  6. package/dist/examples/property-insurance.js.map +1 -1
  7. package/dist/examples/scheduling-outbound.d.ts +1 -0
  8. package/dist/examples/scheduling-outbound.js +62 -0
  9. package/dist/examples/scheduling-outbound.js.map +1 -0
  10. package/dist/examples/thai-palace.js +61 -0
  11. package/dist/examples/thai-palace.js.map +1 -0
  12. package/dist/package.json +7 -5
  13. package/dist/src/action_item.d.ts +34 -12
  14. package/dist/src/action_item.js +34 -7
  15. package/dist/src/action_item.js.map +1 -1
  16. package/dist/src/call-controller.d.ts +137 -0
  17. package/dist/src/call-controller.js +433 -0
  18. package/dist/src/call-controller.js.map +1 -0
  19. package/dist/src/commands.d.ts +67 -27
  20. package/dist/src/commands.js +41 -27
  21. package/dist/src/commands.js.map +1 -1
  22. package/dist/src/events.d.ts +47 -30
  23. package/dist/src/events.js +42 -36
  24. package/dist/src/events.js.map +1 -1
  25. package/dist/src/example_data.d.ts +1 -0
  26. package/dist/src/example_data.js +33 -0
  27. package/dist/src/example_data.js.map +1 -1
  28. package/dist/src/helpers/openai.d.ts +12 -1
  29. package/dist/src/helpers/openai.js +168 -68
  30. package/dist/src/helpers/openai.js.map +1 -1
  31. package/dist/src/index.d.ts +6 -121
  32. package/dist/src/index.js +249 -483
  33. package/dist/src/index.js.map +1 -1
  34. package/dist/src/logging.d.ts +2 -1
  35. package/dist/src/logging.js +32 -7
  36. package/dist/src/logging.js.map +1 -1
  37. package/dist/src/telemetry.d.ts +23 -0
  38. package/dist/src/telemetry.js +98 -0
  39. package/dist/src/telemetry.js.map +1 -0
  40. package/dist/src/utils.d.ts +3 -0
  41. package/dist/src/utils.js +28 -0
  42. package/dist/src/utils.js.map +1 -0
  43. package/examples/biome.json +5 -0
  44. package/examples/credit-card-activation.ts +20 -26
  45. package/examples/property-insurance.ts +6 -16
  46. package/examples/scheduling-outbound.ts +80 -0
  47. package/examples/{thai_palace.ts → thai-palace.ts} +10 -13
  48. package/package.json +7 -5
  49. package/src/action_item.ts +53 -13
  50. package/src/call-controller.ts +451 -0
  51. package/src/commands.ts +58 -42
  52. package/src/events.ts +66 -51
  53. package/src/example_data.ts +42 -0
  54. package/src/helpers/openai.ts +73 -18
  55. package/src/index.ts +81 -403
  56. package/src/logging.ts +39 -7
  57. package/src/telemetry.ts +125 -0
  58. package/src/utils.ts +32 -0
  59. package/dist/examples/thai_palace.js +0 -79
  60. package/dist/examples/thai_palace.js.map +0 -1
  61. /package/dist/examples/{thai_palace.d.ts → thai-palace.d.ts} +0 -0
@@ -0,0 +1,125 @@
1
+ import { getDefaultLogger, type Logger } from "./logging.ts";
2
+ import { getBaseUrl, fetchOrThrow } from "./utils.ts";
3
+
4
+ const QUEUE_MAX_SIZE = 100;
5
+ const UPLOAD_INTERVAL_MS = 10_000;
6
+
7
+ const debugEnabled = ["yes", "true"].includes(
8
+ (process.env.GUAVA_DEBUG_TELEMETRY ?? "false").toLowerCase().trim(),
9
+ );
10
+ const logger: Logger = debugEnabled
11
+ ? getDefaultLogger()
12
+ : { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} };
13
+
14
+ export const TelemetryEvent = {
15
+ METHOD_CALL: "method-call",
16
+ EXCEPTION_RAISED: "exception-raised",
17
+ } as const;
18
+
19
+ export type TelemetryEvent = (typeof TelemetryEvent)[keyof typeof TelemetryEvent];
20
+
21
+ interface QueuedEvent {
22
+ timestamp_ms: number;
23
+ event_type: TelemetryEvent;
24
+ data: Record<string, unknown>;
25
+ }
26
+
27
+ export abstract class BaseTelemetryClient {
28
+ protected sdkHeaders: Record<string, string> = {};
29
+
30
+ abstract sendEvent(event: TelemetryEvent, data?: Record<string, unknown>): void;
31
+
32
+ setSdkHeaders(headers: Record<string, string>) {
33
+ this.sdkHeaders = headers;
34
+ }
35
+
36
+ trackClass(onlyExceptions = new Set<string>()) {
37
+ const client = this;
38
+ return <T extends abstract new (...args: unknown[]) => object>(
39
+ target: T,
40
+ _context: ClassDecoratorContext<T>,
41
+ ): T => {
42
+ // TODO: Wrap public methods.
43
+
44
+ // Wrap the constructor
45
+ const trackConstructorCalls = !onlyExceptions.has("constructor");
46
+ class Wrapped extends (target as unknown as new (...args: unknown[]) => object) {
47
+ constructor(...args: unknown[]) {
48
+ try {
49
+ if (trackConstructorCalls) {
50
+ client.sendEvent(TelemetryEvent.METHOD_CALL, {
51
+ function_name: `${target.name}.constructor`,
52
+ });
53
+ }
54
+ super(...args);
55
+ } catch (e) {
56
+ client.sendEvent(TelemetryEvent.EXCEPTION_RAISED, {
57
+ function_name: `${target.name}.constructor`,
58
+ exception: String(e),
59
+ });
60
+ throw e;
61
+ }
62
+ }
63
+ }
64
+ Object.defineProperty(Wrapped, "name", { value: target.name });
65
+ return Wrapped as unknown as T;
66
+ };
67
+ }
68
+ }
69
+
70
+ export class TelemetryClient extends BaseTelemetryClient {
71
+ private queue: QueuedEvent[] = [];
72
+ private timer: ReturnType<typeof setInterval>;
73
+ private readonly baseUrl: string;
74
+
75
+ constructor() {
76
+ super();
77
+ this.baseUrl = getBaseUrl();
78
+ this.timer = setInterval(() => {
79
+ void this.uploadEvents();
80
+ }, UPLOAD_INTERVAL_MS);
81
+ // Don't prevent the process from exiting naturally
82
+ this.timer.unref();
83
+
84
+ // Trigger an immediate upload on uncaught exception.
85
+ process.on("uncaughtExceptionMonitor", async () => {
86
+ await this.uploadEvents();
87
+ });
88
+ }
89
+
90
+ sendEvent(event: TelemetryEvent, data: Record<string, unknown> = {}) {
91
+ logger.debug(`Sending telemetry event ${event}, ${JSON.stringify(data)}`);
92
+ if (this.queue.length >= QUEUE_MAX_SIZE) return;
93
+ this.queue.push({ timestamp_ms: Date.now(), event_type: event, data });
94
+ }
95
+
96
+ private async uploadEvents() {
97
+ const payload = this.queue.splice(0);
98
+ if (!payload.length) {
99
+ logger.debug("No events to upload.");
100
+ return;
101
+ }
102
+ logger.debug(`Uploading ${payload.length} telemetry events.`);
103
+ try {
104
+ const url = new URL("v1/upload-telemetry", this.baseUrl);
105
+ await fetchOrThrow(url, {
106
+ method: "POST",
107
+ headers: { ...this.sdkHeaders, "Content-Type": "application/json" },
108
+ body: JSON.stringify({ events: payload }),
109
+ });
110
+ } catch (e) {
111
+ logger.error(`Telemetry upload failed: ${String(e)}`);
112
+ }
113
+ }
114
+ }
115
+
116
+ export class NoOpTelemetryClient extends BaseTelemetryClient {
117
+ sendEvent(_event: TelemetryEvent, _data?: Record<string, unknown>) {}
118
+ }
119
+
120
+ const isDisabled = ["yes", "true"].includes(
121
+ (process.env.GUAVA_DISABLE_TELEMETRY ?? "false").toLowerCase().trim(),
122
+ );
123
+ export const telemetryClient: BaseTelemetryClient = isDisabled
124
+ ? new NoOpTelemetryClient()
125
+ : new TelemetryClient();
package/src/utils.ts ADDED
@@ -0,0 +1,32 @@
1
+ export const DEFAULT_BASE_URL = "https://guava-dev.gridspace.com/";
2
+
3
+ export function getBaseUrl(): string {
4
+ return process.env.GUAVA_BASE_URL ?? DEFAULT_BASE_URL;
5
+ }
6
+
7
+ class HttpStatusError extends Error {
8
+ constructor(
9
+ public readonly status: number,
10
+ public readonly statusText: string,
11
+ public readonly body: string,
12
+ public readonly response: Response,
13
+ ) {
14
+ super(`HTTP ${status} ${statusText}${body ? ` — ${body}` : ""}`);
15
+ this.name = "HttpStatusError";
16
+ }
17
+ }
18
+
19
+ export async function fetchOrThrow(
20
+ input: RequestInfo | URL,
21
+ init?: RequestInit,
22
+ ): Promise<Response> {
23
+ // biome-ignore lint: The wrapper must call fetch.
24
+ const res = await fetch(input, init);
25
+
26
+ if (!res.ok) {
27
+ const body = await res.text().catch(() => "");
28
+ throw new HttpStatusError(res.status, res.statusText, body, res);
29
+ }
30
+
31
+ return res;
32
+ }
@@ -1,79 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import * as guava from "../src/index.js";
11
- import { getConsoleLogger } from "../src/logging.js";
12
- import { IntentRecognizer } from "../src/helpers/openai.js";
13
- class ThaiPalaceCallController extends guava.CallController {
14
- constructor(logger) {
15
- super(logger);
16
- this.choices = ["restaurant waitlist", "anything else"];
17
- this.intentRecognizer = new IntentRecognizer(this.choices, logger);
18
- this.setPersona({
19
- organizationName: "Thai Palace",
20
- });
21
- }
22
- onIncomingCall(from_number) {
23
- const _super = Object.create(null, {
24
- onIncomingCall: { get: () => super.onIncomingCall }
25
- });
26
- return __awaiter(this, void 0, void 0, function* () {
27
- yield _super.onIncomingCall.call(this, from_number);
28
- this.acceptCall();
29
- this.setThaiPalaceTask();
30
- });
31
- }
32
- setThaiPalaceTask() {
33
- this.setTask({
34
- objective: `You are a virtual assistant for a restaurant called Thai Palace.
35
- Your job is to add callers to the waitlist.`,
36
- checklist: [
37
- {
38
- item_type: "field",
39
- key: "caller_name",
40
- field_type: "text",
41
- description: "The name to be added to the waitlist",
42
- },
43
- {
44
- item_type: "field",
45
- key: "party_size",
46
- field_type: "integer",
47
- description: "The number of people attending",
48
- },
49
- {
50
- item_type: "field",
51
- key: "phone_number",
52
- field_type: "text",
53
- description: "phone number to text when table is ready",
54
- },
55
- "Read the phone number back to the caller to make sure you got it right",
56
- ],
57
- }, () => this.hangup());
58
- }
59
- onIntent(intent) {
60
- return __awaiter(this, void 0, void 0, function* () {
61
- const choice = yield this.intentRecognizer.classify(intent);
62
- this.logger.info(`Chosen intent: ${choice}`);
63
- if (choice == "restaurant waitlist") {
64
- this.setThaiPalaceTask();
65
- return null;
66
- }
67
- else {
68
- return "Tell them we only handle waitlist additions at this number.";
69
- }
70
- });
71
- }
72
- }
73
- export function run(_args) {
74
- new guava.Client().listenInbound({ agent_number: process.env.GUAVA_AGENT_NUMBER }, (logger) => new ThaiPalaceCallController(getConsoleLogger("debug")));
75
- }
76
- if (import.meta.main) {
77
- run([]);
78
- }
79
- //# sourceMappingURL=thai_palace.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"thai_palace.js","sourceRoot":"","sources":["../../examples/thai_palace.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,KAAK,KAAK,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,MAAM,wBAAyB,SAAQ,KAAK,CAAC,cAAc;IAGzD,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QAHR,YAAO,GAAG,CAAC,qBAAqB,EAAE,eAAe,CAAU,CAAC;QAIlE,IAAI,CAAC,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACnE,IAAI,CAAC,UAAU,CAAC;YACd,gBAAgB,EAAE,aAAa;SAChC,CAAC,CAAC;IACL,CAAC;IAEc,cAAc,CAAC,WAAoB;;;;;YAChD,MAAM,OAAM,cAAc,YAAC,WAAW,CAAC,CAAC;YACxC,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;KAAA;IAED,iBAAiB;QACf,IAAI,CAAC,OAAO,CACV;YACE,SAAS,EAAE;6CAC0B;YACrC,SAAS,EAAE;gBACT;oBACE,SAAS,EAAE,OAAO;oBAClB,GAAG,EAAE,aAAa;oBAClB,UAAU,EAAE,MAAM;oBAClB,WAAW,EAAE,sCAAsC;iBACpD;gBACD;oBACE,SAAS,EAAE,OAAO;oBAClB,GAAG,EAAE,YAAY;oBACjB,UAAU,EAAE,SAAS;oBACrB,WAAW,EAAE,gCAAgC;iBAC9C;gBACD;oBACE,SAAS,EAAE,OAAO;oBAClB,GAAG,EAAE,cAAc;oBACnB,UAAU,EAAE,MAAM;oBAClB,WAAW,EAAE,0CAA0C;iBACxD;gBACD,wEAAwE;aACzE;SACF,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CACpB,CAAC;IACJ,CAAC;IAEc,QAAQ,CAAC,MAAc;;YACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC;YAC7C,IAAI,MAAM,IAAI,qBAAqB,EAAE,CAAC;gBACpC,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACzB,OAAO,IAAI,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,OAAO,6DAA6D,CAAC;YACvE,CAAC;QACH,CAAC;KAAA;CACF;AAED,MAAM,UAAU,GAAG,CAAC,KAAe;IACjC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC,aAAa,CAC9B,EAAE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAmB,EAAE,EACjD,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,wBAAwB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CACpE,CAAC;AACJ,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,CAAC,CAAC;AACV,CAAC"}