@pliuz/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,578 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/gated.ts
6
+
7
+ // src/version.ts
8
+ var VERSION = "0.1.0";
9
+
10
+ // src/errors.ts
11
+ var PliuzError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "PliuzError";
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
17
+ };
18
+ var PliuzNetworkError = class extends PliuzError {
19
+ constructor(message, cause) {
20
+ super(message);
21
+ this.cause = cause;
22
+ this.name = "PliuzNetworkError";
23
+ Object.setPrototypeOf(this, new.target.prototype);
24
+ }
25
+ cause;
26
+ };
27
+ var PliuzTimeoutError = class extends PliuzError {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = "PliuzTimeoutError";
31
+ Object.setPrototypeOf(this, new.target.prototype);
32
+ }
33
+ };
34
+ var PliuzApiError = class extends PliuzError {
35
+ constructor(statusCode, errorCode, message, details) {
36
+ super(message);
37
+ this.statusCode = statusCode;
38
+ this.errorCode = errorCode;
39
+ this.details = details;
40
+ this.name = "PliuzApiError";
41
+ Object.setPrototypeOf(this, new.target.prototype);
42
+ }
43
+ statusCode;
44
+ errorCode;
45
+ details;
46
+ };
47
+ var PliuzValidationError = class extends PliuzApiError {
48
+ constructor(statusCode, errorCode, message, details) {
49
+ super(statusCode, errorCode, message, details);
50
+ this.name = "PliuzValidationError";
51
+ Object.setPrototypeOf(this, new.target.prototype);
52
+ }
53
+ };
54
+ var PliuzAuthError = class extends PliuzApiError {
55
+ constructor(statusCode, errorCode, message, details) {
56
+ super(statusCode, errorCode, message, details);
57
+ this.name = "PliuzAuthError";
58
+ Object.setPrototypeOf(this, new.target.prototype);
59
+ }
60
+ };
61
+ var PliuzForbiddenError = class extends PliuzApiError {
62
+ constructor(statusCode, errorCode, message, details) {
63
+ super(statusCode, errorCode, message, details);
64
+ this.name = "PliuzForbiddenError";
65
+ Object.setPrototypeOf(this, new.target.prototype);
66
+ }
67
+ };
68
+ var PliuzNotFoundError = class extends PliuzApiError {
69
+ constructor(statusCode, errorCode, message, details) {
70
+ super(statusCode, errorCode, message, details);
71
+ this.name = "PliuzNotFoundError";
72
+ Object.setPrototypeOf(this, new.target.prototype);
73
+ }
74
+ };
75
+ var PliuzConflictError = class extends PliuzApiError {
76
+ constructor(statusCode, errorCode, message, details) {
77
+ super(statusCode, errorCode, message, details);
78
+ this.name = "PliuzConflictError";
79
+ Object.setPrototypeOf(this, new.target.prototype);
80
+ }
81
+ };
82
+ var PliuzPolicyError = class extends PliuzApiError {
83
+ constructor(statusCode, errorCode, message, details) {
84
+ super(statusCode, errorCode, message, details);
85
+ this.name = "PliuzPolicyError";
86
+ Object.setPrototypeOf(this, new.target.prototype);
87
+ }
88
+ };
89
+ var PliuzRateLimitError = class extends PliuzApiError {
90
+ constructor(statusCode, errorCode, message, details) {
91
+ super(statusCode, errorCode, message, details);
92
+ this.name = "PliuzRateLimitError";
93
+ Object.setPrototypeOf(this, new.target.prototype);
94
+ }
95
+ };
96
+ var PliuzServerError = class extends PliuzApiError {
97
+ constructor(statusCode, errorCode, message, details) {
98
+ super(statusCode, errorCode, message, details);
99
+ this.name = "PliuzServerError";
100
+ Object.setPrototypeOf(this, new.target.prototype);
101
+ }
102
+ };
103
+ var PliuzRejectedError = class extends PliuzError {
104
+ constructor(approvalId, reason) {
105
+ const msg = reason ? `approval ${approvalId} was rejected: ${reason}` : `approval ${approvalId} was rejected`;
106
+ super(msg);
107
+ this.approvalId = approvalId;
108
+ this.reason = reason;
109
+ this.name = "PliuzRejectedError";
110
+ Object.setPrototypeOf(this, new.target.prototype);
111
+ }
112
+ approvalId;
113
+ reason;
114
+ };
115
+ var PliuzApprovalExpiredError = class extends PliuzError {
116
+ constructor(approvalId) {
117
+ super(`approval ${approvalId} expired before a human decided`);
118
+ this.approvalId = approvalId;
119
+ this.name = "PliuzApprovalExpiredError";
120
+ Object.setPrototypeOf(this, new.target.prototype);
121
+ }
122
+ approvalId;
123
+ };
124
+ var PliuzApprovalTimeoutError = class extends PliuzError {
125
+ constructor(approvalId, timeoutMs) {
126
+ super(
127
+ `approval ${approvalId} did not resolve within ${timeoutMs}ms (SDK polling timeout \u2014 approval may still be pending server-side)`
128
+ );
129
+ this.approvalId = approvalId;
130
+ this.timeoutMs = timeoutMs;
131
+ this.name = "PliuzApprovalTimeoutError";
132
+ Object.setPrototypeOf(this, new.target.prototype);
133
+ }
134
+ approvalId;
135
+ timeoutMs;
136
+ };
137
+ var STATUS_TO_ERROR = {
138
+ 400: PliuzValidationError,
139
+ 401: PliuzAuthError,
140
+ 403: PliuzForbiddenError,
141
+ 404: PliuzNotFoundError,
142
+ 409: PliuzConflictError,
143
+ 422: PliuzPolicyError,
144
+ 429: PliuzRateLimitError
145
+ };
146
+ function errorForStatus(statusCode, errorCode, message, details) {
147
+ if (statusCode >= 500 && statusCode < 600) {
148
+ return new PliuzServerError(statusCode, errorCode, message, details);
149
+ }
150
+ const Cls = STATUS_TO_ERROR[statusCode] ?? PliuzApiError;
151
+ return new Cls(statusCode, errorCode, message, details);
152
+ }
153
+
154
+ // src/_http.ts
155
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "PUT", "DELETE"]);
156
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
157
+ function userAgent() {
158
+ return `pliuz-typescript/${VERSION}`;
159
+ }
160
+ function buildHeaders(apiKey) {
161
+ return {
162
+ "X-Pliuz-Api-Key": apiKey,
163
+ "User-Agent": userAgent(),
164
+ Accept: "application/json",
165
+ "Content-Type": "application/json"
166
+ };
167
+ }
168
+ function backoffMs(attempt, baseMs = 500, capMs = 8e3) {
169
+ const expo = Math.min(capMs, baseMs * 2 ** (attempt - 1));
170
+ return Math.floor(Math.random() * expo);
171
+ }
172
+ function isRetryable(method, status, retryOnPost) {
173
+ if (!RETRYABLE_STATUS.has(status)) return false;
174
+ if (IDEMPOTENT_METHODS.has(method)) return true;
175
+ if (method === "POST" && retryOnPost) return true;
176
+ return false;
177
+ }
178
+ async function parseError(response) {
179
+ let body = {};
180
+ try {
181
+ body = await response.json();
182
+ } catch {
183
+ }
184
+ const errorCode = body.error ?? "unknown_error";
185
+ const message = body.message ?? response.statusText ?? "Pliuz API error";
186
+ const details = body.details;
187
+ return errorForStatus(response.status, errorCode, message, details);
188
+ }
189
+ function sleep(ms) {
190
+ return new Promise((resolve) => setTimeout(resolve, ms));
191
+ }
192
+ async function request(opts) {
193
+ const { method, url, apiKey, body, timeoutMs, maxRetries, retryOnPost = false, fetchImpl } = opts;
194
+ const headers = buildHeaders(apiKey);
195
+ let lastError = null;
196
+ for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
197
+ const controller = new AbortController();
198
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
199
+ let response;
200
+ try {
201
+ response = await fetchImpl(url, {
202
+ method,
203
+ headers,
204
+ body: body == null ? void 0 : JSON.stringify(body),
205
+ signal: controller.signal
206
+ });
207
+ } catch (e) {
208
+ clearTimeout(timer);
209
+ const isTimeout = e instanceof Error && (e.name === "AbortError" || /abort|timeout/i.test(e.message));
210
+ lastError = isTimeout ? new PliuzTimeoutError(`Pliuz request timed out after ${timeoutMs}ms`) : new PliuzNetworkError(
211
+ `Pliuz network error: ${e instanceof Error ? e.message : String(e)}`,
212
+ e
213
+ );
214
+ if (attempt > maxRetries) throw lastError;
215
+ await sleep(backoffMs(attempt));
216
+ continue;
217
+ }
218
+ clearTimeout(timer);
219
+ if (response.status >= 200 && response.status < 300) {
220
+ try {
221
+ return await response.json();
222
+ } catch (e) {
223
+ throw new PliuzApiError(
224
+ response.status,
225
+ "invalid_json",
226
+ `Pliuz returned non-JSON 2xx body: ${e instanceof Error ? e.message : String(e)}`
227
+ );
228
+ }
229
+ }
230
+ if (isRetryable(method, response.status, retryOnPost)) {
231
+ if (attempt > maxRetries) {
232
+ throw await parseError(response);
233
+ }
234
+ await sleep(backoffMs(attempt));
235
+ continue;
236
+ }
237
+ throw await parseError(response);
238
+ }
239
+ throw lastError ?? new PliuzApiError(0, "exhausted", "retry loop exhausted unexpectedly");
240
+ }
241
+
242
+ // src/client.ts
243
+ var DEFAULT_BASE_URL = "https://pliuz-dev.vercel.app";
244
+ var DEFAULT_TIMEOUT_MS = 3e4;
245
+ var DEFAULT_MAX_RETRIES = 3;
246
+ var ENV_API_KEY = "PLIUZ_API_KEY";
247
+ var ENV_BASE_URL = "PLIUZ_BASE_URL";
248
+ function resolveApiKey(explicit) {
249
+ if (explicit) return explicit;
250
+ if (typeof process !== "undefined" && process.env?.[ENV_API_KEY]) {
251
+ return process.env[ENV_API_KEY];
252
+ }
253
+ throw new Error(`Pliuz API key not provided. Pass apiKey or set $${ENV_API_KEY}.`);
254
+ }
255
+ function resolveBaseUrl(explicit) {
256
+ if (explicit) return explicit.replace(/\/+$/, "");
257
+ if (typeof process !== "undefined" && process.env?.[ENV_BASE_URL]) {
258
+ return process.env[ENV_BASE_URL].replace(/\/+$/, "");
259
+ }
260
+ return DEFAULT_BASE_URL;
261
+ }
262
+ function approvalUrl(baseUrl, approvalId, suffix) {
263
+ let path = "/api/v1/approvals";
264
+ if (approvalId) path += `/${encodeURIComponent(approvalId)}`;
265
+ if (suffix) path += `/${suffix}`;
266
+ return baseUrl + path;
267
+ }
268
+ var PliuzClient = class {
269
+ apiKey;
270
+ baseUrl;
271
+ timeoutMs;
272
+ maxRetries;
273
+ fetchImpl;
274
+ constructor(options = {}) {
275
+ this.apiKey = resolveApiKey(options.apiKey);
276
+ this.baseUrl = resolveBaseUrl(options.baseUrl);
277
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
278
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
279
+ const fetchImpl = options.fetch ?? globalThis.fetch;
280
+ if (!fetchImpl) {
281
+ throw new Error(
282
+ "No fetch implementation available. Pliuz requires Node \u226518 or a polyfill."
283
+ );
284
+ }
285
+ this.fetchImpl = fetchImpl;
286
+ }
287
+ /**
288
+ * Create (or idempotently replay) an approval request.
289
+ *
290
+ * With `idempotency_key`, the call retries on network failure without
291
+ * risk of duplicates (backend dedupes within 24h).
292
+ */
293
+ async createApproval(input) {
294
+ const body = {
295
+ tool_name: input.tool_name,
296
+ tool_args: input.tool_args,
297
+ context_messages: input.context_messages ?? null,
298
+ originator: input.originator ?? { type: "system" },
299
+ session_id: input.session_id ?? null,
300
+ client_metadata: input.client_metadata ?? null,
301
+ idempotency_key: input.idempotency_key ?? null
302
+ };
303
+ return request({
304
+ method: "POST",
305
+ url: approvalUrl(this.baseUrl),
306
+ apiKey: this.apiKey,
307
+ body,
308
+ timeoutMs: this.timeoutMs,
309
+ maxRetries: this.maxRetries,
310
+ retryOnPost: Boolean(input.idempotency_key),
311
+ fetchImpl: this.fetchImpl
312
+ });
313
+ }
314
+ /**
315
+ * Fetch the current state of an approval request.
316
+ * Used by polling in `gated()` to detect status transitions.
317
+ */
318
+ async getApproval(approvalId) {
319
+ return request({
320
+ method: "GET",
321
+ url: approvalUrl(this.baseUrl, approvalId),
322
+ apiKey: this.apiKey,
323
+ timeoutMs: this.timeoutMs,
324
+ maxRetries: this.maxRetries,
325
+ fetchImpl: this.fetchImpl
326
+ });
327
+ }
328
+ /**
329
+ * Close the audit loop by reporting the outcome of the gated action.
330
+ *
331
+ * Single-shot: a second call returns 409 (PliuzConflictError).
332
+ * Only the originating agent can report — others get 403 (PliuzForbiddenError).
333
+ */
334
+ async reportExecution(approvalId, input) {
335
+ const body = {
336
+ status: input.status,
337
+ error: input.error ?? null,
338
+ latency_ms: input.latency_ms ?? null,
339
+ target_response_excerpt: input.target_response_excerpt ?? null
340
+ };
341
+ return request({
342
+ method: "POST",
343
+ url: approvalUrl(this.baseUrl, approvalId, "execution"),
344
+ apiKey: this.apiKey,
345
+ body,
346
+ timeoutMs: this.timeoutMs,
347
+ maxRetries: this.maxRetries,
348
+ retryOnPost: false,
349
+ fetchImpl: this.fetchImpl
350
+ });
351
+ }
352
+ };
353
+
354
+ // src/redaction.ts
355
+ var REDACTED_PLACEHOLDER = "<redacted>";
356
+ function applyRedaction(payload, paths) {
357
+ if (!paths || paths.length === 0) {
358
+ return deepClone(payload);
359
+ }
360
+ const result = deepClone(payload);
361
+ for (const path of paths) {
362
+ redactPath(result, parsePath(path));
363
+ }
364
+ return result;
365
+ }
366
+ function parsePath(path) {
367
+ if (!path) {
368
+ throw new Error("redaction path cannot be empty");
369
+ }
370
+ const segments = [];
371
+ for (const raw of path.split(".")) {
372
+ if (raw.endsWith("[*]")) {
373
+ const key = raw.slice(0, -3);
374
+ if (!key) {
375
+ throw new Error(`redaction path segment before [*] cannot be empty: ${path}`);
376
+ }
377
+ segments.push({ key, isArrayWildcard: true });
378
+ } else if (raw.includes("[") || raw.includes("]")) {
379
+ throw new Error(`unsupported redaction syntax in '${path}' \u2014 only [*] is allowed`);
380
+ } else {
381
+ if (!raw) {
382
+ throw new Error(`redaction path has empty segment: ${path}`);
383
+ }
384
+ segments.push({ key: raw, isArrayWildcard: false });
385
+ }
386
+ }
387
+ return segments;
388
+ }
389
+ function redactPath(node, segments) {
390
+ if (segments.length === 0) return;
391
+ if (!isPlainObject(node)) return;
392
+ const [head, ...rest] = segments;
393
+ if (!head) return;
394
+ const { key, isArrayWildcard } = head;
395
+ if (!(key in node)) return;
396
+ if (isArrayWildcard) {
397
+ const target = node[key];
398
+ if (!Array.isArray(target)) return;
399
+ if (rest.length === 0) {
400
+ node[key] = target.map(() => REDACTED_PLACEHOLDER);
401
+ return;
402
+ }
403
+ for (const item of target) {
404
+ redactPath(item, rest);
405
+ }
406
+ } else {
407
+ if (rest.length === 0) {
408
+ node[key] = REDACTED_PLACEHOLDER;
409
+ } else {
410
+ redactPath(node[key], rest);
411
+ }
412
+ }
413
+ }
414
+ function isPlainObject(v) {
415
+ return typeof v === "object" && v !== null && !Array.isArray(v);
416
+ }
417
+ function deepClone(v) {
418
+ if (typeof structuredClone === "function") {
419
+ return structuredClone(v);
420
+ }
421
+ return JSON.parse(JSON.stringify(v));
422
+ }
423
+
424
+ // src/gated.ts
425
+ var DEFAULT_GATED_TIMEOUT_MS = 3e5;
426
+ var DEFAULT_GATED_POLL_INTERVAL_MS = 2e3;
427
+ var TERMINAL_OK = /* @__PURE__ */ new Set(["approved"]);
428
+ var TERMINAL_REJECT = /* @__PURE__ */ new Set(["rejected"]);
429
+ var TERMINAL_EXPIRED = /* @__PURE__ */ new Set(["expired"]);
430
+ function gated(options, fn) {
431
+ const toolName = options.toolName ?? (fn.name || "anonymous");
432
+ const toolArgsMapper = options.toolArgs ?? ((...args) => ({ args }));
433
+ const timeoutMs = options.timeoutMs ?? DEFAULT_GATED_TIMEOUT_MS;
434
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_GATED_POLL_INTERVAL_MS;
435
+ const redactPaths = options.redact;
436
+ return async (...args) => {
437
+ const activeClient = options.client ?? new PliuzClient();
438
+ const rawArgs = toolArgsMapper(...args);
439
+ const sendArgs = redactPaths && redactPaths.length > 0 ? applyRedaction(rawArgs, redactPaths) : rawArgs;
440
+ const idempotencyKey = idempotencyKeyFor(toolName, sendArgs, options.sessionId);
441
+ const metadata = {};
442
+ if (options.policy) metadata.policy = options.policy;
443
+ const response = await activeClient.createApproval({
444
+ tool_name: toolName,
445
+ tool_args: sendArgs,
446
+ context_messages: options.contextMessages ?? null,
447
+ originator: options.originator,
448
+ session_id: options.sessionId ?? null,
449
+ client_metadata: Object.keys(metadata).length > 0 ? metadata : null,
450
+ idempotency_key: idempotencyKey
451
+ });
452
+ const approvalId = response.id;
453
+ if (TERMINAL_REJECT.has(response.status)) {
454
+ throw new PliuzRejectedError(approvalId, response.message ?? null);
455
+ }
456
+ if (TERMINAL_EXPIRED.has(response.status)) {
457
+ throw new PliuzApprovalExpiredError(approvalId);
458
+ }
459
+ if (!TERMINAL_OK.has(response.status)) {
460
+ const deadline = Date.now() + timeoutMs;
461
+ while (true) {
462
+ if (Date.now() >= deadline) {
463
+ throw new PliuzApprovalTimeoutError(approvalId, timeoutMs);
464
+ }
465
+ await sleep2(pollIntervalMs);
466
+ const approval = await activeClient.getApproval(approvalId);
467
+ if (terminalOrThrow(approval)) break;
468
+ }
469
+ }
470
+ const started = Date.now();
471
+ try {
472
+ const result = await fn(...args);
473
+ const latencyMs = Date.now() - started;
474
+ void safeReport(activeClient, approvalId, "success", null, latencyMs, result);
475
+ return result;
476
+ } catch (e) {
477
+ const latencyMs = Date.now() - started;
478
+ const errMsg = e instanceof Error ? e.message : String(e);
479
+ void safeReport(activeClient, approvalId, "error", errMsg.slice(0, 2e3), latencyMs, null);
480
+ throw e;
481
+ }
482
+ };
483
+ }
484
+ function idempotencyKeyFor(toolName, toolArgs, sessionId) {
485
+ JSON.stringify(
486
+ { tool: toolName, args: toolArgs, session: sessionId ?? null },
487
+ Object.keys({ tool: 0, args: 0, session: 0 }).sort()
488
+ );
489
+ const canonical = canonicalJSON({ tool: toolName, args: toolArgs, session: sessionId ?? null });
490
+ return "g_" + crypto.createHash("sha256").update(canonical).digest("hex").slice(0, 30);
491
+ }
492
+ function canonicalJSON(value) {
493
+ if (value === null || typeof value !== "object") {
494
+ return JSON.stringify(value);
495
+ }
496
+ if (Array.isArray(value)) {
497
+ return "[" + value.map(canonicalJSON).join(",") + "]";
498
+ }
499
+ const obj = value;
500
+ const keys = Object.keys(obj).sort();
501
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalJSON(obj[k])).join(",") + "}";
502
+ }
503
+ function terminalOrThrow(approval) {
504
+ if (TERMINAL_OK.has(approval.status)) return true;
505
+ if (TERMINAL_REJECT.has(approval.status)) {
506
+ throw new PliuzRejectedError(approval.id, approval.decision_reason);
507
+ }
508
+ if (TERMINAL_EXPIRED.has(approval.status)) {
509
+ throw new PliuzApprovalExpiredError(approval.id);
510
+ }
511
+ return false;
512
+ }
513
+ async function safeReport(client, approvalId, status, errorMessage, latencyMs, result) {
514
+ let excerpt = null;
515
+ if (result != null) {
516
+ try {
517
+ excerpt = JSON.stringify(result).slice(0, 2e3);
518
+ } catch {
519
+ excerpt = String(result).slice(0, 2e3);
520
+ }
521
+ }
522
+ try {
523
+ await client.reportExecution(approvalId, {
524
+ status,
525
+ error: errorMessage,
526
+ latency_ms: latencyMs,
527
+ target_response_excerpt: excerpt
528
+ });
529
+ } catch {
530
+ }
531
+ }
532
+ function sleep2(ms) {
533
+ return new Promise((resolve) => setTimeout(resolve, ms));
534
+ }
535
+
536
+ // src/adapters/ai.ts
537
+ var ADAPTER_NAME = "vercel-ai";
538
+ function gatedTool(options, tool) {
539
+ if (typeof tool.execute !== "function") {
540
+ return tool;
541
+ }
542
+ const toolName = options.toolName ?? (typeof tool.description === "string" ? tool.description.slice(0, 64) : "ai_tool");
543
+ const originalExecute = tool.execute;
544
+ let pendingOptions = void 0;
545
+ const gatedExecute1Arg = gated(
546
+ {
547
+ policy: options.policy,
548
+ redact: options.redact,
549
+ timeoutMs: options.timeoutMs,
550
+ pollIntervalMs: options.pollIntervalMs,
551
+ client: options.client,
552
+ contextMessages: options.contextMessages,
553
+ sessionId: options.sessionId,
554
+ originator: options.originator,
555
+ toolName,
556
+ // Surface the Vercel-AI input dict directly as Pliuz tool_args
557
+ // instead of wrapping in `{ args: [input] }` for cleaner audit logs.
558
+ toolArgs: (input) => isPlainObject2(input) ? input : { input }
559
+ },
560
+ async (input) => originalExecute(input, pendingOptions)
561
+ );
562
+ const wrappedExecute = async (input, opts) => {
563
+ pendingOptions = opts;
564
+ return gatedExecute1Arg(input);
565
+ };
566
+ return {
567
+ ...tool,
568
+ execute: wrappedExecute
569
+ };
570
+ }
571
+ function isPlainObject2(v) {
572
+ return typeof v === "object" && v !== null && !Array.isArray(v);
573
+ }
574
+
575
+ exports.ADAPTER_NAME = ADAPTER_NAME;
576
+ exports.gatedTool = gatedTool;
577
+ //# sourceMappingURL=ai.cjs.map
578
+ //# sourceMappingURL=ai.cjs.map