@guava-ai/guava-sdk 0.23.0 → 0.25.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 (91) hide show
  1. package/README.md +13 -3
  2. package/bin/example-runner.ts +138 -11
  3. package/dist/bin/example-runner.d.ts +23 -6
  4. package/dist/bin/example-runner.js +112 -9
  5. package/dist/bin/example-runner.js.map +1 -1
  6. package/dist/examples/{example.test.js → agent-testing.test.js} +2 -2
  7. package/dist/examples/agent-testing.test.js.map +1 -0
  8. package/dist/examples/help-desk.js +22 -8
  9. package/dist/examples/help-desk.js.map +1 -1
  10. package/dist/examples/multiple-agents.d.ts +1 -0
  11. package/dist/examples/multiple-agents.js +52 -0
  12. package/dist/examples/multiple-agents.js.map +1 -0
  13. package/dist/examples/property-insurance.js +14 -3
  14. package/dist/examples/property-insurance.js.map +1 -1
  15. package/dist/examples/restaurant-waitlist.js +9 -1
  16. package/dist/examples/restaurant-waitlist.js.map +1 -1
  17. package/dist/examples/scheduling-outbound.js +5 -6
  18. package/dist/examples/scheduling-outbound.js.map +1 -1
  19. package/dist/src/action-item.d.ts +4 -4
  20. package/dist/src/action-item.js +1 -1
  21. package/dist/src/action-item.js.map +1 -1
  22. package/dist/src/agent.d.ts +31 -11
  23. package/dist/src/agent.js +278 -135
  24. package/dist/src/agent.js.map +1 -1
  25. package/dist/src/call.d.ts +12 -1
  26. package/dist/src/call.js +87 -24
  27. package/dist/src/call.js.map +1 -1
  28. package/dist/src/client.d.ts +3 -0
  29. package/dist/src/client.js +6 -0
  30. package/dist/src/client.js.map +1 -1
  31. package/dist/src/commands.d.ts +37 -9
  32. package/dist/src/commands.js +13 -3
  33. package/dist/src/commands.js.map +1 -1
  34. package/dist/src/events.d.ts +28 -1
  35. package/dist/src/events.js +10 -2
  36. package/dist/src/events.js.map +1 -1
  37. package/dist/src/example-utils.d.ts +2 -0
  38. package/dist/src/example-utils.js +115 -0
  39. package/dist/src/example-utils.js.map +1 -0
  40. package/dist/src/health.d.ts +35 -0
  41. package/dist/src/health.js +99 -0
  42. package/dist/src/health.js.map +1 -0
  43. package/dist/src/helpers/index.d.ts +1 -0
  44. package/dist/src/helpers/index.js +9 -0
  45. package/dist/src/helpers/index.js.map +1 -0
  46. package/dist/src/helpers/llm.d.ts +43 -1
  47. package/dist/src/helpers/llm.js +247 -0
  48. package/dist/src/helpers/llm.js.map +1 -1
  49. package/dist/src/helpers/server-rag.d.ts +25 -0
  50. package/dist/src/helpers/server-rag.js +119 -0
  51. package/dist/src/helpers/server-rag.js.map +1 -0
  52. package/dist/src/index.d.ts +3 -1
  53. package/dist/src/index.js +4 -1
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/runner.d.ts +10 -0
  56. package/dist/src/runner.js +101 -0
  57. package/dist/src/runner.js.map +1 -0
  58. package/dist/src/socket/protocol.d.ts +4 -4
  59. package/dist/src/testing/mocks.d.ts +6 -0
  60. package/dist/src/testing/mocks.js +12 -0
  61. package/dist/src/testing/mocks.js.map +1 -1
  62. package/dist/src/testing/session.d.ts +7 -0
  63. package/dist/src/testing/session.js +3 -22
  64. package/dist/src/testing/session.js.map +1 -1
  65. package/dist/src/version.d.ts +1 -1
  66. package/dist/src/version.js +1 -1
  67. package/examples/{example.test.ts → agent-testing.test.ts} +1 -1
  68. package/examples/help-desk.ts +21 -12
  69. package/examples/multiple-agents.ts +18 -0
  70. package/examples/property-insurance.ts +15 -3
  71. package/examples/restaurant-waitlist.ts +10 -1
  72. package/examples/scheduling-outbound.ts +5 -6
  73. package/package.json +9 -1
  74. package/src/action-item.ts +1 -1
  75. package/src/agent.ts +273 -147
  76. package/src/call.ts +115 -24
  77. package/src/client.ts +7 -0
  78. package/src/commands.ts +16 -2
  79. package/src/events.ts +11 -1
  80. package/src/example-utils.ts +87 -0
  81. package/src/health.ts +120 -0
  82. package/src/helpers/index.ts +2 -0
  83. package/src/helpers/llm.ts +170 -1
  84. package/src/helpers/server-rag.ts +132 -0
  85. package/src/index.ts +3 -1
  86. package/src/runner.ts +39 -0
  87. package/src/testing/mocks.ts +17 -0
  88. package/src/testing/session.ts +2 -22
  89. package/src/version.ts +1 -1
  90. package/dist/examples/example.test.js.map +0 -1
  91. /package/dist/examples/{example.test.d.ts → agent-testing.test.d.ts} +0 -0
@@ -1,5 +1,9 @@
1
- import type { Client } from "../client.ts";
1
+ import * as z from "zod";
2
+ import type { SuggestedAction } from "../agent.ts";
3
+ import { Client } from "../client.ts";
4
+ import { telemetryClient } from "../telemetry.ts";
2
5
  import { fetchOrThrow } from "../utils.ts";
6
+ import { ServerRAG } from "./server-rag.ts";
3
7
 
4
8
  export async function _generate(
5
9
  client: Client,
@@ -14,7 +18,172 @@ export async function _generate(
14
18
  method: "POST",
15
19
  headers: { ...(await client.headers()), "Content-Type": "application/json" },
16
20
  body: JSON.stringify(body),
21
+ signal: AbortSignal.timeout(60_000),
17
22
  });
18
23
 
19
24
  return ((await response.json()) as { text: string }).text;
20
25
  }
26
+
27
+ @telemetryClient.trackClass()
28
+ export class DocumentQA {
29
+ private readonly _rag: ServerRAG;
30
+ private readonly _instructions: string | undefined;
31
+ private readonly _initialized: Promise<void>;
32
+
33
+ constructor({
34
+ documents,
35
+ ids,
36
+ instructions,
37
+ namespace,
38
+ }: {
39
+ documents?: string | string[];
40
+ ids?: string[];
41
+ instructions?: string;
42
+ namespace?: string;
43
+ } = {}) {
44
+ this._rag = new ServerRAG(new Client(), { namespace });
45
+ this._instructions = instructions;
46
+
47
+ if (documents !== undefined) {
48
+ const docs = typeof documents === "string" ? [documents] : documents;
49
+ this._initialized = this._rag.reconcile(docs, ids);
50
+ } else {
51
+ this._initialized = Promise.resolve();
52
+ }
53
+ }
54
+
55
+ async upsertDocument(key: string, text: string): Promise<void> {
56
+ await this._initialized;
57
+ await this._rag.upsertDocument(key, text);
58
+ }
59
+
60
+ async addDocument(text: string): Promise<void> {
61
+ await this._initialized;
62
+ await this._rag.addDocument(text);
63
+ }
64
+
65
+ async deleteDocument(key: string): Promise<void> {
66
+ await this._initialized;
67
+ await this._rag.removeDocument(key);
68
+ }
69
+
70
+ async clear(): Promise<void> {
71
+ await this._initialized;
72
+ await this._rag.clear();
73
+ }
74
+
75
+ async ask(question: string): Promise<string> {
76
+ await this._initialized;
77
+ const trackedKeys = this._rag.trackedKeys;
78
+ return this._rag.ask(question, {
79
+ documentKeys: trackedKeys.size > 0 ? [...trackedKeys] : undefined,
80
+ instructions: this._instructions,
81
+ });
82
+ }
83
+ }
84
+
85
+ export const _filterSchema = z.object({
86
+ matching_appointments: z.array(z.string()),
87
+ other_appointments: z.array(z.string()),
88
+ });
89
+
90
+ @telemetryClient.trackClass()
91
+ export class DatetimeFilter {
92
+ private readonly _client: Client;
93
+ private readonly _slotsStr: string;
94
+
95
+ constructor(sourceList: string[]) {
96
+ this._client = new Client();
97
+ this._slotsStr = sourceList.join("\n");
98
+ }
99
+
100
+ async filter(
101
+ query: string,
102
+ { maxResults = 5 }: { maxResults?: number } = {},
103
+ ): Promise<[string[], string[]]> {
104
+ const today = new Date().toLocaleDateString("en-US", {
105
+ month: "long",
106
+ day: "numeric",
107
+ year: "numeric",
108
+ });
109
+
110
+ const prompt = `Return datetime slots from the list that match the query.
111
+ If none match, return close alternatives in other_appointments instead.
112
+ Never return datetimes that are not in the list.
113
+
114
+ Query: <query>${query}</query>
115
+ Today's Date: ${today}
116
+ Available slots:
117
+ ${this._slotsStr}
118
+
119
+ Return at most ${maxResults} items per list.`;
120
+
121
+ const text = await _generate(this._client, prompt, z.toJSONSchema(_filterSchema));
122
+ const result = _filterSchema.parse(JSON.parse(text));
123
+ return [
124
+ result.matching_appointments.slice(0, maxResults),
125
+ result.other_appointments.slice(0, maxResults),
126
+ ];
127
+ }
128
+ }
129
+
130
+ export function _makeIntentSchema(choiceList: [string, ...string[]]) {
131
+ return z
132
+ .object({
133
+ possible_matches: z
134
+ .array(z.enum(choiceList))
135
+ .describe(
136
+ "Choices that could match the caller's intent, ordered by likelihood. Include all plausible matches.",
137
+ ),
138
+ })
139
+ .meta({ title: "ChoiceModel" });
140
+ }
141
+
142
+ @telemetryClient.trackClass()
143
+ export class IntentRecognizer {
144
+ private readonly _client: Client;
145
+ private readonly _intentChoices: string[] | Record<string, string>;
146
+ private readonly _schema: ReturnType<typeof _makeIntentSchema>;
147
+
148
+ constructor(intentChoices: string[] | Record<string, string>) {
149
+ this._client = new Client();
150
+ this._intentChoices = intentChoices;
151
+ const choiceList = Array.isArray(intentChoices) ? intentChoices : Object.keys(intentChoices);
152
+ this._schema = _makeIntentSchema(choiceList as [string, ...string[]]);
153
+ }
154
+
155
+ async classify(intent: string): Promise<SuggestedAction[] | null> {
156
+ const choiceList = Array.isArray(this._intentChoices)
157
+ ? this._intentChoices
158
+ : Object.keys(this._intentChoices);
159
+
160
+ let prompt = `Classify the intent below into the most appropriate choice(s) from the list.
161
+
162
+ Intent: <intent>${intent}</intent>
163
+ Available Choices: ${JSON.stringify(choiceList)}
164
+
165
+ Rules:
166
+ - Default to returning a single choice — the one that best matches the intent.
167
+ - Only return additional choices when the intent is genuinely ambiguous: a reasonable person reading it would be unable to decide which category it belongs to. Thematic overlap or partial relevance is NOT enough — do not include weakly or tangentially related choices.
168
+ - Order matches by likelihood (most likely first).
169
+ - If no choice plausibly matches, return an empty list.`;
170
+
171
+ if (!Array.isArray(this._intentChoices)) {
172
+ const descriptionString = Object.entries(this._intentChoices)
173
+ .map(([key, val]) => `${key}: ${val}`)
174
+ .join("\n ");
175
+ prompt += `\n\nDetailed descriptions of each choice:\n ${descriptionString}`;
176
+ }
177
+
178
+ const text = await _generate(this._client, prompt, z.toJSONSchema(this._schema));
179
+ const { possible_matches: keys } = this._schema.parse(JSON.parse(text));
180
+
181
+ if (!keys.length) return null;
182
+
183
+ if (!Array.isArray(this._intentChoices)) {
184
+ const choices = this._intentChoices;
185
+ return keys.map((k) => ({ key: k, description: choices[k] }));
186
+ }
187
+ return keys.map((k) => ({ key: k }));
188
+ }
189
+ }
@@ -0,0 +1,132 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { Client } from "../client.ts";
3
+ import { fetchOrThrow } from "../utils.ts";
4
+
5
+ export function _contentKey(text: string): string {
6
+ return createHash("sha256").update(text).digest("hex").slice(0, 16);
7
+ }
8
+
9
+ export function _prefixedKey(namespace: string | undefined, key: string): string {
10
+ return namespace ? `${namespace}.${key}` : key;
11
+ }
12
+
13
+ export class ServerRAG {
14
+ private readonly _client: Client;
15
+ private readonly _namespace: string | undefined;
16
+ readonly trackedKeys = new Set<string>();
17
+
18
+ constructor(client: Client, { namespace }: { namespace?: string } = {}) {
19
+ this._client = client;
20
+ this._namespace = namespace;
21
+ }
22
+
23
+ async uploadDocument(key: string, text: string): Promise<void> {
24
+ const url = new URL("v1/rag/documents", this._client.getHttpBase());
25
+ await fetchOrThrow(url, {
26
+ method: "POST",
27
+ headers: { ...(await this._client.headers()), "Content-Type": "application/json" },
28
+ body: JSON.stringify({ key, text }),
29
+ signal: AbortSignal.timeout(60_000),
30
+ });
31
+ }
32
+
33
+ async deleteDocument(key: string): Promise<void> {
34
+ const url = new URL(`v1/rag/documents/${key}`, this._client.getHttpBase());
35
+ await fetchOrThrow(url, {
36
+ method: "DELETE",
37
+ headers: await this._client.headers(),
38
+ signal: AbortSignal.timeout(30_000),
39
+ });
40
+ }
41
+
42
+ async listDocuments(): Promise<{ key: string }[]> {
43
+ const url = new URL("v1/rag/documents", this._client.getHttpBase());
44
+ const response = await fetchOrThrow(url, {
45
+ headers: await this._client.headers(),
46
+ signal: AbortSignal.timeout(30_000),
47
+ });
48
+ return (await response.json()) as { key: string }[];
49
+ }
50
+
51
+ async ask(
52
+ question: string,
53
+ { documentKeys, instructions }: { documentKeys?: string[]; instructions?: string } = {},
54
+ ): Promise<string> {
55
+ const url = new URL("v1/rag/ask", this._client.getHttpBase());
56
+ const payload: Record<string, unknown> = { question };
57
+ if (documentKeys !== undefined) payload.document_keys = documentKeys;
58
+ if (instructions !== undefined) payload.instructions = instructions;
59
+
60
+ const response = await fetchOrThrow(url, {
61
+ method: "POST",
62
+ headers: { ...(await this._client.headers()), "Content-Type": "application/json" },
63
+ body: JSON.stringify(payload),
64
+ signal: AbortSignal.timeout(120_000),
65
+ });
66
+ const result = (await response.json()) as { answer: string; warning?: string };
67
+ if (result.warning) console.warn(`Guava RAG: ${result.warning}`);
68
+ return result.answer;
69
+ }
70
+
71
+ async reconcile(documents: string[], ids?: string[]): Promise<void> {
72
+ if (ids !== undefined && ids.length !== documents.length) {
73
+ throw new Error(
74
+ `ids length (${ids.length}) must match documents length (${documents.length})`,
75
+ );
76
+ }
77
+
78
+ const desired = new Map<string, string>();
79
+ if (ids !== undefined) {
80
+ for (let i = 0; i < documents.length; i++) {
81
+ desired.set(_prefixedKey(this._namespace, ids[i]!), documents[i]!);
82
+ }
83
+ } else {
84
+ for (const doc of documents) {
85
+ desired.set(_prefixedKey(this._namespace, _contentKey(doc)), doc);
86
+ }
87
+ }
88
+
89
+ const existing = await this.listDocuments();
90
+ const existingKeys = new Set(existing.map((d) => d.key));
91
+ const scopedKeys = this._namespace
92
+ ? new Set([...existingKeys].filter((k) => k.startsWith(`${this._namespace}.`)))
93
+ : existingKeys;
94
+
95
+ for (const [key, doc] of desired) {
96
+ if (ids !== undefined || !existingKeys.has(key)) {
97
+ await this.uploadDocument(key, doc);
98
+ }
99
+ this.trackedKeys.add(key);
100
+ }
101
+
102
+ const stale = [...scopedKeys].filter((k) => !desired.has(k));
103
+ for (const key of stale) {
104
+ await this.deleteDocument(key);
105
+ }
106
+ }
107
+
108
+ async upsertDocument(key: string, text: string): Promise<void> {
109
+ const fullKey = _prefixedKey(this._namespace, key);
110
+ await this.uploadDocument(fullKey, text);
111
+ this.trackedKeys.add(fullKey);
112
+ }
113
+
114
+ async addDocument(text: string): Promise<void> {
115
+ const fullKey = _prefixedKey(this._namespace, _contentKey(text));
116
+ await this.uploadDocument(fullKey, text);
117
+ this.trackedKeys.add(fullKey);
118
+ }
119
+
120
+ async removeDocument(key: string): Promise<void> {
121
+ const fullKey = _prefixedKey(this._namespace, key);
122
+ await this.deleteDocument(fullKey);
123
+ this.trackedKeys.delete(fullKey);
124
+ }
125
+
126
+ async clear(): Promise<void> {
127
+ for (const key of this.trackedKeys) {
128
+ await this.deleteDocument(key);
129
+ }
130
+ this.trackedKeys.clear();
131
+ }
132
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export { Client, type InboundConnection } from "./client.ts";
2
+ export { Runner } from "./runner.ts";
2
3
  export type { SmsMessage } from "./sms.ts";
3
4
  export { CallController, type TaskObjective } from "./call-controller.ts";
4
5
  export { Say, Field } from "./action-item.ts";
5
6
  export { Logger, getConsoleLogger, getDefaultLogger } from "./logging.ts";
6
7
  export { Agent, CallInfo } from "./agent.ts";
7
- export { Call } from "./call.ts";
8
+ export { Call, DEFAULT_REACH_PERSON_OUTCOMES } from "./call.ts";
9
+ export type { ReachPersonOutcome } from "./call.ts";
8
10
  export type { BotSessionEnded, TerminationReason, DTMFPressedEvent, DTMFDigit } from "./events.ts";
9
11
  export { TestSession } from "./testing/session.ts";
10
12
  export { MockCall } from "./testing/mocks.ts";
package/src/runner.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { Agent } from "./agent.ts";
2
+ import { MultiHealthContext, getHealthServer } from "./health.ts";
3
+
4
+ export class Runner {
5
+ private _healthCtx = new MultiHealthContext();
6
+ private _tasks: Array<() => Promise<void>> = [];
7
+
8
+ listenPhone(agent: Agent, agentNumber: string): this {
9
+ const ctx = this._healthCtx.createCtx();
10
+ this._tasks.push(() => agent._listenInbound(ctx, { agent_number: agentNumber }));
11
+ return this;
12
+ }
13
+
14
+ listenWebrtc(agent: Agent, webrtcCode?: string): this {
15
+ const ctx = this._healthCtx.createCtx();
16
+ this._tasks.push(async () => {
17
+ const code = webrtcCode ?? (await agent._client.createWebrtcAgent(3600));
18
+ return agent._listenInbound(ctx, { webrtc_code: code });
19
+ });
20
+ return this;
21
+ }
22
+
23
+ listenSip(agent: Agent, sipCode: string): this {
24
+ const ctx = this._healthCtx.createCtx();
25
+ this._tasks.push(() => agent._listenInbound(ctx, { sip_code: sipCode }));
26
+ return this;
27
+ }
28
+
29
+ attachCampaign(agent: Agent, campaignCode: string): this {
30
+ const ctx = this._healthCtx.createCtx();
31
+ this._tasks.push(() => agent._serveCampaign(ctx, campaignCode));
32
+ return this;
33
+ }
34
+
35
+ async run(): Promise<void> {
36
+ await using _server = await getHealthServer(this._healthCtx);
37
+ await Promise.all(this._tasks.map((task) => task()));
38
+ }
39
+ }
@@ -1,7 +1,24 @@
1
+ import crypto from "node:crypto";
1
2
  import { Call } from "../call.ts";
3
+ import type { CallInfo } from "../socket/call-info.ts";
2
4
  import type { Command } from "../commands.ts";
3
5
 
4
6
  export class MockCall extends Call {
7
+ constructor(
8
+ args: { callId?: string; callInfo?: CallInfo; variables?: Record<string, any> } = {},
9
+ ) {
10
+ super(
11
+ args.callId ?? `mock-${crypto.randomBytes(6).toString("hex")}`,
12
+ args.callInfo ?? {
13
+ call_type: "pstn",
14
+ from_number: null,
15
+ to_number: "+15555555555",
16
+ caller_id: null,
17
+ },
18
+ args.variables ?? {},
19
+ );
20
+ }
21
+
5
22
  get commands(): Command[] {
6
23
  return this._commandQueue;
7
24
  }
@@ -7,7 +7,7 @@ import { _generate } from "../helpers/llm.ts";
7
7
 
8
8
  type SessionEvent = InjectASR | BotTTS;
9
9
 
10
- const _evalResponseSchema = z.object({
10
+ export const _evalResponseSchema = z.object({
11
11
  results: z.array(
12
12
  z.object({
13
13
  passed: z.boolean(),
@@ -166,28 +166,8 @@ ${transcript || "(empty — no conversation occurred)"}
166
166
  Criteria:
167
167
  ${criteriaList}`;
168
168
 
169
- const evalSchema = {
170
- type: "object",
171
- properties: {
172
- results: {
173
- type: "array",
174
- items: {
175
- type: "object",
176
- properties: {
177
- passed: { type: "boolean" },
178
- reasoning: { type: "string" },
179
- },
180
- required: ["passed"],
181
- additionalProperties: false,
182
- },
183
- },
184
- },
185
- required: ["results"],
186
- additionalProperties: false,
187
- };
188
-
189
169
  const evalResponse = _evalResponseSchema.parse(
190
- JSON.parse(await _generate(this._client, prompt, evalSchema)),
170
+ JSON.parse(await _generate(this._client, prompt, z.toJSONSchema(_evalResponseSchema))),
191
171
  );
192
172
 
193
173
  if (evalResponse.results.length !== allCriteria.length) {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.23.0";
1
+ export const SDK_VERSION = "0.25.0";
@@ -1 +0,0 @@
1
- {"version":3,"file":"example.test.js","sourceRoot":"","sources":["../../examples/example.test.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAGH,mDAA+C;AAC/C,sEAA+D;AAE/D,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,UAAU,GAAG,MAAM,iBAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,oBAAQ,EAAE,EAAE,qBAAqB,CAAC,CAAC;QAC/F,MAAM,CAAC,UAAU,CAAC,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,OAAO,GAAG,MAAM,iBAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YACjD,iFAAiF;YACjF,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;YAEvD,6CAA6C;YAC7C,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,mEAAmE;QAEnE,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAChD,mFAAmF;QACnF,MAAM,OAAO,GAAG,MAAM,iBAAK,CAAC,YAAY,CAAC,6CAA6C,CAAC,CAAC;QAExF,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACzD,4EAA4E;QAC5E,MAAM,OAAO,GAAG,iBAAK,CAAC,KAAK,EAAE,CAAC;QAE9B,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,IAAU,EAAE,EAAE;YAC7C,IAAI,CAAC,MAAM,CACT,8GAA8G,CAC/G,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,6CAA6C,CAAC,CAAC;QAE1F,8FAA8F;QAC9F,MAAM,OAAO,CAAC,QAAQ,CAAC;YACrB,YAAY,EAAE,CAAC,sEAAsE,CAAC;YACtF,YAAY,EAAE,CAAC,2DAA2D,CAAC;SAC5E,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}