@guava-ai/guava-sdk 0.9.0 → 0.11.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/README.md +1 -1
- package/bin/example-runner.ts +4 -4
- package/dist/bin/example-runner.js +4 -4
- package/dist/bin/example-runner.js.map +1 -1
- package/dist/examples/help-desk.js +71 -0
- package/dist/examples/help-desk.js.map +1 -0
- package/dist/examples/property-insurance.js +14 -22
- package/dist/examples/property-insurance.js.map +1 -1
- package/dist/examples/restaurant-waitlist.js +74 -0
- package/dist/examples/restaurant-waitlist.js.map +1 -0
- package/dist/examples/scheduling-outbound.js +30 -54
- package/dist/examples/scheduling-outbound.js.map +1 -1
- package/dist/src/{action_item.d.ts → action-item.d.ts} +2 -0
- package/dist/src/{action_item.js → action-item.js} +3 -1
- package/dist/src/action-item.js.map +1 -0
- package/dist/src/agent.d.ts +72 -0
- package/dist/src/agent.js +444 -0
- package/dist/src/agent.js.map +1 -0
- package/dist/src/call-controller.d.ts +1 -1
- package/dist/src/call-controller.js +1 -1
- package/dist/src/call.d.ts +73 -0
- package/dist/src/call.js +252 -0
- package/dist/src/call.js.map +1 -0
- package/dist/src/commands.d.ts +95 -0
- package/dist/src/commands.js +24 -2
- package/dist/src/commands.js.map +1 -1
- package/dist/src/events.d.ts +25 -0
- package/dist/src/events.js +12 -1
- package/dist/src/events.js.map +1 -1
- package/dist/src/example-data.d.ts +1 -0
- package/dist/src/example-data.js +41 -1
- package/dist/src/example-data.js.map +1 -1
- package/dist/src/index.d.ts +14 -5
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging.js +1 -1
- package/dist/src/logging.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/examples/README.md +6 -0
- package/examples/help-desk.ts +60 -0
- package/examples/property-insurance.ts +14 -30
- package/examples/restaurant-waitlist.ts +47 -0
- package/examples/scheduling-outbound.ts +40 -76
- package/package.json +2 -1
- package/src/{action_item.ts → action-item.ts} +3 -0
- package/src/agent.ts +439 -0
- package/src/call-controller.ts +1 -1
- package/src/call.ts +279 -0
- package/src/commands.ts +31 -1
- package/src/events.ts +15 -0
- package/src/example-data.ts +41 -0
- package/src/index.ts +7 -5
- package/src/logging.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/examples/credit-card-activation.js +0 -177
- package/dist/examples/credit-card-activation.js.map +0 -1
- package/dist/examples/thai-palace.js +0 -94
- package/dist/examples/thai-palace.js.map +0 -1
- package/dist/src/action_item.js.map +0 -1
- package/examples/credit-card-activation.ts +0 -178
- package/examples/thai-palace.ts +0 -67
- /package/dist/examples/{credit-card-activation.d.ts → help-desk.d.ts} +0 -0
- /package/dist/examples/{thai-palace.d.ts → restaurant-waitlist.d.ts} +0 -0
package/src/call.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { type Logger, getDefaultLogger } from "./logging.ts";
|
|
2
|
+
import {
|
|
3
|
+
type Command,
|
|
4
|
+
SetPersona,
|
|
5
|
+
SetLanguageModeCommand,
|
|
6
|
+
type Language,
|
|
7
|
+
SetTaskCommand,
|
|
8
|
+
SendInstructionCommand,
|
|
9
|
+
TransferCommand,
|
|
10
|
+
ReadScriptCommand,
|
|
11
|
+
RetryTaskCommand,
|
|
12
|
+
} from "./commands.ts";
|
|
13
|
+
import type * as z from "zod";
|
|
14
|
+
import type {
|
|
15
|
+
ActionItem,
|
|
16
|
+
FieldItem,
|
|
17
|
+
SayItem,
|
|
18
|
+
SerializableFieldItem,
|
|
19
|
+
TodoItem,
|
|
20
|
+
} from "./action-item.ts";
|
|
21
|
+
import { Say } from "./action-item.ts";
|
|
22
|
+
import { telemetryClient } from "./telemetry.ts";
|
|
23
|
+
|
|
24
|
+
export type TaskObjective =
|
|
25
|
+
| { objective: string }
|
|
26
|
+
| { objective?: string; checklist: (FieldItem | SayItem | string)[] };
|
|
27
|
+
|
|
28
|
+
export type ReachPersonOutcome = {
|
|
29
|
+
key: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
nextActionPreview?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
@telemetryClient.trackClass()
|
|
35
|
+
export class Call {
|
|
36
|
+
private _commandQueue: Command[] = [];
|
|
37
|
+
private _variables: Record<string, any> = {};
|
|
38
|
+
protected logger: Logger;
|
|
39
|
+
|
|
40
|
+
// drain functions are expected to cleanup
|
|
41
|
+
// the part of the queue that is successfully sent from its
|
|
42
|
+
// input (mutating it) (i.e. _drain should use Array.splice)
|
|
43
|
+
private _drain?: (_: Command[]) => Promise<void>;
|
|
44
|
+
_fieldValues: Record<string, unknown> = {};
|
|
45
|
+
|
|
46
|
+
constructor(variables: Record<string, any> = {}, logger: Logger = getDefaultLogger()) {
|
|
47
|
+
// Set initial variables.
|
|
48
|
+
this._variables = { ...variables };
|
|
49
|
+
|
|
50
|
+
// Set up the default logger.
|
|
51
|
+
this.logger = logger;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @description Supply a function used to consume commands from the internal command queue.
|
|
56
|
+
*
|
|
57
|
+
* The function is expected to remove from the argument array commands that it has handled (iterating
|
|
58
|
+
* through the result of `Array.splice(0)` is sufficient)
|
|
59
|
+
*/
|
|
60
|
+
async setDrain(newDrain: (_: Command[]) => Promise<void>) {
|
|
61
|
+
this._drain = newDrain;
|
|
62
|
+
await this.flush();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async flush() {
|
|
66
|
+
await this._drain?.call(this, this._commandQueue);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async getField(key: string) {
|
|
70
|
+
// Async since the implementation is likely to become async in teh future.
|
|
71
|
+
if (key in this._fieldValues) {
|
|
72
|
+
return this._fieldValues[key];
|
|
73
|
+
} else {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async sendCommand<C extends Command, Schema extends z.ZodType<C>>(
|
|
79
|
+
schema: Schema,
|
|
80
|
+
data: z.input<Schema>,
|
|
81
|
+
) {
|
|
82
|
+
const command = schema.parse(data);
|
|
83
|
+
this._commandQueue.push(command);
|
|
84
|
+
await this.flush();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async setLanguageMode(args: { primary?: Language; secondary?: Language[] }) {
|
|
88
|
+
await this.sendCommand(SetLanguageModeCommand, {
|
|
89
|
+
command_type: "set-language-mode",
|
|
90
|
+
primary: args.primary ?? "english",
|
|
91
|
+
secondary: args.secondary ?? [],
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @description provide identifiers the agent will use to identify the virtual agent
|
|
97
|
+
*/
|
|
98
|
+
async setPersona(args: {
|
|
99
|
+
organizationName?: string;
|
|
100
|
+
agentName?: string;
|
|
101
|
+
agentPurpose?: string;
|
|
102
|
+
voice?: string;
|
|
103
|
+
}) {
|
|
104
|
+
await this.sendCommand(SetPersona, {
|
|
105
|
+
command_type: "set-persona",
|
|
106
|
+
organization_name: args.organizationName,
|
|
107
|
+
agent_name: args.agentName,
|
|
108
|
+
agent_purpose: args.agentPurpose,
|
|
109
|
+
voice: args.voice,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @description direct the agent to collect information
|
|
115
|
+
* @param taskArgs.task_id unique identifier for this task
|
|
116
|
+
* @param taskArgs.objective high-level goal for the agent
|
|
117
|
+
* @param taskArgs.checklist ordered list of fields, statements, or instructions to collect
|
|
118
|
+
*/
|
|
119
|
+
async setTask(taskArgs: {
|
|
120
|
+
taskId: string;
|
|
121
|
+
objective?: string;
|
|
122
|
+
checklist?: (FieldItem | SayItem | string)[];
|
|
123
|
+
completionCriteria?: string;
|
|
124
|
+
}) {
|
|
125
|
+
const { taskId, objective = "", checklist = [], completionCriteria } = taskArgs;
|
|
126
|
+
|
|
127
|
+
if (!objective && checklist.length === 0) {
|
|
128
|
+
throw new Error("At least one of ['objective', 'checklist'] must be provided.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const action_items = checklist.map((item): ActionItem => {
|
|
132
|
+
if (typeof item === "string") {
|
|
133
|
+
return { item_type: "todo", description: item } satisfies TodoItem;
|
|
134
|
+
}
|
|
135
|
+
if (item.item_type === "field") {
|
|
136
|
+
if (item.choiceGenerator) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
"choiceGenerator is not compatible with the Agent / Call API. Use searchable=true and register a handler.",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const { choiceGenerator: _, ...fieldData } = item;
|
|
142
|
+
return { ...fieldData, is_search_field: item.searchable } satisfies SerializableFieldItem;
|
|
143
|
+
}
|
|
144
|
+
return item;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await this.sendCommand(SetTaskCommand, {
|
|
148
|
+
command_type: "set-task",
|
|
149
|
+
task_id: taskId,
|
|
150
|
+
objective,
|
|
151
|
+
action_items,
|
|
152
|
+
completion_criteria: completionCriteria,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async transfer(destination: string, instructions?: string) {
|
|
157
|
+
await this.sendCommand(TransferCommand, {
|
|
158
|
+
command_type: "transfer-call",
|
|
159
|
+
to_number: destination,
|
|
160
|
+
transfer_message:
|
|
161
|
+
instructions ?? "Notify the caller that you will be transferring them, and then transfer.",
|
|
162
|
+
soft_transfer: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async addInfo(label: string, info: unknown) {
|
|
167
|
+
await this.sendInstruction(
|
|
168
|
+
`Here is some information about the following topic ${label}:\n${JSON.stringify(info, null, 2)}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async retryTask(reason: string) {
|
|
173
|
+
await this.sendCommand(RetryTaskCommand, {
|
|
174
|
+
command_type: "retry-task",
|
|
175
|
+
reason,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async readScript(script: string) {
|
|
180
|
+
await this.sendCommand(ReadScriptCommand, {
|
|
181
|
+
command_type: "read-script",
|
|
182
|
+
script,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async sendInstruction(instruction: string) {
|
|
187
|
+
await this.sendCommand(SendInstructionCommand, {
|
|
188
|
+
command_type: "send-instruction",
|
|
189
|
+
instruction: instruction,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @description hang up an accepted call
|
|
195
|
+
*/
|
|
196
|
+
async hangup(final_instructions: string = "") {
|
|
197
|
+
let instructions: string;
|
|
198
|
+
if (final_instructions) {
|
|
199
|
+
instructions = `Start ending the conversation. Here are your final instructions: ${final_instructions} Once you've completed the final instructions, naturally end the conversation and hang up the call.`;
|
|
200
|
+
} else {
|
|
201
|
+
instructions = "Naturally end the conversation and hang up the call.";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await this.sendInstruction(instructions);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async reachPerson(
|
|
208
|
+
contactFullName: string,
|
|
209
|
+
options: { outcomes?: ReachPersonOutcome[]; greeting?: string } = {},
|
|
210
|
+
) {
|
|
211
|
+
const outcomes = options.outcomes ?? [
|
|
212
|
+
{ key: "available", description: "The contact is available to speak." },
|
|
213
|
+
{
|
|
214
|
+
key: "unavailable",
|
|
215
|
+
description:
|
|
216
|
+
"The contact is not available to speak. This includes reaching a wrong number.",
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
const availabilityDescription =
|
|
221
|
+
`The availability of ${contactFullName}` +
|
|
222
|
+
(outcomes.some((o) => o.description)
|
|
223
|
+
? "\nDetailed descriptions of each choice:\n" +
|
|
224
|
+
outcomes
|
|
225
|
+
.filter((o) => o.description)
|
|
226
|
+
.map((o) => ` - ${o.key}: ${o.description}`)
|
|
227
|
+
.join("\n")
|
|
228
|
+
: "");
|
|
229
|
+
|
|
230
|
+
const checklist: (FieldItem | SayItem | string)[] = [
|
|
231
|
+
options.greeting !== undefined
|
|
232
|
+
? Say(options.greeting)
|
|
233
|
+
: `Greet the person who answered the phone. Notify them who you are calling on behalf of and the purpose of the call. Ask to speak with ${contactFullName}`,
|
|
234
|
+
{
|
|
235
|
+
item_type: "field",
|
|
236
|
+
key: "contact_availability",
|
|
237
|
+
field_type: "multiple_choice",
|
|
238
|
+
description: availabilityDescription,
|
|
239
|
+
choices: outcomes.map((o) => o.key),
|
|
240
|
+
} satisfies FieldItem,
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
const nextActionLines = outcomes
|
|
244
|
+
.filter((o) => o.nextActionPreview)
|
|
245
|
+
.map((o) => `- ${o.key} → ${o.nextActionPreview}`);
|
|
246
|
+
if (nextActionLines.length > 0) {
|
|
247
|
+
checklist.push(
|
|
248
|
+
"If a next action is defined below for the value of `contact_availability`, briefly ask the contact to wait just a second while you perform it.\n" +
|
|
249
|
+
nextActionLines.join("\n"),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const objective = `\
|
|
254
|
+
OBJECTIVE:
|
|
255
|
+
Your goal is to reach ${contactFullName} and determine their availability to proceed with this call.
|
|
256
|
+
|
|
257
|
+
RULES:
|
|
258
|
+
1. If the initial respondent is NOT ${contactFullName}:
|
|
259
|
+
- Politely ask to speak with ${contactFullName}
|
|
260
|
+
- Wait to be transferred or for ${contactFullName} to come to the phone
|
|
261
|
+
2. Once you have ${contactFullName} on the line:
|
|
262
|
+
- Briefly restate who you are and the purpose of your call
|
|
263
|
+
- Determine and record their current availability status
|
|
264
|
+
3. DO NOT hang up the call under any circumstances, unless it's a wrong number.
|
|
265
|
+
|
|
266
|
+
TASK COMPLETION REQUIREMENTS:
|
|
267
|
+
- The availability of ${contactFullName} must be recorded in \`contact_availability\`.`;
|
|
268
|
+
|
|
269
|
+
await this.setTask({ taskId: "reach_person", objective, checklist });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async setVariable(variableName: string, variableValue: any) {
|
|
273
|
+
this._variables[variableName] = variableValue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async getVariable(variableName: string) {
|
|
277
|
+
return this._variables[variableName] ?? null;
|
|
278
|
+
}
|
|
279
|
+
}
|
package/src/commands.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
import { ActionItem } from "./
|
|
2
|
+
import { ActionItem } from "./action-item.ts";
|
|
3
3
|
|
|
4
4
|
export const StartOutboundCallCommand = z.strictObject({
|
|
5
5
|
command_type: z.literal("start-outbound"),
|
|
@@ -39,6 +39,7 @@ export const SetTaskCommand = z.strictObject({
|
|
|
39
39
|
task_id: z.string(),
|
|
40
40
|
objective: z.string(),
|
|
41
41
|
action_items: z.array(ActionItem),
|
|
42
|
+
completion_criteria: z.string().optional(),
|
|
42
43
|
});
|
|
43
44
|
export type SetTaskCommand = z.input<typeof SetTaskCommand>;
|
|
44
45
|
|
|
@@ -64,6 +65,16 @@ export const SetPersona = z.strictObject({
|
|
|
64
65
|
});
|
|
65
66
|
export type SetPersona = z.input<typeof SetPersona>;
|
|
66
67
|
|
|
68
|
+
export const Language = z.enum(["english", "spanish", "french", "german", "italian"]);
|
|
69
|
+
export type Language = z.infer<typeof Language>;
|
|
70
|
+
|
|
71
|
+
export const SetLanguageModeCommand = z.strictObject({
|
|
72
|
+
command_type: z.literal("set-language-mode"),
|
|
73
|
+
primary: Language.default("english"),
|
|
74
|
+
secondary: z.array(Language).default([]),
|
|
75
|
+
});
|
|
76
|
+
export type SetLanguageModeCommand = z.input<typeof SetLanguageModeCommand>;
|
|
77
|
+
|
|
67
78
|
export const SendInstructionCommand = z.strictObject({
|
|
68
79
|
command_type: z.literal("send-instruction"),
|
|
69
80
|
instruction: z.string(),
|
|
@@ -74,6 +85,7 @@ export const TransferCommand = z.strictObject({
|
|
|
74
85
|
command_type: z.literal("transfer-call"),
|
|
75
86
|
transfer_message: z.string(),
|
|
76
87
|
to_number: z.string(),
|
|
88
|
+
soft_transfer: z.boolean().optional().default(false),
|
|
77
89
|
});
|
|
78
90
|
export type TransferCommand = z.input<typeof TransferCommand>;
|
|
79
91
|
|
|
@@ -81,9 +93,18 @@ export const RegisteredHooksCommand = z.strictObject({
|
|
|
81
93
|
command_type: z.literal("registered-hooks"),
|
|
82
94
|
has_on_question: z.boolean(),
|
|
83
95
|
has_on_intent: z.boolean(),
|
|
96
|
+
has_on_action_requested: z.boolean().optional().default(false),
|
|
84
97
|
});
|
|
85
98
|
export type RegisteredHooksCommand = z.input<typeof RegisteredHooksCommand>;
|
|
86
99
|
|
|
100
|
+
export const ActionSuggestionCommand = z.strictObject({
|
|
101
|
+
command_type: z.literal("action-suggestion"),
|
|
102
|
+
intent_id: z.string(),
|
|
103
|
+
action_key: z.string().nullable(),
|
|
104
|
+
action_description: z.string().default(""),
|
|
105
|
+
});
|
|
106
|
+
export type ActionSuggestionCommand = z.input<typeof ActionSuggestionCommand>;
|
|
107
|
+
|
|
87
108
|
export const ChoiceResultCommand = z.strictObject({
|
|
88
109
|
command_type: z.literal("choice-query-result"),
|
|
89
110
|
field_key: z.string(),
|
|
@@ -93,6 +114,12 @@ export const ChoiceResultCommand = z.strictObject({
|
|
|
93
114
|
});
|
|
94
115
|
export type ChoiceResultCommand = z.input<typeof ChoiceResultCommand>;
|
|
95
116
|
|
|
117
|
+
export const RetryTaskCommand = z.strictObject({
|
|
118
|
+
command_type: z.literal("retry-task"),
|
|
119
|
+
reason: z.string(),
|
|
120
|
+
});
|
|
121
|
+
export type RetryTaskCommand = z.input<typeof RetryTaskCommand>;
|
|
122
|
+
|
|
96
123
|
export const AnyCommand = z.union([
|
|
97
124
|
StartOutboundCallCommand,
|
|
98
125
|
ListenInboundCommand,
|
|
@@ -102,10 +129,13 @@ export const AnyCommand = z.union([
|
|
|
102
129
|
ReadScriptCommand,
|
|
103
130
|
AnswerQuestionCommand,
|
|
104
131
|
SetPersona,
|
|
132
|
+
SetLanguageModeCommand,
|
|
105
133
|
SendInstructionCommand,
|
|
106
134
|
TransferCommand,
|
|
107
135
|
RegisteredHooksCommand,
|
|
108
136
|
ChoiceResultCommand,
|
|
137
|
+
ActionSuggestionCommand,
|
|
138
|
+
RetryTaskCommand,
|
|
109
139
|
]);
|
|
110
140
|
export type Command = z.input<typeof AnyCommand>;
|
|
111
141
|
|
package/src/events.ts
CHANGED
|
@@ -100,6 +100,19 @@ export const ChoiceQueryEvent = z.object({
|
|
|
100
100
|
});
|
|
101
101
|
export type ChoiceQueryEvent = z.infer<typeof ChoiceQueryEvent>;
|
|
102
102
|
|
|
103
|
+
export const ActionRequestEvent = z.object({
|
|
104
|
+
event_type: z.literal("action-request"),
|
|
105
|
+
intent_id: z.string(),
|
|
106
|
+
intent_summary: z.string(),
|
|
107
|
+
});
|
|
108
|
+
export type ActionRequestEvent = z.infer<typeof ActionRequestEvent>;
|
|
109
|
+
|
|
110
|
+
export const ExecuteActionEvent = z.object({
|
|
111
|
+
event_type: z.literal("execute-action"),
|
|
112
|
+
action_key: z.string(),
|
|
113
|
+
});
|
|
114
|
+
export type ExecuteActionEvent = z.infer<typeof ExecuteActionEvent>;
|
|
115
|
+
|
|
103
116
|
export const GuavaEvent = z.union([
|
|
104
117
|
SessionStartedEvent,
|
|
105
118
|
InboundCallEvent,
|
|
@@ -115,6 +128,8 @@ export const GuavaEvent = z.union([
|
|
|
115
128
|
OutboundCallFailed,
|
|
116
129
|
BotSessionEnded,
|
|
117
130
|
ChoiceQueryEvent,
|
|
131
|
+
ActionRequestEvent,
|
|
132
|
+
ExecuteActionEvent,
|
|
118
133
|
]);
|
|
119
134
|
export type GuavaEvent = z.infer<typeof GuavaEvent>;
|
|
120
135
|
|
package/src/example-data.ts
CHANGED
|
@@ -649,3 +649,44 @@ The actual insurance policy, including Declarations, core forms, endorsements, a
|
|
|
649
649
|
|
|
650
650
|
End of Harper Valley Property Insurance
|
|
651
651
|
Comprehensive Residential Program Manual.`;
|
|
652
|
+
|
|
653
|
+
export const FURNITURE_RETAILER_QA = `
|
|
654
|
+
CLEARFIELD HOME & LIVING — HELP DESK Q&A DOCUMENT
|
|
655
|
+
==================================================
|
|
656
|
+
|
|
657
|
+
Q: What are your store hours?
|
|
658
|
+
A: Most Clearfield locations are open Monday–Saturday, 10 AM–8 PM, and Sunday, 11 AM–6 PM. Hours may vary by location and on holidays. Customers can check the store locator on the website for exact hours.
|
|
659
|
+
|
|
660
|
+
Q: How long does delivery take?
|
|
661
|
+
A: Standard delivery is 5–10 business days. Express delivery (available in select areas) is 2–3 business days. Furniture and large appliances require a scheduled delivery window.
|
|
662
|
+
|
|
663
|
+
Q: Can I track my order?
|
|
664
|
+
A: Yes. Customers can track their order at clearfieldhome.com/track using their order number and the email address used at checkout. A tracking link is also sent once the order ships.
|
|
665
|
+
|
|
666
|
+
Q: Can I change or cancel my order?
|
|
667
|
+
A: Orders can be modified or cancelled within 24 hours of placement at no charge. After 24 hours, a restocking fee may apply if the item has already been processed for shipment.
|
|
668
|
+
|
|
669
|
+
Q: What is your return policy?
|
|
670
|
+
A: Items can be returned within 30 days of delivery in original condition with receipt. Mattresses, custom/special-order furniture, and final-sale items are non-returnable.
|
|
671
|
+
|
|
672
|
+
Q: What do I do if my item arrived damaged?
|
|
673
|
+
A: Customers should photograph the damage and contact Clearfield within 48 hours of delivery. Clearfield will arrange a replacement, repair, or refund depending on the situation.
|
|
674
|
+
|
|
675
|
+
Q: Do you offer assembly or installation services?
|
|
676
|
+
A: Yes. White-glove delivery with in-home assembly is available for most furniture and large appliances for an additional fee. It can be added at checkout or when scheduling delivery.
|
|
677
|
+
|
|
678
|
+
Q: How does the warranty work?
|
|
679
|
+
A: All Clearfield products include a 1-year limited warranty covering manufacturing defects. Extended 2- or 5-year warranties are available for purchase. Normal wear, accidental damage, and misuse are not covered.
|
|
680
|
+
|
|
681
|
+
Q: Do you offer financing?
|
|
682
|
+
A: Yes. Clearfield offers financing through ClearPay with 6, 12, and 24-month plans. Customers can apply online or in-store, subject to credit approval.
|
|
683
|
+
|
|
684
|
+
Q: How do I earn and redeem rewards points?
|
|
685
|
+
A: Rewards members earn 1 point per $1 spent. Every 100 points = $5 off at checkout. Points expire after 12 months of account inactivity.
|
|
686
|
+
|
|
687
|
+
Q: Do you offer price matching?
|
|
688
|
+
A: Yes. Clearfield matches a competitor's current advertised price on an identical in-stock item within 14 days of purchase. Proof of price is required. Excludes auction sites, wholesale clubs, and marketplace sellers.
|
|
689
|
+
|
|
690
|
+
Q: Can businesses place bulk orders?
|
|
691
|
+
A: Yes. Clearfield supports bulk purchases and interior design partnerships through corporate accounts.
|
|
692
|
+
`;
|
package/src/index.ts
CHANGED
|
@@ -13,8 +13,10 @@ import { getBaseUrl, fetchOrThrow } from "./utils.ts";
|
|
|
13
13
|
import { telemetryClient } from "./telemetry.ts";
|
|
14
14
|
import type { CallController } from "./call-controller.ts";
|
|
15
15
|
export { CallController, type TaskObjective } from "./call-controller.ts";
|
|
16
|
-
export { Say, Field } from "./
|
|
17
|
-
export { Logger, getConsoleLogger } from "./logging.ts";
|
|
16
|
+
export { Say, Field } from "./action-item.ts";
|
|
17
|
+
export { Logger, getConsoleLogger, getDefaultLogger } from "./logging.ts";
|
|
18
|
+
export { Agent, CallInfo } from "./agent.ts";
|
|
19
|
+
export { Call } from "./call.ts";
|
|
18
20
|
|
|
19
21
|
const SDK_NAME = "typescript-sdk";
|
|
20
22
|
|
|
@@ -81,7 +83,7 @@ export class Client {
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
getWebsocketBase() {
|
|
85
87
|
if (http_start.test(this._baseUrl)) {
|
|
86
88
|
return `ws://${this._baseUrl.substring("ws://".length)}`;
|
|
87
89
|
} else if (https_start.test(this._baseUrl)) {
|
|
@@ -91,11 +93,11 @@ export class Client {
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
getHttpBase() {
|
|
95
97
|
return this._baseUrl;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
headers() {
|
|
99
101
|
return {
|
|
100
102
|
Authorization: `Bearer ${this._apiKey}`,
|
|
101
103
|
"x-guava-platform": os.platform(),
|
package/src/logging.ts
CHANGED
|
@@ -44,7 +44,7 @@ function makeColoredMethod(
|
|
|
44
44
|
): (format: string, ...args: unknown[]) => void {
|
|
45
45
|
if (!useColor) return fn.bind(console);
|
|
46
46
|
return (format: string, ...args: unknown[]) =>
|
|
47
|
-
fn(`${LEVEL_COLORS[level]}[${level}] ${format}${ANSI_RESET}`, ...args);
|
|
47
|
+
fn(`${LEVEL_COLORS[level]}[${level.toLocaleUpperCase()}] ${format}${ANSI_RESET}`, ...args);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export function getConsoleLogger(loggerLevel: LogLevel, useColor = false): Logger {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.
|
|
1
|
+
export const SDK_VERSION = "0.11.0";
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.run = run;
|
|
37
|
-
const guava = __importStar(require("@guava-ai/guava-sdk"));
|
|
38
|
-
const openai_1 = require("@guava-ai/guava-sdk/helpers/openai");
|
|
39
|
-
const CUSTOMER_DB = [
|
|
40
|
-
{
|
|
41
|
-
name: "John Smith",
|
|
42
|
-
ssn: "123456789",
|
|
43
|
-
unactivated_cards: {
|
|
44
|
-
"6011002980139424": 567,
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
function findCustomerBySSN(ssn) {
|
|
49
|
-
return CUSTOMER_DB.find((c) => c.ssn === ssn);
|
|
50
|
-
}
|
|
51
|
-
const ORGANIZATION_NAME = "Harper Valley Bank";
|
|
52
|
-
class CreditCardActivationController extends guava.CallController {
|
|
53
|
-
choices = ["activate credit card", "anything else"];
|
|
54
|
-
intentRecognizer;
|
|
55
|
-
constructor(logger) {
|
|
56
|
-
super(logger);
|
|
57
|
-
this.intentRecognizer = new openai_1.IntentRecognizer(this.choices, logger);
|
|
58
|
-
this.setPersona({
|
|
59
|
-
organizationName: ORGANIZATION_NAME,
|
|
60
|
-
agentPurpose: `You are a customer service voice agent that activates credit cards for customers of ${ORGANIZATION_NAME}.`,
|
|
61
|
-
});
|
|
62
|
-
this.readScript(`Hello, thank you for calling the credit card activation line for ${ORGANIZATION_NAME}. My name is Grace. Are you here to activate your credit card?`);
|
|
63
|
-
this.acceptCall();
|
|
64
|
-
}
|
|
65
|
-
async onIntent(intent) {
|
|
66
|
-
const choice = await this.intentRecognizer.classify(intent);
|
|
67
|
-
this.logger.info(`Chosen intent: ${choice}`);
|
|
68
|
-
if (choice === "activate credit card") {
|
|
69
|
-
await this.activateCreditCard();
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
return "Unfortunately I'm not able to help with that.";
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async findCustomer() {
|
|
77
|
-
let customer;
|
|
78
|
-
let cardNumber;
|
|
79
|
-
while (true) {
|
|
80
|
-
await this.awaitTask({
|
|
81
|
-
checklist: [
|
|
82
|
-
guava.Field({
|
|
83
|
-
description: "Could you give me your social security number?",
|
|
84
|
-
key: "social_security_number",
|
|
85
|
-
fieldType: "integer",
|
|
86
|
-
required: true,
|
|
87
|
-
}),
|
|
88
|
-
],
|
|
89
|
-
});
|
|
90
|
-
const ssn_data = this.getField("social_security_number");
|
|
91
|
-
let ssn;
|
|
92
|
-
if (typeof ssn_data === "string") {
|
|
93
|
-
ssn = ssn_data;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
// Should we assume all payloads are strings? or leave room by returning unknown
|
|
97
|
-
ssn = JSON.stringify(ssn_data);
|
|
98
|
-
}
|
|
99
|
-
customer = findCustomerBySSN(ssn);
|
|
100
|
-
if (!customer) {
|
|
101
|
-
this.sendInstruction("We were unable to identify the customer using the SSN they provided. Let the caller know this, and ask if they have the correct social security number.");
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
await this.awaitTask({
|
|
105
|
-
objective: "We were able to identify the customer using the Social Security Number they have provided. We're going to confirm the client's name.",
|
|
106
|
-
checklist: [
|
|
107
|
-
guava.Field({
|
|
108
|
-
description: `We're going to confirm the client's name. Am I speaking with ${customer.name}?`,
|
|
109
|
-
key: "is_client",
|
|
110
|
-
fieldType: "multiple_choice",
|
|
111
|
-
choices: ["yes", "no"],
|
|
112
|
-
required: true,
|
|
113
|
-
}),
|
|
114
|
-
],
|
|
115
|
-
});
|
|
116
|
-
if (this.getField("is_client") === "no") {
|
|
117
|
-
this.sendInstruction("We were unable to identify the client's name in our files. Let the caller know this, and re-ask their social security number.");
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
this.sendInstruction("We were able to find the client's name in our files. Proceed to ask for their card number.");
|
|
125
|
-
while (true) {
|
|
126
|
-
await this.awaitTask({
|
|
127
|
-
checklist: [
|
|
128
|
-
guava.Field({
|
|
129
|
-
fieldType: "integer",
|
|
130
|
-
description: "Could you read me the digits on the front of your credit card?",
|
|
131
|
-
key: "credit_card_number",
|
|
132
|
-
required: true,
|
|
133
|
-
}),
|
|
134
|
-
],
|
|
135
|
-
});
|
|
136
|
-
cardNumber = this.getField("credit_card_number");
|
|
137
|
-
if (!(cardNumber in customer.unactivated_cards)) {
|
|
138
|
-
this.sendInstruction("We were unable to find the matching card number in our system. Let the caller know this, and re-ask for the credit card number.");
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
this.sendInstruction("We were able to find the matching card number in our system. Let the caller know this, and ask for security code on their card.");
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const correctCvv = customer.unactivated_cards[cardNumber];
|
|
146
|
-
while (true) {
|
|
147
|
-
await this.awaitTask({
|
|
148
|
-
checklist: [
|
|
149
|
-
guava.Field({
|
|
150
|
-
fieldType: "integer",
|
|
151
|
-
key: "security_code",
|
|
152
|
-
description: "To wrap up, could I get the security code on your card?",
|
|
153
|
-
required: true,
|
|
154
|
-
}),
|
|
155
|
-
],
|
|
156
|
-
});
|
|
157
|
-
const security_code = this.getField("security_code");
|
|
158
|
-
if (security_code !== correctCvv.toString()) {
|
|
159
|
-
this.sendInstruction("We were unable to match the security code to the credit card. Let the caller know this and re-ask for the security code.");
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
this.hangup("Explain to the caller that their credit card has now been activated. Thank them for using the bank's services, and hang up.");
|
|
166
|
-
}
|
|
167
|
-
async activateCreditCard() {
|
|
168
|
-
this.sendInstruction("We are starting the credit card activation process, which starts with asking the caller for their social security number.");
|
|
169
|
-
await this.findCustomer();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
async function run(_args) {
|
|
173
|
-
new guava.Client().listenInbound({
|
|
174
|
-
agent_number: process.env.GUAVA_AGENT_NUMBER,
|
|
175
|
-
}, (logger) => new CreditCardActivationController(logger));
|
|
176
|
-
}
|
|
177
|
-
//# sourceMappingURL=credit-card-activation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"credit-card-activation.js","sourceRoot":"","sources":["../../examples/credit-card-activation.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0KA,kBAOC;AAjLD,2DAA6C;AAC7C,+DAAsE;AAStE,MAAM,WAAW,GAAe;IAC9B;QACE,IAAI,EAAE,YAAY;QAClB,GAAG,EAAE,WAAW;QAChB,iBAAiB,EAAE;YACjB,kBAAkB,EAAE,GAAG;SACxB;KACF;CACF,CAAC;AAEF,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAE/C,MAAM,8BAA+B,SAAQ,KAAK,CAAC,cAAc;IACvD,OAAO,GAAG,CAAC,sBAAsB,EAAE,eAAe,CAAU,CAAC;IAC7D,gBAAgB,CAAwC;IAChE,YAAY,MAAc;QACxB,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,gBAAgB,GAAG,IAAI,yBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACnE,IAAI,CAAC,UAAU,CAAC;YACd,gBAAgB,EAAE,iBAAiB;YACnC,YAAY,EAAE,uFAAuF,iBAAiB,GAAG;SAC1H,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,CACb,oEAAoE,iBAAiB,gEAAgE,CACtJ,CAAC;QACF,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAEQ,KAAK,CAAC,QAAQ,CAAC,MAAc;QACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC5D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC;QAC7C,IAAI,MAAM,KAAK,sBAAsB,EAAE,CAAC;YACtC,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;aAAM,CAAC;YACN,OAAO,+CAA+C,CAAC;QACzD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,IAAI,QAA8B,CAAC;QACnC,IAAI,UAAkB,CAAC;QACvB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE;oBACT,KAAK,CAAC,KAAK,CAAC;wBACV,WAAW,EAAE,gDAAgD;wBAC7D,GAAG,EAAE,wBAAwB;wBAC7B,SAAS,EAAE,SAAS;wBACpB,QAAQ,EAAE,IAAI;qBACf,CAAC;iBACH;aACF,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC;YACzD,IAAI,GAAW,CAAC;YAChB,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACjC,GAAG,GAAG,QAAQ,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,gFAAgF;gBAChF,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;YACD,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,IAAI,CAAC,eAAe,CAClB,yJAAyJ,CAC1J,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,CAAC,SAAS,CAAC;oBACnB,SAAS,EACP,sIAAsI;oBACxI,SAAS,EAAE;wBACT,KAAK,CAAC,KAAK,CAAC;4BACV,WAAW,EAAE,gEAAgE,QAAQ,CAAC,IAAI,GAAG;4BAC7F,GAAG,EAAE,WAAW;4BAChB,SAAS,EAAE,iBAAiB;4BAC5B,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;4BACtB,QAAQ,EAAE,IAAI;yBACf,CAAC;qBACH;iBACF,CAAC,CAAC;gBAEH,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC;oBACxC,IAAI,CAAC,eAAe,CAClB,+HAA+H,CAChI,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,eAAe,CAClB,4FAA4F,CAC7F,CAAC;QACF,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE;oBACT,KAAK,CAAC,KAAK,CAAC;wBACV,SAAS,EAAE,SAAS;wBACpB,WAAW,EAAE,gEAAgE;wBAC7E,GAAG,EAAE,oBAAoB;wBACzB,QAAQ,EAAE,IAAI;qBACf,CAAC;iBACH;aACF,CAAC,CAAC;YAEH,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAW,CAAC;YAC3D,IAAI,CAAC,CAAC,UAAU,IAAI,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBAChD,IAAI,CAAC,eAAe,CAClB,iIAAiI,CAClI,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,eAAe,CAClB,iIAAiI,CAClI,CAAC;gBACF,MAAM;YACR,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAC1D,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE;oBACT,KAAK,CAAC,KAAK,CAAC;wBACV,SAAS,EAAE,SAAS;wBACpB,GAAG,EAAE,eAAe;wBACpB,WAAW,EAAE,yDAAyD;wBACtE,QAAQ,EAAE,IAAI;qBACf,CAAC;iBACH;aACF,CAAC,CAAC;YAEH,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAW,CAAC;YAC/D,IAAI,aAAa,KAAK,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC;gBAC5C,IAAI,CAAC,eAAe,CAClB,0HAA0H,CAC3H,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,CACT,6HAA6H,CAC9H,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,IAAI,CAAC,eAAe,CAClB,2HAA2H,CAC5H,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;IAC5B,CAAC;CACF;AAEM,KAAK,UAAU,GAAG,CAAC,KAAe;IACvC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC,aAAa,CAC9B;QACE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAmB;KAC9C,EACD,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,8BAA8B,CAAC,MAAM,CAAC,CACvD,CAAC;AACJ,CAAC"}
|