@guava-ai/guava-sdk 0.1.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.
@@ -0,0 +1,49 @@
1
+ import { parseArgs } from "node:util";
2
+ import * as guava from "../src/index.ts";
3
+ import { DocumentQA } from "../src/helpers/openai.ts";
4
+ import { readFileSync } from "node:fs";
5
+ import * as path from "node:path";
6
+ import { type Logger } from "../src/logging.ts";
7
+ import {PROPERTY_INSURANCE_POLICY} from "../src/example_data.ts";
8
+
9
+ class InsuranceCallController extends guava.CallController {
10
+ private documentQA: DocumentQA;
11
+ constructor(logger: Logger) {
12
+ super(logger);
13
+
14
+ this.documentQA = new DocumentQA(
15
+ "harper-valley-property-insurance",
16
+ PROPERTY_INSURANCE_POLICY,
17
+ logger,
18
+ );
19
+ }
20
+ override async onCallStart(): Promise<void> {
21
+ await this.setPersona("Harper Valley Property Insurance");
22
+ await this.setTask(
23
+ "You are making an outbound call to a potential customer. Your task is to answer questions regarding property insurance policy until there are no more questions.",
24
+ );
25
+ }
26
+
27
+ override async onQuestion(question: string): Promise<string> {
28
+ return await this.documentQA.ask(question);
29
+ }
30
+ }
31
+
32
+ export function run(args: string[]) {
33
+ const [phone] = args;
34
+
35
+ if (!phone) {
36
+ console.error("Usage: guava-example property-insurance <phone>");
37
+ process.exit(1);
38
+ }
39
+
40
+ new guava.Client().createOutbound(
41
+ process.env.GUAVA_AGENT_NUMBER!,
42
+ phone,
43
+ (logger) => new InsuranceCallController(logger),
44
+ );
45
+ }
46
+
47
+ if (import.meta.main) {
48
+ run(process.argv.slice(2))
49
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@guava-ai/guava-sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "devDependencies": {
8
+ "@biomejs/biome": "2.3.11",
9
+ "@types/node": "^25.0.7",
10
+ "@types/ws": "^8.18.1",
11
+ "typescript": "^5.9.3"
12
+ },
13
+ "bin": {
14
+ "guava-example": "./bin/example-runner.js"
15
+ },
16
+ "dependencies": {
17
+ "openai": "^6.16.0",
18
+ "ws": "^8.19.0",
19
+ "zod": "^4.3.5"
20
+ },
21
+ "scripts": {
22
+ "typecheck": "tsc --noEmit",
23
+ "lint": "biome lint",
24
+ "format": "biome format --write ./src",
25
+ "format-ex": "biome format --write ./examples",
26
+ "check": "npm run typecheck && npm audit",
27
+ "clean": "rm -rf ./dist",
28
+ "build": "tsc",
29
+ "prepublishOnly": "npm run clean && npm run build"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "files": [
35
+ "/src",
36
+ "/dist",
37
+ "/examples",
38
+ "/bin"
39
+ ]
40
+ }
@@ -0,0 +1,42 @@
1
+ import * as z from "zod";
2
+
3
+ export const fieldItemType = z.union(
4
+ (["text", "date", "datetime", "integer", "multiple_choice"] as const).map(
5
+ (val) => z.literal(val),
6
+ ),
7
+ );
8
+ export type FieldItemType = z.input<typeof fieldItemType>;
9
+
10
+ export const fieldItem = z
11
+ .object({
12
+ item_type: z.literal("field"),
13
+ key: z.string(),
14
+ description: z.string(),
15
+ field_type: fieldItemType,
16
+ required: z.boolean().default(true),
17
+ choices: z.array(z.string()),
18
+ })
19
+ .refine((field) => {
20
+ if (field.field_type == "multiple_choice" && field.choices.length > 10) {
21
+ process.emitWarning("Performance degrades with large number of choices for multiple choice field.", "ACTION_ITEM")
22
+ }
23
+ return true;
24
+ });
25
+ export type Field = z.input<typeof fieldItem>;
26
+
27
+ export const sayItem = z.object({
28
+ item_type: z.literal("say"),
29
+ statement: z.string(),
30
+ key: z.string().default(() => Math.random().toString(16).substring(2, 6)),
31
+ });
32
+ export type Say = z.input<typeof sayItem>;
33
+
34
+ export const todoItem = z.object({
35
+ item_type: z.literal("todo"),
36
+ description: z.string(),
37
+ key: z.string().default(() => Math.random().toString(16).substring(2, 6)),
38
+ });
39
+ export type Todo = z.input<typeof todoItem>;
40
+
41
+ export const actionItem = z.union([fieldItem, sayItem, todoItem]);
42
+ export type ActionItem = z.input<typeof actionItem>;
@@ -0,0 +1,94 @@
1
+ import * as z from "zod";
2
+ import { actionItem } from "./action_item.ts";
3
+
4
+ export const startOutboundCallCommand = z.strictObject({
5
+ command_type: z.literal("start-outbound"),
6
+
7
+ from_number: z.e164().optional(),
8
+ to_number: z.e164(),
9
+ });
10
+ export type StartOutboundCallCommand = z.input<typeof startOutboundCallCommand>;
11
+
12
+ export const listenInboundCommand = z
13
+ .strictObject({
14
+ command_type: z.literal("listen-inbound"),
15
+
16
+ agent_number: z.e164().optional(),
17
+ webrtc_code: z.string().optional(),
18
+ })
19
+ .refine((obj) => {
20
+ !!obj.agent_number || !!obj.webrtc_code;
21
+ });
22
+ export type ListenInboundCommand = z.input<typeof listenInboundCommand>;
23
+
24
+ export const rejectInboundCallCommand = z.strictObject({
25
+ command_type: z.literal("reject-inbound"),
26
+ });
27
+ export type RejectInboundCallCommand = z.input<typeof rejectInboundCallCommand>;
28
+
29
+ export const acceptInboundCallCommand = z.strictObject({
30
+ command_type: z.literal("accept-inbound"),
31
+ });
32
+ export type AcceptInboundCallCommand = z.input<typeof acceptInboundCallCommand>;
33
+
34
+ export const setTaskCommand = z.strictObject({
35
+ command_type: z.literal("set-task"),
36
+ task_id: z.string(),
37
+ objective: z.string(),
38
+ action_items: z.array(actionItem),
39
+ });
40
+ export type SetTaskCommand = z.input<typeof setTaskCommand>;
41
+
42
+ export const readScriptCommand = z.strictObject({
43
+ command_type: z.literal("read-script"),
44
+ script: z.string(),
45
+ });
46
+ export type ReadScriptCommand = z.input<typeof readScriptCommand>;
47
+
48
+ export const answerQuestionCommand = z.strictObject({
49
+ command_type: z.literal("answer-question"),
50
+ question_id: z.string(),
51
+ answer: z.string(),
52
+ });
53
+ export type AnswerQuestionCommand = z.input<typeof answerQuestionCommand>;
54
+
55
+ export const setPersona = z.strictObject({
56
+ command_type: z.literal("set-persona"),
57
+ agent_name: z.string().optional(),
58
+ organization_name: z.string().optional(),
59
+ agent_purpose: z.string().optional(),
60
+ });
61
+ export type SetPersona = z.input<typeof setPersona>;
62
+
63
+ export const sendInstructionCommand = z.strictObject({
64
+ command_type: z.literal("send-instruction"),
65
+ instruction: z.string(),
66
+ });
67
+ export type SendInstructionCommand = z.input<typeof sendInstructionCommand>;
68
+
69
+ export const transferCommand = z.strictObject({
70
+ command_type: z.literal("transfer-call"),
71
+ transfer_message: z.string(),
72
+ to_number: z.string(),
73
+ });
74
+ export type TransferCommand = z.input<typeof transferCommand>;
75
+
76
+ export const anyCommand = z.union([
77
+ startOutboundCallCommand,
78
+ listenInboundCommand,
79
+ rejectInboundCallCommand,
80
+ acceptInboundCallCommand,
81
+ setTaskCommand,
82
+ readScriptCommand,
83
+ answerQuestionCommand,
84
+ setPersona,
85
+ sendInstructionCommand,
86
+ transferCommand,
87
+ ]);
88
+ export type Command = z.input<typeof anyCommand>;
89
+
90
+ export const inboundTunnelCommand = z.strictObject({
91
+ call_id: z.string(),
92
+ command: anyCommand,
93
+ });
94
+ export type InboundTunnelCommand = z.input<typeof inboundTunnelCommand>;
package/src/events.ts ADDED
@@ -0,0 +1,142 @@
1
+ import * as z from "zod";
2
+
3
+ export const sessionStartedEvent = z.object({
4
+ event_type: z.literal("session-started"),
5
+ session_id: z.string(),
6
+ });
7
+ export type SessionStartedEvent = z.input<typeof sessionStartedEvent>;
8
+
9
+ export const inboundCallEvent = z.object({
10
+ event_type: z.literal("inbound-call"),
11
+ caller_number: z.e164().optional(),
12
+ agent_number: z.e164().optional(),
13
+ });
14
+ export type InboundCallEvent = z.input<typeof inboundCallEvent>;
15
+
16
+ /**
17
+ * @description The caller has said something.
18
+ */
19
+ export const callerSpeechEvent = z.object({
20
+ event_type: z.literal("caller-speech"),
21
+
22
+ utterance: z.string(),
23
+ });
24
+ export type CallerSpeechEvent = z.input<typeof callerSpeechEvent>;
25
+
26
+ /**
27
+ * @description The agent has said something.
28
+ */
29
+ export const agentSpeechEvent = z.object({
30
+ event_type: z.literal("agent-speech"),
31
+
32
+ utterance: z.string(),
33
+ interrupted: z.boolean().default(false),
34
+ });
35
+ export type AgentSpeechEvent = z.input<typeof agentSpeechEvent>;
36
+
37
+ export const errorEvent = z.object({
38
+ event_type: z.literal("error"),
39
+ content: z.string(),
40
+ });
41
+ export type ErrorEvent = z.input<typeof errorEvent>;
42
+
43
+ export const warningEvent = z.object({
44
+ event_type: z.literal("warning"),
45
+ content: z.string(),
46
+ });
47
+ export type WarningEvent = z.input<typeof warningEvent>;
48
+
49
+ export const agentQuestionEvent = z.object({
50
+ event_type: z.literal("agent-question"),
51
+ question_id: z.string(),
52
+ question: z.string(),
53
+ });
54
+ export type AgentQuestionEvent = z.input<typeof agentQuestionEvent>;
55
+
56
+ export const intentEvent = z.object({
57
+ event_type: z.literal("intent"),
58
+ intent_id: z.string(),
59
+ intent_summary: z.string(),
60
+ });
61
+ export type IntentEvent = z.input<typeof intentEvent>;
62
+
63
+ export const actionItemCompletedEvent = z.object({
64
+ event_type: z.literal("action-item-done"),
65
+ key: z.string(),
66
+ payload: z.unknown(),
67
+ });
68
+ export type ActionItemCompletedEvent = z.input<typeof actionItemCompletedEvent>;
69
+
70
+ export const taskCompletedEvent = z.object({
71
+ event_type: z.literal("task-done"),
72
+
73
+ task_id: z.string(),
74
+ });
75
+ export type TaskCompletedEvent = z.input<typeof taskCompletedEvent>;
76
+
77
+ export const outboundCallConnected = z.object({
78
+ event_type: z.literal("outbound-call-connected"),
79
+ });
80
+ export type OutboundCallConnected = z.input<typeof outboundCallConnected>;
81
+
82
+ export const outboundCallFailed = z.object({
83
+ event_type: z.literal("outbound-call-failed"),
84
+
85
+ error_code: z.int(),
86
+ error_reason: z.string(),
87
+ });
88
+ export type OutboundCallFailed = z.input<typeof outboundCallFailed>;
89
+
90
+ export const botSessionEnded = z.object({
91
+ event_type: z.literal("bot-session-ended"),
92
+ });
93
+ export type BotSessionEnded = z.input<typeof botSessionEnded>;
94
+
95
+ export const anyEvent = z.union([
96
+ sessionStartedEvent,
97
+ inboundCallEvent,
98
+ callerSpeechEvent,
99
+ agentSpeechEvent,
100
+ errorEvent,
101
+ warningEvent,
102
+ agentQuestionEvent,
103
+ intentEvent,
104
+ actionItemCompletedEvent,
105
+ taskCompletedEvent,
106
+ outboundCallConnected,
107
+ outboundCallFailed,
108
+ botSessionEnded,
109
+ ]);
110
+ export type GuavaEvent = z.input<typeof anyEvent>;
111
+
112
+ export function decodeEvent(
113
+ serialized_event: string | ArrayBuffer | Buffer | Buffer[],
114
+ ): GuavaEvent | null {
115
+ let data: Record<string, any>;
116
+ if (typeof serialized_event == "string") {
117
+ data = JSON.parse(serialized_event);
118
+ } else if (serialized_event instanceof ArrayBuffer) {
119
+ data = JSON.parse(new TextDecoder().decode(serialized_event));
120
+ } else if (serialized_event instanceof Array) {
121
+ let decoded = "";
122
+ for (const buf of serialized_event) {
123
+ decoded += buf.toString("utf8");
124
+ }
125
+ data = JSON.parse(decoded);
126
+ } else {
127
+ data = JSON.parse(serialized_event.toString("utf8"));
128
+ }
129
+
130
+ try {
131
+ return anyEvent.parse(data);
132
+ } catch (e) {
133
+ // TODO logging global use signleton to warn
134
+ return null;
135
+ }
136
+ }
137
+
138
+ export const inboundTunnelEvent = z.object({
139
+ call_id: z.string(),
140
+ event: anyEvent,
141
+ });
142
+ export type inboundTunnelEvent = z.input<typeof inboundTunnelEvent>;