@jsm-mit/sultana-agent-tools-package 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 (44) hide show
  1. package/README.md +106 -8
  2. package/dist/confirmations.d.ts +61 -0
  3. package/dist/confirmations.d.ts.map +1 -0
  4. package/dist/confirmations.js +100 -0
  5. package/dist/create-salon-tools.d.ts +13 -2
  6. package/dist/create-salon-tools.d.ts.map +1 -1
  7. package/dist/create-salon-tools.js +10 -4
  8. package/dist/customer/create-customer-tools.d.ts +38 -0
  9. package/dist/customer/create-customer-tools.d.ts.map +1 -0
  10. package/dist/customer/create-customer-tools.js +47 -0
  11. package/dist/customer/customer-core-port.d.ts +94 -0
  12. package/dist/customer/customer-core-port.d.ts.map +1 -0
  13. package/dist/customer/customer-core-port.js +1 -0
  14. package/dist/customer/ic-customer-core-port.d.ts +45 -0
  15. package/dist/customer/ic-customer-core-port.d.ts.map +1 -0
  16. package/dist/customer/ic-customer-core-port.js +132 -0
  17. package/dist/customer/persona.d.ts +6 -0
  18. package/dist/customer/persona.d.ts.map +1 -0
  19. package/dist/customer/persona.js +27 -0
  20. package/dist/customer/time.d.ts +27 -0
  21. package/dist/customer/time.d.ts.map +1 -0
  22. package/dist/customer/time.js +113 -0
  23. package/dist/customer/tools.d.ts +17 -0
  24. package/dist/customer/tools.d.ts.map +1 -0
  25. package/dist/customer/tools.js +415 -0
  26. package/dist/index.d.ts +10 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -0
  29. package/dist/persona.d.ts +4 -1
  30. package/dist/persona.d.ts.map +1 -1
  31. package/dist/persona.js +18 -7
  32. package/dist/tools/promos.d.ts +2 -1
  33. package/dist/tools/promos.d.ts.map +1 -1
  34. package/dist/tools/promos.js +79 -39
  35. package/dist/tools/schedule.d.ts +2 -1
  36. package/dist/tools/schedule.d.ts.map +1 -1
  37. package/dist/tools/schedule.js +51 -31
  38. package/dist/tools/services.d.ts +4 -2
  39. package/dist/tools/services.d.ts.map +1 -1
  40. package/dist/tools/services.js +75 -30
  41. package/dist/tools/write-gate.d.ts +29 -0
  42. package/dist/tools/write-gate.d.ts.map +1 -0
  43. package/dist/tools/write-gate.js +37 -0
  44. package/package.json +4 -3
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # sultana-agent-tools-package
2
2
 
3
- `@jsm-mit/sultana-agent-tools-package` — the tool layer that lets an LLM agent edit a Sultana
4
- salon by conversation: services, promos and working hours.
3
+ `@jsm-mit/sultana-agent-tools-package` — the tool layer that lets an LLM agent work in Sultana by
4
+ conversation: a salon owner's set (services, promos and working hours) and a customer's set
5
+ (search, free times, booking on the customer's behalf).
5
6
 
6
7
  It is **framework-agnostic**. A tool is `{ name, description, parameters, progress, execute }`, which is what
7
8
  OpenAI, Anthropic and `@jsm-mit/chat-agent-package` all want from a function tool, so wiring this
@@ -23,6 +24,55 @@ const tools = createSalonTools({
23
24
  `salonId` is captured at construction and is **never a tool argument** — a model holding these
24
25
  tools cannot reach another salon's data whatever it sends.
25
26
 
27
+ ### Confirmations across turns
28
+
29
+ A host that runs the owner set in a chat keeps one `ConfirmationLedger` per conversation and builds
30
+ the tools again for every incoming owner message, with that message's turn:
31
+
32
+ ```ts
33
+ import { ConfirmationLedger, createSalonTools, SALON_AGENT_PERSONA_PL } from "@jsm-mit/sultana-agent-tools-package";
34
+
35
+ const ledger = new ConfirmationLedger(); // one per conversation, kept in memory
36
+
37
+ // on every owner message — one agent turn:
38
+ const tools = createSalonTools({ canisterId, identity, salonId, confirmations: ledger.forTurn() });
39
+ ```
40
+
41
+ With `confirmations`, the two-step protocol is enforced by the tools, not only asked for by the
42
+ persona:
43
+
44
+ 1. `confirmed: false` makes no canister write and returns `confirmation_required` with a
45
+ `summary` and an `echo` that now carries a `confirmationId` (`c-` and 8 hex digits).
46
+ 2. `confirmed: true` goes through only with that `confirmationId`, the **same arguments** (key
47
+ order does not matter, array order does; `confirmed`, `confirmationId` and
48
+ `confirmationPhrase` are not compared), from a **later turn**, within 15 minutes, and once.
49
+ Anything else is `status: "error"`, `code: "invalid_arguments"`, returned before the tool calls
50
+ the canister at all:
51
+
52
+ | why | summary |
53
+ |---|---|
54
+ | same turn as the preview | Zgoda musi przyjść w osobnej wiadomości właścicielki — najpierw pokaż podsumowanie i poczekaj na odpowiedź. |
55
+ | no id, unknown, used or expired | Brak ważnego podglądu tej zmiany — wywołaj narzędzie z confirmed=false i pokaż właścicielce podsumowanie. |
56
+ | other tool or other arguments | Argumenty różnią się od pokazanego podglądu — pokaż nowy podgląd (confirmed=false). |
57
+
58
+ Every write tool's schema gets a `confirmationId` parameter when a ledger is passed; without one,
59
+ the schemas and the protocol are what they were in 0.2.0 (a model can still confirm in the same
60
+ breath — only the persona stops it). The id is used up right before the write, not at the first
61
+ check: a confirmation refused for a mistyped name can be sent again with the same id once the owner
62
+ has typed it right. Two parallel calls with one id cannot both write: the second is refused right before its write.
63
+
64
+ `ConfirmationLedger` takes `{ ttlMs, now, newId }` for tests; `createSalonToolsFromPort(port,
65
+ { confirmations })` is the same over any port. `forTurn()` returns `TurnConfirmations` —
66
+ `issue(toolName, args)`, `check(toolName, args, id)` (the verdict, the id kept) and
67
+ `redeem(toolName, args, id)` (the verdict, the id used up) — for a host that wants the protocol
68
+ around tools of its own.
69
+
70
+ `SALON_AGENT_PERSONA_PL` is written for this: every write first with `confirmed=false`, the
71
+ summary shown, the owner's answer awaited in her **next** message, then `confirmed=true` with the
72
+ `confirmationId` and the same arguments; a removal only with the name the owner typed herself; and
73
+ with read tools only, the agent says that changes need the "Salon Assistant" page open in the
74
+ panel.
75
+
26
76
  ### Read-only set
27
77
 
28
78
  An assistant that runs without the owner's identity takes the read-only set and its own persona:
@@ -79,17 +129,62 @@ never sent to the model. A write tool previewing with `confirmed: false` shows t
79
129
  `createSalonTools` returns all 21; `createSalonReadTools` returns `list_services`, `list_promos`
80
130
  and `find_service_type`.
81
131
 
132
+ ## Customer tools
133
+
134
+ The second set works for ONE customer instead of one salon: it searches across all salons and
135
+ books, lists and cancels visits **on the customer's behalf**. The host passes the customer's lent
136
+ identity (an Internet Identity delegation) and the position the app sent; neither is ever a tool
137
+ argument, so a model holding these tools can act for nobody else and search around nowhere else.
138
+
139
+ ```ts
140
+ import { createCustomerTools, createCustomerReadTools, CUSTOMER_AGENT_PERSONA_PL } from "@jsm-mit/sultana-agent-tools-package";
141
+
142
+ const tools = session
143
+ ? createCustomerTools({ canisterId, identity: session.identity, location: session.location, confirmations: ledger.forTurn() })
144
+ : createCustomerReadTools({ canisterId, location }); // no identity: search only, nothing books
145
+ ```
146
+
147
+ | tool | writes | needs identity | progress |
148
+ |---|---|---|---|
149
+ | `find_service_type` | | | Szukam typu usługi… |
150
+ | `search_slots` | | | Szukam wolnych terminów w okolicy… |
151
+ | `get_salon` | | | Sprawdzam salon… |
152
+ | `get_free_times` | | | Sprawdzam wolne godziny… |
153
+ | `book_appointment` | yes | yes | Rezerwuję wizytę… |
154
+ | `list_my_appointments` | | yes | Sprawdzam Twoje wizyty… |
155
+ | `cancel_appointment` | yes | yes | Odwołuję wizytę… |
156
+
157
+ - `search_slots` without `date`/`time` is the package's "as soon as possible" search (the next
158
+ hour, else up to 24 h ahead); with them it probes from 30 min before to 60 min after the moment.
159
+ Results: at most 6 salons, soonest first, each with its services and up to 6 free moments.
160
+ - A model reads and sends local time: `date` `"YYYY-MM-DD"`, `time` `"HH:mm"`.
161
+ `get_free_times` folds the 5-minute start grid into ranges.
162
+ - `book_appointment` picks the first free worker, like the app, and checks the slot on the preview
163
+ AND on the confirmed call — a booking the canister refuses still spends the customer's daily quota.
164
+ - The public reads go out anonymously even with an identity; only booking, the visits and
165
+ cancelling are signed.
166
+ - **The host process must run in the salons' time zone** (`TZ=Europe/Warsaw`): the canister wrapper
167
+ takes every offset from the process clock. While it does not, every time-dependent tool answers
168
+ `unavailable` instead of booking hours off (`timeZone: null` skips the guard; `hostTimeZoneProblem`
169
+ lets a host check at start-up).
170
+
82
171
  ## What the tools guarantee
83
172
 
84
173
  - **`execute` never throws.** Every call returns `{status: "ok" | "confirmation_required" | "error"}`
85
174
  so an agent can read a failure and talk about it.
86
175
  - **Confirmation is a schema field, not a prompt rule.** Called with `confirmed: false`, a write
87
- tool makes no canister call and returns a sentence built from *resolved* arguments — worker names
88
- and service-type labels already looked up — plus the exact arguments to send back. A persona can
89
- be talked out of a rule; a required field cannot.
176
+ tool makes no canister write and returns a sentence built from *resolved* arguments — worker names
177
+ and service-type labels already looked up — plus the arguments to send back in `echo`. A persona
178
+ can be talked out of a rule; a required field cannot. With a `ConfirmationLedger` (above) the
179
+ confirmation must also come from a later turn, with the preview's id.
90
180
  - **Irreversible operations need the entity's own name** typed back in `confirmationPhrase`.
91
- - **A partial edit is merged.** `updateSalonService` replaces the whole record on the canister, so
92
- "just change the price" would otherwise unassign the team.
181
+ `remove_service` and `remove_promo` leave the name OUT of `echo`, and their summary asks the
182
+ model to have the owner write it: a model that just sends the echo back deletes nothing.
183
+ - **A partial edit is merged — at write time.** `updateSalonService` replaces the whole record on
184
+ the canister, so "just change the price" would otherwise unassign the team. The preview summary
185
+ shows the merged record, but `update_service`'s `echo` carries only `salonServiceId` and the
186
+ fields the model sent; the confirmed call reads the record again and merges then, so a change
187
+ made in the panel between preview and confirmation is kept.
93
188
  - **Units are fixed at the boundary**: price is whole zloty, duration is minutes rounded up to a
94
189
  multiple of 5 (the availability grid), and the tool says when it rounded.
95
190
  - **Service-type ids come from the catalogue.** `find_service_type` uses the same matcher as both
@@ -105,13 +200,16 @@ and `find_service_type`.
105
200
  | `npm run sandbox -- --fake` | talk to the tools against an in-memory salon |
106
201
  | `npm run sandbox` | the same conversation against a real canister |
107
202
  | `npm run test-tools` | the round trip against a live canister (creates its own salon) |
203
+ | `npm run test-customer-tools` | the customer's round trip against a live, seeded canister: search, book, list, cancel (a fresh identity per run) |
108
204
  | `npm run publish-public` | publishes to npm from master — see below |
109
205
 
110
206
  ## Testing by talking
111
207
 
112
208
  `npm run sandbox -- --fake` is the loop to iterate in: a real model, real tool calls, an in-memory
113
209
  salon. Every call is printed with its arguments and its result, so a tool description that misleads
114
- the model shows up immediately — and a wrong answer costs no cycles and no daily action quota.
210
+ the model shows up immediately — and a wrong answer costs no cycles and no daily action quota. The
211
+ sandbox builds the tools again for every line you type, with a turn of one `ConfirmationLedger`,
212
+ as a host does — so the confirmation id and the next-message rule are in play there too.
115
213
 
116
214
  Drop `--fake` to run the same conversation against a canister with the owner's identity
117
215
  (`OWNER1_PEM`). Start with `npm run whoami`: it prints the principal the PEM makes you, the salons
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The two-step write protocol, enforced in code rather than by the persona.
3
+ *
4
+ * A write tool called with `confirmed: false` issues a confirmation id for exactly those arguments.
5
+ * The write itself goes through only when a LATER turn — the owner's next message — sends the same
6
+ * arguments back with that id. A model that previews and confirms in one breath, confirms
7
+ * something it never showed, or reuses an id, is refused before any canister call.
8
+ *
9
+ * The ledger lives in memory next to the conversation. A host keeps one ledger per conversation and
10
+ * calls `forTurn()` once per incoming owner message, when it builds that turn's tools.
11
+ */
12
+ export declare const DEFAULT_CONFIRMATION_TTL_MS: number;
13
+ export interface ConfirmationLedgerOptions {
14
+ /** How long a preview can still be confirmed. Defaults to 15 minutes. */
15
+ ttlMs?: number;
16
+ /** Milliseconds since the epoch. For tests. */
17
+ now?: () => number;
18
+ /** Confirmation id generator. For tests. */
19
+ newId?: () => string;
20
+ }
21
+ export type ConfirmationRefusal =
22
+ /** No `confirmationId` was sent. */
23
+ "missing"
24
+ /** The id was never issued, was already used, or was cleared out after expiring. */
25
+ | "unknown"
26
+ /** The id comes from this very turn — the owner has not answered the preview yet. */
27
+ | "same_turn"
28
+ /** The preview is older than the ledger's TTL. */
29
+ | "expired"
30
+ /** The id was issued for another tool or other arguments. */
31
+ | "mismatch";
32
+ export type RedeemResult = {
33
+ ok: true;
34
+ } | {
35
+ ok: false;
36
+ reason: ConfirmationRefusal;
37
+ };
38
+ /** One turn's view of the ledger. Ids issued here can only be redeemed from a later turn. */
39
+ export interface TurnConfirmations {
40
+ /** Records a preview of `toolName` with `args` and returns its id (`c-` and 8 hex digits). */
41
+ issue(toolName: string, args: Record<string, unknown>): string;
42
+ /** Whether `redeem` would succeed now — without using the id up. An expired entry is cleared. */
43
+ check(toolName: string, args: Record<string, unknown>, confirmationId: string | undefined): RedeemResult;
44
+ /** The same verdict as `check`; on success the id is used up and cannot confirm anything again. */
45
+ redeem(toolName: string, args: Record<string, unknown>, confirmationId: string | undefined): RedeemResult;
46
+ }
47
+ export declare class ConfirmationLedger {
48
+ private readonly ttlMs;
49
+ private readonly now;
50
+ private readonly newId;
51
+ private readonly pending;
52
+ private turn;
53
+ constructor(options?: ConfirmationLedgerOptions);
54
+ /** Starts a new turn. Call it once per incoming owner message and hand the result to that
55
+ * turn's tools; every call is a later turn than the one before. */
56
+ forTurn(): TurnConfirmations;
57
+ private issue;
58
+ private verdict;
59
+ private isExpired;
60
+ }
61
+ //# sourceMappingURL=confirmations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirmations.d.ts","sourceRoot":"","sources":["../src/confirmations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,2BAA2B,QAAiB,CAAC;AAK1D,MAAM,WAAW,yBAAyB;IACtC,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB;AAC3B,oCAAoC;AAClC,SAAS;AACX,oFAAoF;GAClF,SAAS;AACX,qFAAqF;GACnF,WAAW;AACb,kDAAkD;GAChD,SAAS;AACX,6DAA6D;GAC3D,UAAU,CAAC;AAEjB,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAErF,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAC9B,8FAA8F;IAC9F,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAC/D,iGAAiG;IACjG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;IACzG,mGAAmG;IACnG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;CAC7G;AASD,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;IAClE,OAAO,CAAC,IAAI,CAAK;gBAEL,OAAO,GAAE,yBAA8B;IAMnD;uEACmE;IAC5D,OAAO,IAAI,iBAAiB;IAWnC,OAAO,CAAC,KAAK;IAeb,OAAO,CAAC,OAAO;IA6Bf,OAAO,CAAC,SAAS;CAGpB"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The two-step write protocol, enforced in code rather than by the persona.
3
+ *
4
+ * A write tool called with `confirmed: false` issues a confirmation id for exactly those arguments.
5
+ * The write itself goes through only when a LATER turn — the owner's next message — sends the same
6
+ * arguments back with that id. A model that previews and confirms in one breath, confirms
7
+ * something it never showed, or reuses an id, is refused before any canister call.
8
+ *
9
+ * The ledger lives in memory next to the conversation. A host keeps one ledger per conversation and
10
+ * calls `forTurn()` once per incoming owner message, when it builds that turn's tools.
11
+ */
12
+ export const DEFAULT_CONFIRMATION_TTL_MS = 15 * 60 * 1000;
13
+ /** Argument keys that carry the protocol, not the change — never part of what is compared. */
14
+ const PROTOCOL_KEYS = new Set(["confirmed", "confirmationId", "confirmationPhrase"]);
15
+ export class ConfirmationLedger {
16
+ ttlMs;
17
+ now;
18
+ newId;
19
+ pending = new Map();
20
+ turn = 0;
21
+ constructor(options = {}) {
22
+ this.ttlMs = options.ttlMs ?? DEFAULT_CONFIRMATION_TTL_MS;
23
+ this.now = options.now ?? Date.now;
24
+ this.newId = options.newId ?? randomConfirmationId;
25
+ }
26
+ /** Starts a new turn. Call it once per incoming owner message and hand the result to that
27
+ * turn's tools; every call is a later turn than the one before. */
28
+ forTurn() {
29
+ this.turn += 1;
30
+ const turn = this.turn;
31
+ return {
32
+ issue: (toolName, args) => this.issue(turn, toolName, args),
33
+ check: (toolName, args, confirmationId) => this.verdict(turn, toolName, args, confirmationId, false),
34
+ redeem: (toolName, args, confirmationId) => this.verdict(turn, toolName, args, confirmationId, true),
35
+ };
36
+ }
37
+ issue(turn, toolName, args) {
38
+ // Previews nobody confirmed would otherwise pile up for the life of the conversation.
39
+ const now = this.now();
40
+ for (const [id, entry] of this.pending) {
41
+ if (this.isExpired(entry, now))
42
+ this.pending.delete(id);
43
+ }
44
+ // A 32-bit collision only replaces an older preview, whose id then fails as a mismatch —
45
+ // never lets a change through.
46
+ const id = this.newId();
47
+ this.pending.set(id, { toolName, argsKey: confirmationArgsKey(args), turn, issuedAt: now });
48
+ return id;
49
+ }
50
+ verdict(turn, toolName, args, confirmationId, redeem) {
51
+ const id = confirmationId?.trim();
52
+ if (!id)
53
+ return { ok: false, reason: "missing" };
54
+ const entry = this.pending.get(id);
55
+ if (!entry)
56
+ return { ok: false, reason: "unknown" };
57
+ if (this.isExpired(entry, this.now())) {
58
+ this.pending.delete(id);
59
+ return { ok: false, reason: "expired" };
60
+ }
61
+ if (entry.turn >= turn)
62
+ return { ok: false, reason: "same_turn" };
63
+ if (entry.toolName !== toolName || entry.argsKey !== confirmationArgsKey(args)) {
64
+ return { ok: false, reason: "mismatch" };
65
+ }
66
+ if (redeem)
67
+ this.pending.delete(id);
68
+ return { ok: true };
69
+ }
70
+ isExpired(entry, now) {
71
+ return now - entry.issuedAt > this.ttlMs;
72
+ }
73
+ }
74
+ /** Canonical JSON of the arguments without the protocol keys: object keys sorted at every depth,
75
+ * array order kept — `{b, a}` and `{a, b}` are the same change, `[1, 2]` and `[2, 1]` are not. */
76
+ function confirmationArgsKey(args) {
77
+ const change = {};
78
+ for (const [key, value] of Object.entries(args)) {
79
+ if (!PROTOCOL_KEYS.has(key))
80
+ change[key] = value;
81
+ }
82
+ return JSON.stringify(sortKeys(change));
83
+ }
84
+ function sortKeys(value) {
85
+ if (Array.isArray(value))
86
+ return value.map(sortKeys);
87
+ if (value === null || typeof value !== "object")
88
+ return value;
89
+ const sorted = {};
90
+ for (const key of Object.keys(value).sort()) {
91
+ sorted[key] = sortKeys(value[key]);
92
+ }
93
+ return sorted;
94
+ }
95
+ /** `crypto.getRandomValues` exists in Node and in every browser, secure context or not. */
96
+ function randomConfirmationId() {
97
+ const bytes = new Uint8Array(4);
98
+ globalThis.crypto.getRandomValues(bytes);
99
+ return `c-${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
100
+ }
@@ -1,8 +1,19 @@
1
1
  import type { Identity } from "@icp-sdk/core/agent";
2
+ import type { TurnConfirmations } from "./confirmations.js";
2
3
  import { type IcSalonCorePortOptions } from "./ic-salon-core-port.js";
3
4
  import type { SalonCorePort } from "./salon-core-port.js";
4
5
  import type { AgentTool } from "./types.js";
5
- export interface CreateSalonToolsOptions extends Omit<IcSalonCorePortOptions, "language" | "access"> {
6
+ export interface CreateSalonToolsFromPortOptions {
7
+ /**
8
+ * This turn's confirmations — `ledger.forTurn()` from the conversation's `ConfirmationLedger`,
9
+ * taken once per incoming owner message. With it, every write tool issues a `confirmationId`
10
+ * on `confirmed: false` and refuses `confirmed: true` unless that id comes back, with the same
11
+ * arguments, from a later turn. Omit it and the tools keep the old protocol, where only the
12
+ * persona stops a model from confirming on its own.
13
+ */
14
+ confirmations?: TurnConfirmations;
15
+ }
16
+ export interface CreateSalonToolsOptions extends Omit<IcSalonCorePortOptions, "language" | "access">, CreateSalonToolsFromPortOptions {
6
17
  /** Language of service-type labels. Defaults to Polish. */
7
18
  language?: string;
8
19
  }
@@ -12,7 +23,7 @@ export interface CreateSalonToolsOptions extends Omit<IcSalonCorePortOptions, "l
12
23
  */
13
24
  export declare function createSalonTools(options: CreateSalonToolsOptions): AgentTool[];
14
25
  /** The same tool set over any port — the seam tests and the sandbox use. */
15
- export declare function createSalonToolsFromPort(port: SalonCorePort): AgentTool[];
26
+ export declare function createSalonToolsFromPort(port: SalonCorePort, options?: CreateSalonToolsFromPortOptions): AgentTool[];
16
27
  export interface CreateSalonReadToolsOptions {
17
28
  canisterId: string;
18
29
  salonId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"create-salon-tools.d.ts","sourceRoot":"","sources":["../src/create-salon-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,UAAU,GAAG,QAAQ,CAAC;IAChG,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,SAAS,EAAE,CAG9E;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAEzE;AAED,MAAM,WAAW,2BAA2B;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,EAAE,CAGtF;AAED;mEACmE;AACnE,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAE7E"}
1
+ {"version":3,"file":"create-salon-tools.d.ts","sourceRoot":"","sources":["../src/create-salon-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,+BAA+B;IAC5C;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACrC;AAED,MAAM,WAAW,uBACb,SAAQ,IAAI,CAAC,sBAAsB,EAAE,UAAU,GAAG,QAAQ,CAAC,EACvD,+BAA+B;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,SAAS,EAAE,CAK9E;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,+BAAoC,GAAG,SAAS,EAAE,CAQxH;AAED,MAAM,WAAW,2BAA2B;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,EAAE,CAGtF;AAED;mEACmE;AACnE,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE,CAE7E"}
@@ -7,12 +7,18 @@ import { createServiceTools, findServiceTypeTool, listServicesTool } from "./too
7
7
  * argument, so a model holding these tools cannot reach another salon's data whatever it sends.
8
8
  */
9
9
  export function createSalonTools(options) {
10
- const port = new IcSalonCorePort({ ...options, language: options.language ?? "pl", access: "owner" });
11
- return createSalonToolsFromPort(port);
10
+ const { confirmations, language, ...portOptions } = options;
11
+ const port = new IcSalonCorePort({ ...portOptions, language: language ?? "pl", access: "owner" });
12
+ return createSalonToolsFromPort(port, { confirmations });
12
13
  }
13
14
  /** The same tool set over any port — the seam tests and the sandbox use. */
14
- export function createSalonToolsFromPort(port) {
15
- return [...createServiceTools(port), ...createScheduleTools(port), ...createPromoTools(port)];
15
+ export function createSalonToolsFromPort(port, options = {}) {
16
+ const { confirmations } = options;
17
+ return [
18
+ ...createServiceTools(port, confirmations),
19
+ ...createScheduleTools(port, confirmations),
20
+ ...createPromoTools(port, confirmations),
21
+ ];
16
22
  }
17
23
  /**
18
24
  * The read-only tool set for ONE salon: `list_services`, `list_promos` and `find_service_type`,
@@ -0,0 +1,38 @@
1
+ import type { TurnConfirmations } from "../confirmations.js";
2
+ import type { AgentTool } from "../types.js";
3
+ import type { CustomerCorePort } from "./customer-core-port.js";
4
+ import { type IcCustomerCorePortOptions } from "./ic-customer-core-port.js";
5
+ /** The zone Sultana's salons live in. The canister keeps no zone per salon. */
6
+ export declare const DEFAULT_SALON_TIME_ZONE = "Europe/Warsaw";
7
+ export interface CreateCustomerToolsFromPortOptions {
8
+ /** This turn's confirmations — `ledger.forTurn()`, as for the salon tools. */
9
+ confirmations?: TurnConfirmations;
10
+ /** The clock; defaults to the system's. */
11
+ now?: () => Date;
12
+ /**
13
+ * The salons' time zone. The host process MUST run in it (`TZ=Europe/Warsaw`): the canister
14
+ * wrapper takes every offset from the process clock. While it does not, every tool that
15
+ * touches a time answers with an error instead of booking hours off. `null` skips the guard.
16
+ */
17
+ timeZone?: string | null;
18
+ }
19
+ export interface CreateCustomerToolsOptions extends Omit<IcCustomerCorePortOptions, "language">, CreateCustomerToolsFromPortOptions {
20
+ /** Language of service-type labels. Defaults to Polish. */
21
+ language?: string;
22
+ }
23
+ /**
24
+ * The tool set of ONE customer: search, free times, booking, the customer's visits, cancelling.
25
+ * The customer's identity and position are captured here and never appear as tool arguments, so a
26
+ * model holding these tools can act for nobody else and search around nowhere else.
27
+ */
28
+ export declare function createCustomerTools(options: CreateCustomerToolsOptions): AgentTool[];
29
+ /** The same set over any port — the seam tests use. */
30
+ export declare function createCustomerToolsFromPort(port: CustomerCorePort, options?: CreateCustomerToolsFromPortOptions): AgentTool[];
31
+ export type CreateCustomerReadToolsOptions = Omit<CreateCustomerToolsOptions, "identity" | "confirmations">;
32
+ /**
33
+ * The set for a customer whose delegation is missing or expired: everything that needs no
34
+ * identity — finding a service type, searching slots, a salon, its free times. Nothing books.
35
+ */
36
+ export declare function createCustomerReadTools(options: CreateCustomerReadToolsOptions): AgentTool[];
37
+ export declare function createCustomerReadToolsFromPort(port: CustomerCorePort, options?: Omit<CreateCustomerToolsFromPortOptions, "confirmations">): AgentTool[];
38
+ //# sourceMappingURL=create-customer-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-customer-tools.d.ts","sourceRoot":"","sources":["../../src/customer/create-customer-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAWhG,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,kBAAkB,CAAC;AAEvD,MAAM,WAAW,kCAAkC;IAC/C,8EAA8E;IAC9E,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA2B,SAAQ,IAAI,CAAC,yBAAyB,EAAE,UAAU,CAAC,EAAE,kCAAkC;IAC/H,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,EAAE,CAKpF;AAED,uDAAuD;AACvD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,GAAE,kCAAuC,GAAG,SAAS,EAAE,CASjI;AAED,MAAM,MAAM,8BAA8B,GAAG,IAAI,CAAC,0BAA0B,EAAE,UAAU,GAAG,eAAe,CAAC,CAAC;AAE5G;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,GAAG,SAAS,EAAE,CAK5F;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,GAAE,IAAI,CAAC,kCAAkC,EAAE,eAAe,CAAM,GAAG,SAAS,EAAE,CAE5J"}
@@ -0,0 +1,47 @@
1
+ import { findServiceTypeTool } from "../tools/services.js";
2
+ import { IcCustomerCorePort } from "./ic-customer-core-port.js";
3
+ import { bookAppointmentTool, cancelAppointmentTool, getFreeTimesTool, getSalonTool, listMyAppointmentsTool, searchSlotsTool, } from "./tools.js";
4
+ /** The zone Sultana's salons live in. The canister keeps no zone per salon. */
5
+ export const DEFAULT_SALON_TIME_ZONE = "Europe/Warsaw";
6
+ /**
7
+ * The tool set of ONE customer: search, free times, booking, the customer's visits, cancelling.
8
+ * The customer's identity and position are captured here and never appear as tool arguments, so a
9
+ * model holding these tools can act for nobody else and search around nowhere else.
10
+ */
11
+ export function createCustomerTools(options) {
12
+ const { confirmations, now, timeZone, language, ...portOptions } = options;
13
+ const port = new IcCustomerCorePort({ ...portOptions, language: language ?? "pl" });
14
+ return createCustomerToolsFromPort(port, { confirmations, now, timeZone });
15
+ }
16
+ /** The same set over any port — the seam tests use. */
17
+ export function createCustomerToolsFromPort(port, options = {}) {
18
+ const context = contextOf(port, options);
19
+ return [
20
+ ...readTools(context),
21
+ bookAppointmentTool(context, options.confirmations),
22
+ listMyAppointmentsTool(context),
23
+ cancelAppointmentTool(context, options.confirmations),
24
+ ];
25
+ }
26
+ /**
27
+ * The set for a customer whose delegation is missing or expired: everything that needs no
28
+ * identity — finding a service type, searching slots, a salon, its free times. Nothing books.
29
+ */
30
+ export function createCustomerReadTools(options) {
31
+ const { now, timeZone, language, ...portOptions } = options;
32
+ const port = new IcCustomerCorePort({ ...portOptions, language: language ?? "pl" });
33
+ return createCustomerReadToolsFromPort(port, { now, timeZone });
34
+ }
35
+ export function createCustomerReadToolsFromPort(port, options = {}) {
36
+ return readTools(contextOf(port, options));
37
+ }
38
+ function contextOf(port, options) {
39
+ return {
40
+ port,
41
+ now: options.now ?? (() => new Date()),
42
+ timeZone: options.timeZone === null ? undefined : options.timeZone ?? DEFAULT_SALON_TIME_ZONE,
43
+ };
44
+ }
45
+ function readTools(context) {
46
+ return [findServiceTypeTool(context.port), searchSlotsTool(context), getSalonTool(context), getFreeTimesTool(context)];
47
+ }
@@ -0,0 +1,94 @@
1
+ import type { ServiceTypeView, TimeRange } from "../salon-core-port.js";
2
+ /**
3
+ * Everything the customer tools may do on the core canister, in plain JSON. Unlike
4
+ * `SalonCorePort` it is bound to no salon — a customer looks across all of them — but to ONE
5
+ * customer: the identity behind the port (the customer's lent delegation) and the place the
6
+ * customer searches around. Neither ever appears as a tool argument.
7
+ *
8
+ * Moments are Unix milliseconds; a day is the LOCAL date `"YYYY-MM-DD"`.
9
+ */
10
+ export interface Coordinates {
11
+ lat: number;
12
+ lng: number;
13
+ }
14
+ /** A salon's service as a visitor sees it (active services only). */
15
+ export interface CustomerServiceView {
16
+ id: string;
17
+ salonId: string;
18
+ name: string;
19
+ pricePln: number;
20
+ durationMinutes: number;
21
+ serviceTypeIds: string[];
22
+ /** Workers who can perform it; the tools pick the first free one, like the app does. */
23
+ workerIds: string[];
24
+ }
25
+ export interface SalonView {
26
+ id: string;
27
+ name: string;
28
+ address: string;
29
+ }
30
+ export interface CustomerPromoView {
31
+ name: string;
32
+ textLines: string[];
33
+ discountCode?: string;
34
+ /** The service the promo leads to; absent = the salon itself. */
35
+ targetServiceId?: string;
36
+ }
37
+ /** One salon's answer to a slot search. */
38
+ export interface SalonSlotsView {
39
+ salonId: string;
40
+ distanceKm: number;
41
+ /** The salon's services of the searched type. */
42
+ services: CustomerServiceView[];
43
+ /** Free start moments, ascending. */
44
+ availableAt: number[];
45
+ /** Asap only: the soonest moment the customer can still reach from the search location. */
46
+ soonestAt: number | null;
47
+ }
48
+ export interface SlotSearchView {
49
+ /** Asap only. "extended": nothing reachable within the hour, so the scan went on up to 24 h. */
50
+ phase?: "soon" | "extended";
51
+ salons: SalonSlotsView[];
52
+ /** Probes whose canister call failed — an empty list with failures means "could not check". */
53
+ failedProbes: number;
54
+ }
55
+ export type AppointmentStatusView = "Pending" | "Confirmed" | "Cancelled" | "Completed" | "NoShow";
56
+ export interface AppointmentView {
57
+ id: string;
58
+ salonId: string;
59
+ serviceId: string;
60
+ startAt: number;
61
+ durationMinutes: number;
62
+ pricePln: number;
63
+ status: AppointmentStatusView;
64
+ discountCode?: string;
65
+ }
66
+ export interface BookingInput {
67
+ salonId: string;
68
+ serviceId: string;
69
+ workerId: string;
70
+ startAt: number;
71
+ notes: string;
72
+ discountCode?: string;
73
+ }
74
+ export interface CustomerCorePort {
75
+ /** Where the customer searches around; `null` when the app sent no position. */
76
+ readonly location: Coordinates | null;
77
+ findServiceTypes(query: string): Promise<ServiceTypeView[]>;
78
+ /** "As soon as possible" around `location`. */
79
+ searchAsap(serviceTypeId: string, radiusKm: number, now: Date): Promise<SlotSearchView>;
80
+ /** The given start moments around `location`. */
81
+ searchAtMoments(serviceTypeId: string, radiusKm: number, moments: number[]): Promise<SlotSearchView>;
82
+ getSalons(salonIds: string[]): Promise<SalonView[]>;
83
+ listSalonServices(salonId: string): Promise<CustomerServiceView[]>;
84
+ listSalonPromos(salonId: string): Promise<CustomerPromoView[]>;
85
+ /** One worker's free windows on a local day. A worker with no hours that day gives `[]`. */
86
+ freeWindows(salonId: string, workerId: string, day: string): Promise<TimeRange[]>;
87
+ /** Needs the customer's identity. Returns the appointment id. */
88
+ book(input: BookingInput): Promise<string>;
89
+ /** Needs the customer's identity. Every appointment of the customer, past and cancelled too. */
90
+ listMyAppointments(): Promise<AppointmentView[]>;
91
+ /** Needs the customer's identity. */
92
+ cancel(appointmentId: string): Promise<void>;
93
+ }
94
+ //# sourceMappingURL=customer-core-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-core-port.d.ts","sourceRoot":"","sources":["../../src/customer/customer-core-port.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import type { Identity } from "@icp-sdk/core/agent";
2
+ import type { ServiceTypeView, TimeRange } from "../salon-core-port.js";
3
+ import type { AppointmentView, BookingInput, Coordinates, CustomerCorePort, CustomerPromoView, CustomerServiceView, SalonView, SlotSearchView } from "./customer-core-port.js";
4
+ export interface IcCustomerCorePortOptions {
5
+ canisterId: string;
6
+ /** The customer's lent delegation. Omit it and every read still works (they are public
7
+ * queries), while booking, the customer's appointments and cancelling are refused. */
8
+ identity?: Identity;
9
+ /** Where the customer searches around — the position the app sent with the session. */
10
+ location?: Coordinates;
11
+ /** Language of service-type labels. */
12
+ language: string;
13
+ }
14
+ /**
15
+ * The customer port over the published canister wrapper. Conversions live here and nowhere
16
+ * else: the canister speaks `bigint`, `Principal` and nanoseconds, the tools speak numbers,
17
+ * strings and milliseconds.
18
+ *
19
+ * The public reads go out ANONYMOUSLY even when an identity is present: they answer the same
20
+ * either way, and a signed query would only spend the customer's delegation on nothing.
21
+ */
22
+ export declare class IcCustomerCorePort implements CustomerCorePort {
23
+ readonly location: Coordinates | null;
24
+ private readonly language;
25
+ private readonly search;
26
+ private readonly salons;
27
+ private readonly services;
28
+ private readonly serviceTypes;
29
+ private readonly promo;
30
+ private readonly availability;
31
+ private readonly appointments;
32
+ constructor(options: IcCustomerCorePortOptions);
33
+ findServiceTypes(query: string): Promise<ServiceTypeView[]>;
34
+ searchAsap(serviceTypeId: string, radiusKm: number, now: Date): Promise<SlotSearchView>;
35
+ searchAtMoments(serviceTypeId: string, radiusKm: number, moments: number[]): Promise<SlotSearchView>;
36
+ getSalons(salonIds: string[]): Promise<SalonView[]>;
37
+ listSalonServices(salonId: string): Promise<CustomerServiceView[]>;
38
+ listSalonPromos(salonId: string): Promise<CustomerPromoView[]>;
39
+ freeWindows(salonId: string, workerId: string, day: string): Promise<TimeRange[]>;
40
+ book(input: BookingInput): Promise<string>;
41
+ listMyAppointments(): Promise<AppointmentView[]>;
42
+ cancel(appointmentId: string): Promise<void>;
43
+ private requireLocation;
44
+ }
45
+ //# sourceMappingURL=ic-customer-core-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ic-customer-core-port.d.ts","sourceRoot":"","sources":["../../src/customer/ic-customer-core-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAepD,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,KAAK,EAER,eAAe,EACf,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEnB,SAAS,EACT,cAAc,EACjB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,yBAAyB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB;0FACsF;IACtF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;CACpB;AAaD;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,gBAAgB;IACvD,SAAgB,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAE7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;gBAErC,OAAO,EAAE,yBAAyB;IAajC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAU3D,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC;IAWvF,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAWpG,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAQnD,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAKlE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAa9D,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAUjF,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAW1C,kBAAkB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAehD,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,OAAO,CAAC,eAAe;CAI1B"}