@msg91comm/m91 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +21 -0
  3. package/README.md +425 -2
  4. package/dist/src/client.d.ts +165 -0
  5. package/dist/src/client.d.ts.map +1 -0
  6. package/dist/src/client.js +221 -0
  7. package/dist/src/client.js.map +1 -0
  8. package/dist/src/exceptions.d.ts +114 -0
  9. package/dist/src/exceptions.d.ts.map +1 -0
  10. package/dist/src/exceptions.js +199 -0
  11. package/dist/src/exceptions.js.map +1 -0
  12. package/dist/src/http.d.ts +71 -0
  13. package/dist/src/http.d.ts.map +1 -0
  14. package/dist/src/http.js +150 -0
  15. package/dist/src/http.js.map +1 -0
  16. package/dist/src/index.d.ts +49 -0
  17. package/dist/src/index.d.ts.map +1 -0
  18. package/dist/src/index.js +45 -0
  19. package/dist/src/index.js.map +1 -0
  20. package/dist/src/integrations/index.d.ts +22 -0
  21. package/dist/src/integrations/index.d.ts.map +1 -0
  22. package/dist/src/integrations/index.js +21 -0
  23. package/dist/src/integrations/index.js.map +1 -0
  24. package/dist/src/integrations/langchain.d.ts +43 -0
  25. package/dist/src/integrations/langchain.d.ts.map +1 -0
  26. package/dist/src/integrations/langchain.js +75 -0
  27. package/dist/src/integrations/langchain.js.map +1 -0
  28. package/dist/src/integrations/langgraph.d.ts +79 -0
  29. package/dist/src/integrations/langgraph.d.ts.map +1 -0
  30. package/dist/src/integrations/langgraph.js +114 -0
  31. package/dist/src/integrations/langgraph.js.map +1 -0
  32. package/dist/src/integrations/specs.d.ts +131 -0
  33. package/dist/src/integrations/specs.d.ts.map +1 -0
  34. package/dist/src/integrations/specs.js +156 -0
  35. package/dist/src/integrations/specs.js.map +1 -0
  36. package/dist/src/integrations/vercel-ai.d.ts +36 -0
  37. package/dist/src/integrations/vercel-ai.d.ts.map +1 -0
  38. package/dist/src/integrations/vercel-ai.js +67 -0
  39. package/dist/src/integrations/vercel-ai.js.map +1 -0
  40. package/dist/src/models.d.ts +274 -0
  41. package/dist/src/models.d.ts.map +1 -0
  42. package/dist/src/models.js +383 -0
  43. package/dist/src/models.js.map +1 -0
  44. package/dist/src/platform.d.ts +176 -0
  45. package/dist/src/platform.d.ts.map +1 -0
  46. package/dist/src/platform.js +286 -0
  47. package/dist/src/platform.js.map +1 -0
  48. package/dist/src/url-builder.d.ts +65 -0
  49. package/dist/src/url-builder.d.ts.map +1 -0
  50. package/dist/src/url-builder.js +167 -0
  51. package/dist/src/url-builder.js.map +1 -0
  52. package/examples/approval-gate.ts +35 -0
  53. package/examples/basic-alert.ts +53 -0
  54. package/examples/langchain-agent.ts +65 -0
  55. package/examples/langgraph-hitl.ts +70 -0
  56. package/examples/vercel-ai-agent.ts +34 -0
  57. package/examples/webhook-urls.ts +44 -0
  58. package/package.json +83 -4
  59. package/src/client.ts +324 -0
  60. package/src/exceptions.ts +229 -0
  61. package/src/http.ts +174 -0
  62. package/src/index.ts +91 -0
  63. package/src/integrations/index.ts +23 -0
  64. package/src/integrations/langchain.ts +104 -0
  65. package/src/integrations/langgraph.ts +142 -0
  66. package/src/integrations/specs.ts +224 -0
  67. package/src/integrations/vercel-ai.ts +99 -0
  68. package/src/models.ts +660 -0
  69. package/src/platform.ts +343 -0
  70. package/src/url-builder.ts +225 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * HTTP on native `fetch`, with the retry policy applied. Zero dependencies.
3
+ *
4
+ * The rules, which are the interesting part:
5
+ *
6
+ * - **429 is retried exactly once**, after exactly the delay the server asked
7
+ * for. The send limit is 60/minute per link and every 429 carries
8
+ * `Retry-After: 60`. Retrying a second time would be arguing with it.
9
+ * - **5xx is retried with backoff.** Not the caller's fault, and the alert has
10
+ * not been raised, so repeating it cannot double-page anybody.
11
+ * - **4xx is never retried.** Not transient; the next identical request fails
12
+ * identically.
13
+ * - **A network failure is never retried.** A send that timed out may or may not
14
+ * have raised the alert — the response is what was lost, not necessarily the
15
+ * request. Blind retries are how one condition wakes a team four times.
16
+ */
17
+ import { M91NetworkError, retryAfter, unwrap } from './exceptions.js';
18
+ export const USER_AGENT = 'm91-node/1.0.0';
19
+ /** Delays before the 2nd, 3rd and 4th attempt at a 5xx. Four requests total. */
20
+ export const SERVER_BACKOFF = [1000, 2000, 4000];
21
+ export const RETRYABLE_STATUSES = new Set([500, 502, 503, 504]);
22
+ /**
23
+ * A 429 whose `Retry-After` is longer than this is thrown rather than slept
24
+ * through. The server sends 60; anything far past that is a caller sitting
25
+ * blocked in an alerting path, which is worse than an error it can see.
26
+ */
27
+ export const MAX_RETRY_AFTER_MS = 120_000;
28
+ /**
29
+ * Milliseconds to wait before trying again, or `null` to stop and throw.
30
+ * `attempt` is 1-based and counts requests already made.
31
+ */
32
+ export function retryDelay(status, attempt, retryAfterSeconds) {
33
+ if (status === 429) {
34
+ // Once, and only on the first 429. A second means the window has not
35
+ // actually cleared and the caller needs to hear about it.
36
+ if (attempt >= 2)
37
+ return null;
38
+ const wait = (retryAfterSeconds ?? 60) * 1000;
39
+ return wait > MAX_RETRY_AFTER_MS ? null : Math.max(wait, 0);
40
+ }
41
+ if (RETRYABLE_STATUSES.has(status)) {
42
+ return attempt > SERVER_BACKOFF.length ? null : SERVER_BACKOFF[attempt - 1];
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Matches a send link anywhere — in a bare URL, or buried in a sentence — and
48
+ * takes any query string attached to it. Bounded on whitespace so it stops at
49
+ * the end of the URL rather than eating the rest of the message.
50
+ */
51
+ const SEND_URL_RE = /(\/s\/)[^\s/?#]+(\?[^\s]*)?/g;
52
+ /**
53
+ * Replace a send link's token with `***`, wherever it appears.
54
+ *
55
+ * **The token is the credential and it lives in the path**, so a URL is as
56
+ * sensitive as a password and must never reach a log, an error message or a
57
+ * stack trace intact. The server holds itself to the same rule — its error
58
+ * handler reports the route *pattern*, never `req.path`.
59
+ *
60
+ * This takes free text, not only a URL, and that is the point. Redacting only
61
+ * the URL the SDK interpolates is not enough: a fetch failure can carry the URL
62
+ * in its own message, so a carefully-redacted sentence would arrive with the
63
+ * raw token attached to it by the runtime.
64
+ *
65
+ * Query strings go too: a GET-form send carries the alert title, and titles
66
+ * routinely name customers and hosts.
67
+ */
68
+ export function redact(text) {
69
+ if (typeof text !== 'string')
70
+ return text;
71
+ return text.replace(SEND_URL_RE, '$1***');
72
+ }
73
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
74
+ export class Transport {
75
+ timeoutMs;
76
+ headers;
77
+ fetchImpl;
78
+ sleepImpl;
79
+ constructor(options = {}) {
80
+ this.timeoutMs = options.timeoutMs ?? 15_000;
81
+ this.headers = {
82
+ // A GET-form send answers with a rendered HTML page when the caller looks
83
+ // like a browser. That page exists so a person tapping a bookmark sees
84
+ // something other than a wall of JSON — but an SDK is not a person, and
85
+ // has to opt out of it explicitly.
86
+ accept: 'application/json',
87
+ 'user-agent': USER_AGENT,
88
+ ...options.headers,
89
+ };
90
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
91
+ this.sleepImpl = options.sleepImpl ?? defaultSleep;
92
+ }
93
+ async request({ method, url, body, query }) {
94
+ const target = query ? `${url}?${new URLSearchParams(query).toString()}` : url;
95
+ let attempt = 0;
96
+ for (;;) {
97
+ attempt += 1;
98
+ const controller = new AbortController();
99
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
100
+ let status;
101
+ let headers;
102
+ let parsed;
103
+ try {
104
+ const response = await this.fetchImpl(target, {
105
+ method,
106
+ headers: body === undefined
107
+ ? this.headers
108
+ : { ...this.headers, 'content-type': 'application/json' },
109
+ body: body === undefined ? undefined : JSON.stringify(body),
110
+ signal: controller.signal,
111
+ });
112
+ status = response.status;
113
+ headers = response.headers;
114
+ parsed = await readBody(response);
115
+ }
116
+ catch (error) {
117
+ // Redacted AFTER composing, not before: a fetch failure can name the
118
+ // URL in its own message, so masking only our half still ships the token.
119
+ const reason = error instanceof Error ? error.message : String(error);
120
+ throw new M91NetworkError(redact(`Could not reach M91 at ${target}: ${reason}`));
121
+ }
122
+ finally {
123
+ clearTimeout(timer);
124
+ }
125
+ const delay = retryDelay(status, attempt, retryAfter(headers));
126
+ if (delay === null)
127
+ return unwrap(status, parsed, headers);
128
+ await this.sleepImpl(delay);
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * The parsed envelope, or something `unwrap` can still turn into an error.
134
+ *
135
+ * A non-JSON body here means a proxy, a load balancer or an HTML error page
136
+ * answered instead of M91. Wrapping the text in an envelope-shaped object keeps
137
+ * all the error construction in one place rather than adding a second path.
138
+ */
139
+ async function readBody(response) {
140
+ const text = await response.text();
141
+ if (!text)
142
+ return { error: { message: `HTTP ${response.status} with an empty body` } };
143
+ try {
144
+ return JSON.parse(text);
145
+ }
146
+ catch {
147
+ return { error: { message: text.slice(0, 300) } };
148
+ }
149
+ }
150
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * M91 — wake a human when software cannot carry on alone.
3
+ *
4
+ * An alert rings every phone in a channel at full volume, through silent mode
5
+ * and Do Not Disturb, and keeps ringing until somebody answers. HIGH and
6
+ * CRITICAL place a voice call if 45 seconds pass with no answer. Whoever takes
7
+ * it on is recorded, and it clears for everyone else.
8
+ *
9
+ * ```ts
10
+ * import { M91Client } from '@msg91comm/m91';
11
+ *
12
+ * const m91 = new M91Client(process.env.M91_SEND_LINK!);
13
+ * await m91.verify(); // raises nothing; names the channel
14
+ * await m91.send('Payments API is down', { severity: 'CRITICAL', customId: 'payments-down' });
15
+ * await m91.close({ customId: 'payments-down' });
16
+ * ```
17
+ *
18
+ * And the reason an agent wants it — a question that blocks until a person
19
+ * answers on their phone:
20
+ *
21
+ * ```ts
22
+ * if (await m91.requestApproval('Refund 48,000 on ORD-40122?', { customId: 'refund-ORD-40122' })) {
23
+ * await processRefund();
24
+ * }
25
+ * ```
26
+ *
27
+ * Everything here is a convenience over one URL. The send link works from a
28
+ * browser bar, a cron `curl` or an `<img>` tag with no client at all, and
29
+ * `M91URLBuilder` is for exactly those.
30
+ */
31
+ export { M91Client } from './client.js';
32
+ export type { ApprovalOptions, ClientOptions, SendOptions, WaitOptions } from './client.js';
33
+ export { M91Platform, DEFAULT_BASE_URL } from './platform.js';
34
+ export type { PlatformOptions } from './platform.js';
35
+ export { M91URLBuilder, normalizeSendLink } from './url-builder.js';
36
+ export type { BuildManySpec, BuildUrlOptions } from './url-builder.js';
37
+ export { redact } from './http.js';
38
+ export { M91Error, M91ValidationError, M91AuthError, M91ForbiddenError, M91NotFoundError, M91ChannelEmptyError, M91RateLimitError, M91ServerError, M91NetworkError, M91DeliveryError, M91TimeoutError, } from './exceptions.js';
39
+ export { SEVERITIES, EFFECTS, ESCALATING_SEVERITIES, isApproved, validatePhone, buildAlertPayload, } from './models.js';
40
+ export type { AlertDetail, AlertResponse, AlertStatus, ApiKeyInfo, ChannelInfo, Attachment, Decision, Response, Effect, InviteResult, ProvisionedChannel, Recipient, RecipientState, ResponseOption, Severity, } from './models.js';
41
+ /**
42
+ * Kept in step with package.json by `tests/version.test.ts`, not by hand.
43
+ *
44
+ * ESM has no portable way to read package.json at runtime, so this number is
45
+ * written twice — and it drifted, reporting 1.0.0 while the package shipped as
46
+ * 1.1.0. A test is what makes two copies safe.
47
+ */
48
+ export declare const VERSION = "1.1.0";
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5F,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,OAAO,EACP,qBAAqB,EACrB,UAAU,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * M91 — wake a human when software cannot carry on alone.
3
+ *
4
+ * An alert rings every phone in a channel at full volume, through silent mode
5
+ * and Do Not Disturb, and keeps ringing until somebody answers. HIGH and
6
+ * CRITICAL place a voice call if 45 seconds pass with no answer. Whoever takes
7
+ * it on is recorded, and it clears for everyone else.
8
+ *
9
+ * ```ts
10
+ * import { M91Client } from '@msg91comm/m91';
11
+ *
12
+ * const m91 = new M91Client(process.env.M91_SEND_LINK!);
13
+ * await m91.verify(); // raises nothing; names the channel
14
+ * await m91.send('Payments API is down', { severity: 'CRITICAL', customId: 'payments-down' });
15
+ * await m91.close({ customId: 'payments-down' });
16
+ * ```
17
+ *
18
+ * And the reason an agent wants it — a question that blocks until a person
19
+ * answers on their phone:
20
+ *
21
+ * ```ts
22
+ * if (await m91.requestApproval('Refund 48,000 on ORD-40122?', { customId: 'refund-ORD-40122' })) {
23
+ * await processRefund();
24
+ * }
25
+ * ```
26
+ *
27
+ * Everything here is a convenience over one URL. The send link works from a
28
+ * browser bar, a cron `curl` or an `<img>` tag with no client at all, and
29
+ * `M91URLBuilder` is for exactly those.
30
+ */
31
+ export { M91Client } from './client.js';
32
+ export { M91Platform, DEFAULT_BASE_URL } from './platform.js';
33
+ export { M91URLBuilder, normalizeSendLink } from './url-builder.js';
34
+ export { redact } from './http.js';
35
+ export { M91Error, M91ValidationError, M91AuthError, M91ForbiddenError, M91NotFoundError, M91ChannelEmptyError, M91RateLimitError, M91ServerError, M91NetworkError, M91DeliveryError, M91TimeoutError, } from './exceptions.js';
36
+ export { SEVERITIES, EFFECTS, ESCALATING_SEVERITIES, isApproved, validatePhone, buildAlertPayload, } from './models.js';
37
+ /**
38
+ * Kept in step with package.json by `tests/version.test.ts`, not by hand.
39
+ *
40
+ * ESM has no portable way to read package.json at runtime, so this number is
41
+ * written twice — and it drifted, reporting 1.0.0 while the package shipped as
42
+ * 1.1.0. A test is what makes two copies safe.
43
+ */
44
+ export const VERSION = '1.1.0';
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGpE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,OAAO,EACP,qBAAqB,EACrB,UAAU,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAoBrB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Framework adapters, re-exported.
3
+ *
4
+ * Each one imports its framework lazily, so `npm install @msg91comm/m91` pulls in none of
5
+ * them and using one you have not installed says which package to install
6
+ * rather than failing with a bare module-not-found from three frames down.
7
+ *
8
+ * Importing a specific adapter directly is usually better —
9
+ * `@msg91comm/m91/integrations/langchain` — because it pulls in only what you use.
10
+ *
11
+ * `langchain.ts` and `vercel-ai.ts` both export `createM91ApprovalTool`, so
12
+ * they are re-exported under namespaces rather than flattened: two functions of
13
+ * the same name doing the same job for different frameworks is exactly the pair
14
+ * that would silently resolve to the wrong one.
15
+ */
16
+ export * as langchain from './langchain.js';
17
+ export * as langgraph from './langgraph.js';
18
+ export * as vercelAi from './vercel-ai.js';
19
+ export { M91InterruptNotifier, createM91ApprovalNode } from './langgraph.js';
20
+ export { APPROVED, REJECTED, alertResult, errorResult } from './specs.js';
21
+ export type { AlertArgs, ApprovalArgs } from './specs.js';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1E,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Framework adapters, re-exported.
3
+ *
4
+ * Each one imports its framework lazily, so `npm install @msg91comm/m91` pulls in none of
5
+ * them and using one you have not installed says which package to install
6
+ * rather than failing with a bare module-not-found from three frames down.
7
+ *
8
+ * Importing a specific adapter directly is usually better —
9
+ * `@msg91comm/m91/integrations/langchain` — because it pulls in only what you use.
10
+ *
11
+ * `langchain.ts` and `vercel-ai.ts` both export `createM91ApprovalTool`, so
12
+ * they are re-exported under namespaces rather than flattened: two functions of
13
+ * the same name doing the same job for different frameworks is exactly the pair
14
+ * that would silently resolve to the wrong one.
15
+ */
16
+ export * as langchain from './langchain.js';
17
+ export * as langgraph from './langgraph.js';
18
+ export * as vercelAi from './vercel-ai.js';
19
+ export { M91InterruptNotifier, createM91ApprovalNode } from './langgraph.js';
20
+ export { APPROVED, REJECTED, alertResult, errorResult } from './specs.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * LangChain tools.
3
+ *
4
+ * Two of them, and the split matters more than it looks:
5
+ *
6
+ * - the approval tool **blocks**. The agent stops until a human answers on
7
+ * their phone. Give it to an agent that can do something irreversible.
8
+ * - the alert tool does not. It wakes somebody and carries on.
9
+ *
10
+ * ```ts
11
+ * import { M91Client } from '@msg91comm/m91';
12
+ * import { createM91ApprovalTool, createM91AlertTool } from '@msg91comm/m91/integrations/langchain';
13
+ *
14
+ * const client = new M91Client(process.env.M91_SEND_LINK!);
15
+ * const tools = [
16
+ * await createM91ApprovalTool(client),
17
+ * await createM91AlertTool(client),
18
+ * ...myTools,
19
+ * ];
20
+ * const agent = createReactAgent({ llm, tools });
21
+ * ```
22
+ *
23
+ * The factories are **async** because `@langchain/core` is an optional peer
24
+ * dependency, imported only when one of these is called. That keeps `npm
25
+ * install @msg91comm/m91` at zero dependencies, which matters for a package whose job is
26
+ * to work when everything else is on fire.
27
+ */
28
+ import type { M91Client } from '../client.js';
29
+ import { type AlertDefaults, type ApprovalDefaults } from './specs.js';
30
+ /**
31
+ * The blocking approval gate, as a LangChain tool named `m91_request_approval`.
32
+ * Returns `"approved"`, `"rejected"` or `"error: …"` — and nothing but a real
33
+ * human approval returns `"approved"`.
34
+ */
35
+ export declare function createM91ApprovalTool(client: M91Client, defaults?: ApprovalDefaults): Promise<unknown>;
36
+ /**
37
+ * The fire-and-forget alert, as a LangChain tool named `m91_send_alert`.
38
+ * Returns `"sent:delivered=N"` or `"error: …"`.
39
+ */
40
+ export declare function createM91AlertTool(client: M91Client, defaults?: AlertDefaults): Promise<unknown>;
41
+ /** Both, in the order an agent's `tools` array usually wants them. */
42
+ export declare function createM91Tools(client: M91Client, defaults?: ApprovalDefaults & AlertDefaults): Promise<unknown[]>;
43
+ //# sourceMappingURL=langchain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langchain.d.ts","sourceRoot":"","sources":["../../../src/integrations/langchain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAQL,KAAK,aAAa,EAElB,KAAK,gBAAgB,EAGtB,MAAM,YAAY,CAAC;AAmBpB;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAqB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,aAAkB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,sEAAsE;AACtE,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAgB,GAAG,aAAkB,GAC9C,OAAO,CAAC,OAAO,EAAE,CAAC,CAKpB"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * LangChain tools.
3
+ *
4
+ * Two of them, and the split matters more than it looks:
5
+ *
6
+ * - the approval tool **blocks**. The agent stops until a human answers on
7
+ * their phone. Give it to an agent that can do something irreversible.
8
+ * - the alert tool does not. It wakes somebody and carries on.
9
+ *
10
+ * ```ts
11
+ * import { M91Client } from '@msg91comm/m91';
12
+ * import { createM91ApprovalTool, createM91AlertTool } from '@msg91comm/m91/integrations/langchain';
13
+ *
14
+ * const client = new M91Client(process.env.M91_SEND_LINK!);
15
+ * const tools = [
16
+ * await createM91ApprovalTool(client),
17
+ * await createM91AlertTool(client),
18
+ * ...myTools,
19
+ * ];
20
+ * const agent = createReactAgent({ llm, tools });
21
+ * ```
22
+ *
23
+ * The factories are **async** because `@langchain/core` is an optional peer
24
+ * dependency, imported only when one of these is called. That keeps `npm
25
+ * install @msg91comm/m91` at zero dependencies, which matters for a package whose job is
26
+ * to work when everything else is on fire.
27
+ */
28
+ import { ALERT_DESCRIPTION, ALERT_SCHEMA, ALERT_TOOL_NAME, APPROVAL_DESCRIPTION, APPROVAL_SCHEMA, APPROVAL_TOOL_NAME, runAlert, runApproval, } from './specs.js';
29
+ async function langchainTool() {
30
+ try {
31
+ // The specifier is a variable so TypeScript does not try to resolve an
32
+ // optional peer dependency at build time. Compiling this package must not
33
+ // require every framework it can adapt to.
34
+ const specifier = '@langchain/core/tools';
35
+ const mod = (await import(specifier));
36
+ return mod.tool;
37
+ }
38
+ catch (error) {
39
+ throw new Error('The LangChain integration needs @langchain/core. Install it with: ' +
40
+ 'npm install @langchain/core', { cause: error });
41
+ }
42
+ }
43
+ /**
44
+ * The blocking approval gate, as a LangChain tool named `m91_request_approval`.
45
+ * Returns `"approved"`, `"rejected"` or `"error: …"` — and nothing but a real
46
+ * human approval returns `"approved"`.
47
+ */
48
+ export async function createM91ApprovalTool(client, defaults = {}) {
49
+ const tool = await langchainTool();
50
+ return tool((args) => runApproval(client, args, defaults), {
51
+ name: APPROVAL_TOOL_NAME,
52
+ description: APPROVAL_DESCRIPTION,
53
+ schema: APPROVAL_SCHEMA,
54
+ });
55
+ }
56
+ /**
57
+ * The fire-and-forget alert, as a LangChain tool named `m91_send_alert`.
58
+ * Returns `"sent:delivered=N"` or `"error: …"`.
59
+ */
60
+ export async function createM91AlertTool(client, defaults = {}) {
61
+ const tool = await langchainTool();
62
+ return tool((args) => runAlert(client, args, defaults), {
63
+ name: ALERT_TOOL_NAME,
64
+ description: ALERT_DESCRIPTION,
65
+ schema: ALERT_SCHEMA,
66
+ });
67
+ }
68
+ /** Both, in the order an agent's `tools` array usually wants them. */
69
+ export async function createM91Tools(client, defaults = {}) {
70
+ return [
71
+ await createM91ApprovalTool(client, defaults),
72
+ await createM91AlertTool(client, defaults),
73
+ ];
74
+ }
75
+ //# sourceMappingURL=langchain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../../src/integrations/langchain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAKlB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,KAAK,UAAU,aAAa;IAC1B,IAAI,CAAC;QACH,uEAAuE;QACvE,0EAA0E;QAC1E,2CAA2C;QAC3C,MAAM,SAAS,GAAG,uBAAuB,CAAC;QAC1C,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAwD,CAAC;QAC7F,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,oEAAoE;YAClE,6BAA6B,EAC/B,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAiB,EACjB,WAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,CAAC,IAAkB,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;QACvE,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,oBAAoB;QACjC,MAAM,EAAE,eAAe;KACxB,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAiB,EACjB,WAA0B,EAAE;IAE5B,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,CAAC,IAAe,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;QACjE,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,iBAAiB;QAC9B,MAAM,EAAE,YAAY;KACrB,CAAC,CAAC;AACL,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAiB,EACjB,WAA6C,EAAE;IAE/C,OAAO;QACL,MAAM,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC7C,MAAM,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC;KAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * LangGraph helpers for the `interrupt()` pattern.
3
+ *
4
+ * LangGraph already knows how to stop a graph and resume it. What it has no
5
+ * opinion about is **how the human finds out it stopped** — and the default
6
+ * answer is that somebody notices a paused run in a web UI. At 2am, for a graph
7
+ * that paused because it wants to move money, nobody does. M91 fills exactly
8
+ * that gap: the graph pauses, a phone rings until somebody answers, and the
9
+ * answer resumes the graph.
10
+ *
11
+ * Nothing here imports `@langchain/langgraph`. These are plain callables you use
12
+ * *around* your graph, so they work the same with `interrupt()`, with
13
+ * `new Command({ resume })`, or with a node you wrote by hand.
14
+ */
15
+ import type { M91Client } from '../client.js';
16
+ import type { Severity } from '../models.js';
17
+ import { APPROVED, REJECTED } from './specs.js';
18
+ export interface NotifyOptions {
19
+ title: string;
20
+ customId: string;
21
+ description?: string;
22
+ severity?: Severity;
23
+ approveLabel?: string;
24
+ rejectLabel?: string;
25
+ timeoutMs?: number;
26
+ pollIntervalMs?: number;
27
+ }
28
+ export type NotifyDefaults = Omit<NotifyOptions, 'title' | 'customId'>;
29
+ export type Verdict = typeof APPROVED | typeof REJECTED;
30
+ /**
31
+ * Wake a human about an interrupted graph and return what they said.
32
+ *
33
+ * ```ts
34
+ * const notifier = new M91InterruptNotifier(client);
35
+ *
36
+ * const result = await graph.invoke(state, config);
37
+ * if (result.__interrupt__) {
38
+ * const data = result.__interrupt__[0].value;
39
+ * const decision = await notifier.notify({
40
+ * title: data.question,
41
+ * customId: data.customId,
42
+ * timeoutMs: 600_000,
43
+ * });
44
+ * await graph.invoke(new Command({ resume: decision }), config);
45
+ * }
46
+ * ```
47
+ *
48
+ * Resolves `"approved"` or `"rejected"` and **never anything else** — a failure
49
+ * to reach anybody comes back as `"rejected"`, because a graph that resumes
50
+ * with an ambiguous value resumes with whatever the node's `if` happens to do
51
+ * with it.
52
+ */
53
+ export declare class M91InterruptNotifier {
54
+ private readonly client;
55
+ private readonly defaults;
56
+ constructor(client: M91Client, defaults?: NotifyDefaults);
57
+ notify(options: NotifyOptions): Promise<Verdict>;
58
+ }
59
+ /**
60
+ * Build a graph node that asks a human and writes the answer into state.
61
+ *
62
+ * ```ts
63
+ * builder.addNode('humanApproval', createM91ApprovalNode(client, {
64
+ * approveLabel: 'Deploy Now',
65
+ * rejectLabel: 'Hold Release',
66
+ * timeoutMs: 600_000,
67
+ * }));
68
+ * ```
69
+ *
70
+ * The node reads `title` (or `question`) and `customId` from state and returns
71
+ * `{ decision, approved }` for the next edge to branch on. Anything passed here
72
+ * is a default the state can override per run.
73
+ */
74
+ export declare function createM91ApprovalNode(client: M91Client, defaults?: NotifyDefaults): (state: unknown) => Promise<{
75
+ decision: Verdict;
76
+ approved: boolean;
77
+ error?: string;
78
+ }>;
79
+ //# sourceMappingURL=langgraph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langgraph.d.ts","sourceRoot":"","sources":["../../../src/integrations/langgraph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;AAEvE,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,GAAG,OAAO,QAAQ,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;gBAE9B,MAAM,EAAE,SAAS,EAAE,QAAQ,GAAE,cAAmB;IAKtD,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;CAkBvD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,cAAmB,GAC5B,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgCvF"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * LangGraph helpers for the `interrupt()` pattern.
3
+ *
4
+ * LangGraph already knows how to stop a graph and resume it. What it has no
5
+ * opinion about is **how the human finds out it stopped** — and the default
6
+ * answer is that somebody notices a paused run in a web UI. At 2am, for a graph
7
+ * that paused because it wants to move money, nobody does. M91 fills exactly
8
+ * that gap: the graph pauses, a phone rings until somebody answers, and the
9
+ * answer resumes the graph.
10
+ *
11
+ * Nothing here imports `@langchain/langgraph`. These are plain callables you use
12
+ * *around* your graph, so they work the same with `interrupt()`, with
13
+ * `new Command({ resume })`, or with a node you wrote by hand.
14
+ */
15
+ import { APPROVED, REJECTED } from './specs.js';
16
+ /**
17
+ * Wake a human about an interrupted graph and return what they said.
18
+ *
19
+ * ```ts
20
+ * const notifier = new M91InterruptNotifier(client);
21
+ *
22
+ * const result = await graph.invoke(state, config);
23
+ * if (result.__interrupt__) {
24
+ * const data = result.__interrupt__[0].value;
25
+ * const decision = await notifier.notify({
26
+ * title: data.question,
27
+ * customId: data.customId,
28
+ * timeoutMs: 600_000,
29
+ * });
30
+ * await graph.invoke(new Command({ resume: decision }), config);
31
+ * }
32
+ * ```
33
+ *
34
+ * Resolves `"approved"` or `"rejected"` and **never anything else** — a failure
35
+ * to reach anybody comes back as `"rejected"`, because a graph that resumes
36
+ * with an ambiguous value resumes with whatever the node's `if` happens to do
37
+ * with it.
38
+ */
39
+ export class M91InterruptNotifier {
40
+ client;
41
+ defaults;
42
+ constructor(client, defaults = {}) {
43
+ this.client = client;
44
+ this.defaults = defaults;
45
+ }
46
+ async notify(options) {
47
+ const { title, customId, ...rest } = options;
48
+ const merged = { ...this.defaults, ...strip(rest) };
49
+ try {
50
+ const approved = await this.client.requestApproval(title, {
51
+ customId,
52
+ severity: 'HIGH',
53
+ timeoutMs: 300_000,
54
+ ...merged,
55
+ });
56
+ return approved ? APPROVED : REJECTED;
57
+ }
58
+ catch {
59
+ // Nobody could be reached, so nobody approved. Resuming a graph with
60
+ // "approved" because the pager was down is the one outcome worth ruling
61
+ // out by construction.
62
+ return REJECTED;
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Build a graph node that asks a human and writes the answer into state.
68
+ *
69
+ * ```ts
70
+ * builder.addNode('humanApproval', createM91ApprovalNode(client, {
71
+ * approveLabel: 'Deploy Now',
72
+ * rejectLabel: 'Hold Release',
73
+ * timeoutMs: 600_000,
74
+ * }));
75
+ * ```
76
+ *
77
+ * The node reads `title` (or `question`) and `customId` from state and returns
78
+ * `{ decision, approved }` for the next edge to branch on. Anything passed here
79
+ * is a default the state can override per run.
80
+ */
81
+ export function createM91ApprovalNode(client, defaults = {}) {
82
+ const notifier = new M91InterruptNotifier(client, defaults);
83
+ return async (state) => {
84
+ const data = (state && typeof state === 'object' ? state : {});
85
+ const title = data.title ?? data.question;
86
+ const customId = data.customId ?? data.custom_id;
87
+ if (!title || !customId) {
88
+ // Said loudly rather than defaulted. A node that invents a customId is a
89
+ // node whose alerts never deduplicate and never close.
90
+ return {
91
+ decision: REJECTED,
92
+ approved: false,
93
+ error: "error: the approval node needs 'title' (or 'question') and 'customId' in state",
94
+ };
95
+ }
96
+ const decision = await notifier.notify({
97
+ title: String(title),
98
+ customId: String(customId),
99
+ ...strip({
100
+ description: data.description,
101
+ severity: data.severity,
102
+ approveLabel: data.approveLabel,
103
+ rejectLabel: data.rejectLabel,
104
+ timeoutMs: data.timeoutMs,
105
+ }),
106
+ });
107
+ return { decision, approved: decision === APPROVED };
108
+ };
109
+ }
110
+ /** Drop undefined keys so a spread cannot blank out a default with `undefined`. */
111
+ function strip(value) {
112
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
113
+ }
114
+ //# sourceMappingURL=langgraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langgraph.js","sourceRoot":"","sources":["../../../src/integrations/langgraph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAiBhD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,oBAAoB;IACd,MAAM,CAAY;IAClB,QAAQ,CAAiB;IAE1C,YAAY,MAAiB,EAAE,WAA2B,EAAE;QAC1D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAsB;QACjC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;QAC7C,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE;gBACxD,QAAQ;gBACR,QAAQ,EAAE,MAAM;gBAChB,SAAS,EAAE,OAAO;gBAClB,GAAG,MAAM;aACV,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,wEAAwE;YACxE,uBAAuB;YACvB,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAiB,EACjB,WAA2B,EAAE;IAE7B,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE5D,OAAO,KAAK,EAAE,KAAc,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;QAC1F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC;QAEjD,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxB,yEAAyE;YACzE,uDAAuD;YACvD,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,KAAK;gBACf,KAAK,EACH,gFAAgF;aACnF,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;YACrC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;YACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;YAC1B,GAAG,KAAK,CAAC;gBACP,WAAW,EAAE,IAAI,CAAC,WAAiC;gBACnD,QAAQ,EAAE,IAAI,CAAC,QAAgC;gBAC/C,YAAY,EAAE,IAAI,CAAC,YAAkC;gBACrD,WAAW,EAAE,IAAI,CAAC,WAAiC;gBACnD,SAAS,EAAE,IAAI,CAAC,SAA+B;aAChD,CAAC;SACH,CAAC,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;IACvD,CAAC,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,KAAK,CAAoC,KAAQ;IACxD,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAe,CAAC;AACpG,CAAC"}