@fias/arche-sdk 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -124,6 +124,12 @@ var FiasBridge = class {
124
124
  // useFiasPreviewState() callers don't collide. Providers produce a snapshot
125
125
  // on `preview_state_capture`; restore listeners receive it after the reload.
126
126
  this.previewStateProviders = /* @__PURE__ */ new Map();
127
+ /**
128
+ * The plugin's handler for host-dispatched Fias AI actions (one per plugin
129
+ * — `useFiasAIActions` owns registration). Throw/reject to report failure;
130
+ * the assistant awaits the result before telling the model it applied.
131
+ */
132
+ this.aiActionHandler = null;
127
133
  this.previewStateRestoreListeners = /* @__PURE__ */ new Map();
128
134
  // A restore payload that arrived before its hook registered (the host
129
135
  // restores on `ready`, but the React effect that registers runs later).
@@ -211,6 +217,30 @@ var FiasBridge = class {
211
217
  }
212
218
  return;
213
219
  }
220
+ if (data.type === "ai_action_dispatch") {
221
+ const { messageId } = data;
222
+ const payload = data.payload;
223
+ const handler = this.aiActionHandler;
224
+ void (async () => {
225
+ try {
226
+ if (typeof payload?.actionId !== "string") {
227
+ throw new Error("malformed dispatch: missing actionId");
228
+ }
229
+ if (!handler) {
230
+ throw new Error("the plugin has no Fias AI action handler registered");
231
+ }
232
+ await handler(payload.actionId, payload.params ?? {});
233
+ this.send("ai_action_result", { ok: true }, messageId);
234
+ } catch (err) {
235
+ this.send(
236
+ "ai_action_result",
237
+ { ok: false, error: err instanceof Error ? err.message : String(err) },
238
+ messageId
239
+ );
240
+ }
241
+ })();
242
+ return;
243
+ }
214
244
  if (data.type === "subscription_event") {
215
245
  const payload = data.payload;
216
246
  if (typeof payload?.subscriptionId === "string" && payload.event) {
@@ -491,6 +521,32 @@ var FiasBridge = class {
491
521
  this.subscriptionEndedListeners.delete(subscriptionId);
492
522
  this.send("subscription_unsubscribe", { subscriptionId });
493
523
  }
524
+ /**
525
+ * Register (or clear, with null) THE handler for host-dispatched Fias AI
526
+ * actions. One per plugin — `useFiasAIActions` owns this; last write wins.
527
+ */
528
+ setAiActionHandler(handler) {
529
+ this.aiActionHandler = handler;
530
+ }
531
+ /**
532
+ * Declare (or clear, with null) the plugin's runtime Fias AI actions.
533
+ * Fire-and-forget; the host validates, bounds, and throttles (~1/s), and
534
+ * honours the runtime lane only for TRUSTED arches — non-trusted plugins
535
+ * declare statically via the manifest `fiasAI` block. Requires the
536
+ * `ai:actions` permission.
537
+ */
538
+ declareAiActions(declaration) {
539
+ this.send("ai_actions_declare", declaration);
540
+ }
541
+ /**
542
+ * Push the state snapshot the assistant sees (and `availableWhen` filters
543
+ * against). Keep it SMALL and flag-shaped ({documentOpen: true, …}): the
544
+ * host drops snapshots over its byte cap, and accepted ones are embedded
545
+ * in the assistant's system prompt every turn.
546
+ */
547
+ sendAiState(state) {
548
+ this.send("ai_state", state);
549
+ }
494
550
  send(type, payload, messageId) {
495
551
  if (typeof window === "undefined") return;
496
552
  const message = {
@@ -1191,7 +1247,8 @@ function useFiasDataStore() {
1191
1247
  searchable: options?.searchable,
1192
1248
  writeMinRole: options?.writeMinRole,
1193
1249
  readMinRole: options?.readMinRole,
1194
- writePolicy: options?.writePolicy
1250
+ writePolicy: options?.writePolicy,
1251
+ constraints: options?.constraints
1195
1252
  }
1196
1253
  );
1197
1254
  return res.collection;
@@ -1214,12 +1271,18 @@ function useFiasDataStore() {
1214
1271
  );
1215
1272
  const put = useCallback(
1216
1273
  async (collection, key, data, options) => {
1217
- await invokeEntityOp(bridge, PLUGIN_DATASTORE_ENTITY_ID, "put", {
1218
- collection,
1219
- key,
1220
- data,
1221
- workspaceId: options?.workspaceId
1222
- });
1274
+ const res = await invokeEntityOp(
1275
+ bridge,
1276
+ PLUGIN_DATASTORE_ENTITY_ID,
1277
+ "put",
1278
+ {
1279
+ collection,
1280
+ key,
1281
+ data,
1282
+ workspaceId: options?.workspaceId
1283
+ }
1284
+ );
1285
+ return { version: res?.version };
1223
1286
  },
1224
1287
  [bridge]
1225
1288
  );
@@ -1251,6 +1314,7 @@ function useFiasDataStore() {
1251
1314
  key: res.key ?? key,
1252
1315
  data: res.data,
1253
1316
  updatedAt: res.updatedAt ?? "",
1317
+ ...res.version !== void 0 ? { version: res.version } : {},
1254
1318
  ...res.authoredByCollaborator !== void 0 ? { authoredByCollaborator: res.authoredByCollaborator } : {}
1255
1319
  };
1256
1320
  },
@@ -1294,8 +1358,17 @@ function useFiasDataStore() {
1294
1358
  [bridge]
1295
1359
  );
1296
1360
  const batch = useCallback(
1297
- async (operations) => {
1298
- await invokeEntityOp(bridge, PLUGIN_DATASTORE_ENTITY_ID, "batch", { operations });
1361
+ async (operations, options) => {
1362
+ const res = await invokeEntityOp(
1363
+ bridge,
1364
+ PLUGIN_DATASTORE_ENTITY_ID,
1365
+ "batch",
1366
+ {
1367
+ operations,
1368
+ ...options?.preconditions?.length ? { preconditions: options.preconditions } : {}
1369
+ }
1370
+ );
1371
+ return { written: res?.written };
1299
1372
  },
1300
1373
  [bridge]
1301
1374
  );
@@ -2034,6 +2107,48 @@ function useCommunityAssets() {
2034
2107
  [publish, publishMany, unpublish, listMine, listCommunity, get, getUrl, getUrls, report]
2035
2108
  );
2036
2109
  }
2110
+ function useFiasAIActions(config) {
2111
+ const bridge = useBridge();
2112
+ const onActionRef = useRef(config?.onAction ?? null);
2113
+ onActionRef.current = config?.onAction ?? null;
2114
+ useEffect2(() => {
2115
+ bridge.setAiActionHandler((actionId, params) => {
2116
+ const handler = onActionRef.current;
2117
+ if (!handler) {
2118
+ throw new Error("no Fias AI action handler is currently registered");
2119
+ }
2120
+ return handler(actionId, params);
2121
+ });
2122
+ return () => bridge.setAiActionHandler(null);
2123
+ }, [bridge]);
2124
+ const declarationJson = config ? JSON.stringify({ description: config.description, actions: config.actions }) : null;
2125
+ useEffect2(() => {
2126
+ let cancelled = false;
2127
+ void bridge.waitForInit().then(() => {
2128
+ if (cancelled) return;
2129
+ bridge.declareAiActions(
2130
+ declarationJson ? JSON.parse(declarationJson) : null
2131
+ );
2132
+ });
2133
+ return () => {
2134
+ cancelled = true;
2135
+ };
2136
+ }, [bridge, declarationJson]);
2137
+ useEffect2(() => {
2138
+ return () => bridge.declareAiActions(null);
2139
+ }, [bridge]);
2140
+ const stateJson = config?.state ? JSON.stringify(config.state) : null;
2141
+ useEffect2(() => {
2142
+ if (stateJson === null) return;
2143
+ let cancelled = false;
2144
+ void bridge.waitForInit().then(() => {
2145
+ if (!cancelled) bridge.sendAiState(JSON.parse(stateJson));
2146
+ });
2147
+ return () => {
2148
+ cancelled = true;
2149
+ };
2150
+ }, [bridge, stateJson]);
2151
+ }
2037
2152
 
2038
2153
  // src/generated/surfaces.ts
2039
2154
  var AUDIO_AUDIO_ISOLATION_SURFACE_KEY = "audio.audio-isolation";
@@ -2101,6 +2216,7 @@ var useIntegrationWebSearch = () => useSurface(
2101
2216
 
2102
2217
  // src/generated/permissions.ts
2103
2218
  var VALID_PLUGIN_PERMISSIONS = [
2219
+ "ai:actions",
2104
2220
  "assets:community:publish",
2105
2221
  "assets:community:read",
2106
2222
  "assets:read",
@@ -2127,6 +2243,7 @@ function isValidPluginPermission(perm) {
2127
2243
  return VALID_PLUGIN_PERMISSIONS.includes(perm);
2128
2244
  }
2129
2245
  var PLUGIN_PERMISSION_DESCRIPTIONS = {
2246
+ "ai:actions": "Let Fias AI operate this app for you \u2014 only actions you could do yourself in its interface, and risky ones ask you first.",
2130
2247
  "assets:community:publish": "Share images you made here so other people using this app can see them, and manage what you have shared.",
2131
2248
  "assets:community:read": "Show you images that other people using this app have shared. Everything shared is checked before anyone can see it.",
2132
2249
  "assets:read": "Show images from this app's published asset library.",
@@ -2186,7 +2303,8 @@ var fias = {
2186
2303
  name,
2187
2304
  userScope: options?.userScope ?? "user",
2188
2305
  searchable: options?.searchable,
2189
- writePolicy: options?.writePolicy
2306
+ writePolicy: options?.writePolicy,
2307
+ constraints: options?.constraints
2190
2308
  }
2191
2309
  );
2192
2310
  return res.collection;
@@ -2202,12 +2320,16 @@ var fias = {
2202
2320
  async deleteCollection(name) {
2203
2321
  await invokeEntityOp(getBridge(), PLUGIN_DATASTORE_ENTITY_ID, "delete_collection", { name });
2204
2322
  },
2323
+ /** Write a document. Resolves with the document's post-write `version`
2324
+ * (ADR-014) so a follow-up CAS batch needs no read-back. */
2205
2325
  async put(collection, key, data) {
2206
- await invokeEntityOp(getBridge(), PLUGIN_DATASTORE_ENTITY_ID, "put", {
2207
- collection,
2208
- key,
2209
- data
2210
- });
2326
+ const res = await invokeEntityOp(
2327
+ getBridge(),
2328
+ PLUGIN_DATASTORE_ENTITY_ID,
2329
+ "put",
2330
+ { collection, key, data }
2331
+ );
2332
+ return { version: res?.version };
2211
2333
  },
2212
2334
  async get(collection, key) {
2213
2335
  const res = await invokeEntityOp(
@@ -2227,6 +2349,7 @@ var fias = {
2227
2349
  key: res.key ?? key,
2228
2350
  data: res.data,
2229
2351
  updatedAt: res.updatedAt ?? "",
2352
+ ...res.version !== void 0 ? { version: res.version } : {},
2230
2353
  ...res.authoredByCollaborator !== void 0 ? { authoredByCollaborator: res.authoredByCollaborator } : {}
2231
2354
  };
2232
2355
  },
@@ -2250,9 +2373,20 @@ var fias = {
2250
2373
  async delete(collection, key) {
2251
2374
  await invokeEntityOp(getBridge(), PLUGIN_DATASTORE_ENTITY_ID, "delete", { collection, key });
2252
2375
  },
2253
- /** Apply up to 25 put/delete ops in one all-or-nothing transaction. */
2254
- async batch(operations) {
2255
- await invokeEntityOp(getBridge(), PLUGIN_DATASTORE_ENTITY_ID, "batch", { operations });
2376
+ /** Apply up to 25 put/delete ops in one all-or-nothing transaction.
2377
+ * Pass `options.preconditions` (ADR-014) to make the batch conditional;
2378
+ * the result carries each put's post-write version. */
2379
+ async batch(operations, options) {
2380
+ const res = await invokeEntityOp(
2381
+ getBridge(),
2382
+ PLUGIN_DATASTORE_ENTITY_ID,
2383
+ "batch",
2384
+ {
2385
+ operations,
2386
+ ...options?.preconditions?.length ? { preconditions: options.preconditions } : {}
2387
+ }
2388
+ );
2389
+ return { written: res?.written };
2256
2390
  }
2257
2391
  }
2258
2392
  };
@@ -4213,6 +4347,371 @@ function raceWithTimeout(promise, ms) {
4213
4347
  function useFiasFonts() {
4214
4348
  return useMemo3(() => ({ fonts: FONT_CATALOG, ensureFontLoaded }), []);
4215
4349
  }
4350
+
4351
+ // src/panels.ts
4352
+ var PANEL_MIN_ELEMENTS = 1;
4353
+ var PANEL_MAX_ELEMENTS = 24;
4354
+ var PANEL_TITLE_MAX_LENGTH = 80;
4355
+ var PANEL_TEXT_MAX_LENGTH = 500;
4356
+ var PANEL_IMAGE_ALT_MAX_LENGTH = 120;
4357
+ var PANEL_IMAGE_KEY_MAX_LENGTH = 500;
4358
+ var PANEL_IMAGE_MIN_HEIGHT = 24;
4359
+ var PANEL_IMAGE_MAX_HEIGHT = 512;
4360
+ var PANEL_BUTTON_LABEL_MAX_LENGTH = 40;
4361
+ var PANEL_ACTION_MAX_LENGTH = 2048;
4362
+ var PANEL_EMIT_TOKEN_MAX_LENGTH = 64;
4363
+ var PANEL_ITEMS_EMPTY_TEXT_MAX_LENGTH = 120;
4364
+ var PANEL_ITEMS_SLOT_MAX_LENGTH = 64;
4365
+ var PANEL_SHOW_IF_MAX_LENGTH = 64;
4366
+ var PANEL_PRIORITY_ABS_MAX = 1e3;
4367
+ var PANEL_THEME_FONT_MAX_LENGTH = 200;
4368
+ var PANEL_THEME_AMOUNT_FORMAT_MAX_LENGTH = 24;
4369
+ var PANEL_THEME_Z_MAX = 1e6;
4370
+ var PANEL_THEME_LENGTH_MAX_INTEGER_DIGITS = 4;
4371
+ var PANEL_IMAGE_ROOTS = ["dfe-icons", "dfe-avatars"];
4372
+ var PANEL_ELEMENT_KINDS = [
4373
+ "text",
4374
+ "conditionalText",
4375
+ "image",
4376
+ "assetImage",
4377
+ "items",
4378
+ "slottedItems",
4379
+ "button",
4380
+ "spacer"
4381
+ ];
4382
+ var PANEL_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
4383
+ var SLOT_PATTERN = new RegExp(`^[a-z0-9][a-z0-9_-]{0,${PANEL_ITEMS_SLOT_MAX_LENGTH - 1}}$`);
4384
+ var EMIT_TOKEN_PATTERN = new RegExp(`^[a-z0-9][a-z0-9_-]{0,${PANEL_EMIT_TOKEN_MAX_LENGTH - 1}}$`);
4385
+ var SHOW_IF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
4386
+ var ASSET_ID_PATTERN = /^as_[a-f0-9]{32}$/;
4387
+ var ARCHE_ID_PATTERN = /^arc_[a-f0-9]{32}$/;
4388
+ var IMAGE_KEY_PATTERN = new RegExp(
4389
+ `^(?:${PANEL_IMAGE_ROOTS.join("|")})/[A-Za-z0-9._/-]+\\.(?:png|jpe?g|webp|gif|svg)$`,
4390
+ "i"
4391
+ );
4392
+ var THEME_COLOR_PATTERN = /^(?:#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d{1,3})\s*)?\))$/i;
4393
+ var THEME_LENGTH_PATTERN = new RegExp(
4394
+ `^\\d{1,${PANEL_THEME_LENGTH_MAX_INTEGER_DIGITS}}(?:\\.\\d{1,3})?(?:px|rem|em)$`
4395
+ );
4396
+ var THEME_FONT_PATTERN = /^[A-Za-z0-9 '",._-]+$/;
4397
+ var TEXT_STYLES = ["title", "body", "caption"];
4398
+ var TEXT_ALIGNS = ["left", "center", "right"];
4399
+ var SIZES = ["small", "medium", "large"];
4400
+ var BUTTON_VARIANTS = ["primary", "secondary"];
4401
+ function isRecord(value) {
4402
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4403
+ }
4404
+ function rejectUnknownKeys(value, allowed, path, issues) {
4405
+ for (const key of Object.keys(value)) {
4406
+ if (!allowed.includes(key)) issues.push(`${path}: unknown field "${key}"`);
4407
+ }
4408
+ }
4409
+ function checkString(value, path, issues, opts) {
4410
+ if (typeof value !== "string") {
4411
+ issues.push(`${path}: expected a string`);
4412
+ return;
4413
+ }
4414
+ if (value.length < (opts.min ?? 1)) issues.push(`${path}: must not be empty`);
4415
+ if (value.length > opts.max) issues.push(`${path}: longer than ${opts.max} characters`);
4416
+ if (opts.pattern && !opts.pattern.test(value)) {
4417
+ issues.push(`${path}: ${opts.hint ?? "does not match the required format"}`);
4418
+ }
4419
+ }
4420
+ function checkOptionalEnum(value, path, issues, allowed) {
4421
+ if (value === void 0) return;
4422
+ if (typeof value !== "string" || !allowed.includes(value)) {
4423
+ issues.push(`${path}: must be one of ${allowed.join(" | ")}`);
4424
+ }
4425
+ }
4426
+ function checkOptionalBoolean(value, path, issues) {
4427
+ if (value !== void 0 && typeof value !== "boolean") issues.push(`${path}: expected a boolean`);
4428
+ }
4429
+ function checkOptionalIntRange(value, path, issues, min, max) {
4430
+ if (value === void 0) return;
4431
+ if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
4432
+ issues.push(`${path}: must be an integer between ${min} and ${max}`);
4433
+ }
4434
+ }
4435
+ function validatePanelAction(action, path, issues) {
4436
+ if (typeof action !== "string" || action.length === 0) {
4437
+ issues.push(`${path}: expected an action string`);
4438
+ return;
4439
+ }
4440
+ if (action.length > PANEL_ACTION_MAX_LENGTH) {
4441
+ issues.push(`${path}: longer than ${PANEL_ACTION_MAX_LENGTH} characters`);
4442
+ return;
4443
+ }
4444
+ if (action === "close") return;
4445
+ if (action.startsWith("emit:")) {
4446
+ if (!EMIT_TOKEN_PATTERN.test(action.slice("emit:".length))) {
4447
+ issues.push(
4448
+ `${path}: emit token must be 1\u2013${PANEL_EMIT_TOKEN_MAX_LENGTH} chars \u2014 lowercase letters, digits, hyphens and underscores, not starting with a separator`
4449
+ );
4450
+ }
4451
+ return;
4452
+ }
4453
+ if (action.startsWith("open_arche:")) {
4454
+ if (!ARCHE_ID_PATTERN.test(action.slice("open_arche:".length))) {
4455
+ issues.push(`${path}: open_arche target must be an arche id (arc_ + 32 hex chars)`);
4456
+ }
4457
+ return;
4458
+ }
4459
+ if (action.startsWith("open_url:")) {
4460
+ if (/\s/.test(action)) {
4461
+ issues.push(`${path}: action must not contain whitespace`);
4462
+ return;
4463
+ }
4464
+ let parsed;
4465
+ try {
4466
+ parsed = new URL(action.slice("open_url:".length));
4467
+ } catch {
4468
+ issues.push(`${path}: open_url must be a valid absolute URL`);
4469
+ return;
4470
+ }
4471
+ if (parsed.protocol !== "https:") issues.push(`${path}: open_url scheme must be https`);
4472
+ return;
4473
+ }
4474
+ issues.push(
4475
+ `${path}: must be one of close | emit:<token> | open_arche:arc_<32 hex> | open_url:https://\u2026`
4476
+ );
4477
+ }
4478
+ function validateTextFields(element, path, issues) {
4479
+ checkString(element.text, `${path}.text`, issues, { max: PANEL_TEXT_MAX_LENGTH });
4480
+ checkOptionalEnum(element.style, `${path}.style`, issues, TEXT_STYLES);
4481
+ checkOptionalEnum(element.align, `${path}.align`, issues, TEXT_ALIGNS);
4482
+ checkOptionalBoolean(element.joinNext, `${path}.joinNext`, issues);
4483
+ }
4484
+ function validateItemsFields(element, path, issues) {
4485
+ checkOptionalBoolean(element.showIcons, `${path}.showIcons`, issues);
4486
+ checkOptionalEnum(element.iconSize, `${path}.iconSize`, issues, SIZES);
4487
+ if (element.emptyText !== void 0) {
4488
+ checkString(element.emptyText, `${path}.emptyText`, issues, {
4489
+ min: 0,
4490
+ max: PANEL_ITEMS_EMPTY_TEXT_MAX_LENGTH
4491
+ });
4492
+ }
4493
+ }
4494
+ function validateImageFields(element, path, issues) {
4495
+ if (element.alt !== void 0) {
4496
+ checkString(element.alt, `${path}.alt`, issues, { min: 0, max: PANEL_IMAGE_ALT_MAX_LENGTH });
4497
+ }
4498
+ checkOptionalIntRange(
4499
+ element.maxHeight,
4500
+ `${path}.maxHeight`,
4501
+ issues,
4502
+ PANEL_IMAGE_MIN_HEIGHT,
4503
+ PANEL_IMAGE_MAX_HEIGHT
4504
+ );
4505
+ }
4506
+ function validateElement(value, path, issues) {
4507
+ if (!isRecord(value)) {
4508
+ issues.push(`${path}: expected an object`);
4509
+ return;
4510
+ }
4511
+ const kind = value.kind;
4512
+ if (typeof kind !== "string" || !PANEL_ELEMENT_KINDS.includes(kind)) {
4513
+ issues.push(
4514
+ `${path}.kind: unknown element kind ${JSON.stringify(kind)} \u2014 expected one of ${PANEL_ELEMENT_KINDS.join(" | ")}`
4515
+ );
4516
+ return;
4517
+ }
4518
+ switch (kind) {
4519
+ case "text":
4520
+ rejectUnknownKeys(value, ["kind", "text", "style", "align", "joinNext"], path, issues);
4521
+ validateTextFields(value, path, issues);
4522
+ return;
4523
+ case "conditionalText":
4524
+ rejectUnknownKeys(
4525
+ value,
4526
+ ["kind", "text", "style", "align", "joinNext", "showIf"],
4527
+ path,
4528
+ issues
4529
+ );
4530
+ validateTextFields(value, path, issues);
4531
+ checkString(value.showIf, `${path}.showIf`, issues, {
4532
+ max: PANEL_SHOW_IF_MAX_LENGTH,
4533
+ pattern: SHOW_IF_PATTERN,
4534
+ hint: "must be a value NAME (letters, digits, underscore; not starting with a digit) \u2014 expressions and operators are not supported"
4535
+ });
4536
+ return;
4537
+ case "image":
4538
+ rejectUnknownKeys(value, ["kind", "imageKey", "alt", "maxHeight"], path, issues);
4539
+ checkString(value.imageKey, `${path}.imageKey`, issues, {
4540
+ max: PANEL_IMAGE_KEY_MAX_LENGTH,
4541
+ pattern: IMAGE_KEY_PATTERN,
4542
+ hint: `must be a relative key under ${PANEL_IMAGE_ROOTS.join(" or ")}/ ending in an image extension (no URL, no leading slash)`
4543
+ });
4544
+ if (typeof value.imageKey === "string" && value.imageKey.includes("..")) {
4545
+ issues.push(`${path}.imageKey: must not contain ".."`);
4546
+ }
4547
+ validateImageFields(value, path, issues);
4548
+ return;
4549
+ case "assetImage":
4550
+ rejectUnknownKeys(value, ["kind", "assetId", "alt", "maxHeight"], path, issues);
4551
+ checkString(value.assetId, `${path}.assetId`, issues, {
4552
+ max: 64,
4553
+ pattern: ASSET_ID_PATTERN,
4554
+ hint: "must be an arche asset id (as_ + 32 hex chars)"
4555
+ });
4556
+ validateImageFields(value, path, issues);
4557
+ return;
4558
+ case "items":
4559
+ rejectUnknownKeys(value, ["kind", "showIcons", "iconSize", "emptyText"], path, issues);
4560
+ validateItemsFields(value, path, issues);
4561
+ return;
4562
+ case "slottedItems":
4563
+ rejectUnknownKeys(
4564
+ value,
4565
+ ["kind", "slot", "showIcons", "iconSize", "emptyText"],
4566
+ path,
4567
+ issues
4568
+ );
4569
+ checkString(value.slot, `${path}.slot`, issues, {
4570
+ max: PANEL_ITEMS_SLOT_MAX_LENGTH,
4571
+ pattern: SLOT_PATTERN,
4572
+ hint: "must be lowercase letters, digits, hyphens and underscores, not starting with a separator"
4573
+ });
4574
+ validateItemsFields(value, path, issues);
4575
+ return;
4576
+ case "button":
4577
+ rejectUnknownKeys(value, ["kind", "label", "action", "variant"], path, issues);
4578
+ checkString(value.label, `${path}.label`, issues, { max: PANEL_BUTTON_LABEL_MAX_LENGTH });
4579
+ validatePanelAction(value.action, `${path}.action`, issues);
4580
+ checkOptionalEnum(value.variant, `${path}.variant`, issues, BUTTON_VARIANTS);
4581
+ return;
4582
+ case "spacer":
4583
+ rejectUnknownKeys(value, ["kind", "size"], path, issues);
4584
+ checkOptionalEnum(value.size, `${path}.size`, issues, SIZES);
4585
+ return;
4586
+ }
4587
+ }
4588
+ var THEME_COLOR_FIELDS = ["bg", "fg", "border", "accent", "accentFg", "scrim"];
4589
+ var THEME_LENGTH_FIELDS = [
4590
+ "radius",
4591
+ "buttonRadius",
4592
+ "gap",
4593
+ "fontSize",
4594
+ "widthSmall",
4595
+ "widthMedium",
4596
+ "widthLarge"
4597
+ ];
4598
+ function validatePanelTheme(value, path = "theme") {
4599
+ const issues = [];
4600
+ if (!isRecord(value)) return { ok: false, issues: [`${path}: expected an object`] };
4601
+ rejectUnknownKeys(
4602
+ value,
4603
+ [...THEME_COLOR_FIELDS, ...THEME_LENGTH_FIELDS, "font", "z", "amountFormat"],
4604
+ path,
4605
+ issues
4606
+ );
4607
+ for (const field of THEME_COLOR_FIELDS) {
4608
+ if (value[field] === void 0) continue;
4609
+ checkString(value[field], `${path}.${field}`, issues, {
4610
+ max: 64,
4611
+ pattern: THEME_COLOR_PATTERN,
4612
+ hint: "must be a hex colour (#rgb, #rgba, #rrggbb, #rrggbbaa) or rgb()/rgba() with numeric components"
4613
+ });
4614
+ }
4615
+ for (const field of THEME_LENGTH_FIELDS) {
4616
+ if (value[field] === void 0) continue;
4617
+ checkString(value[field], `${path}.${field}`, issues, {
4618
+ max: 16,
4619
+ pattern: THEME_LENGTH_PATTERN,
4620
+ hint: `must be a non-negative number (max ${PANEL_THEME_LENGTH_MAX_INTEGER_DIGITS} digits before the point) followed by px, rem or em`
4621
+ });
4622
+ }
4623
+ if (value.font !== void 0) {
4624
+ checkString(value.font, `${path}.font`, issues, {
4625
+ max: PANEL_THEME_FONT_MAX_LENGTH,
4626
+ pattern: THEME_FONT_PATTERN,
4627
+ hint: "must be a font-family stack (names, commas, quotes) \u2014 no parentheses, so no url() or var()"
4628
+ });
4629
+ }
4630
+ checkOptionalIntRange(value.z, `${path}.z`, issues, 0, PANEL_THEME_Z_MAX);
4631
+ if (value.amountFormat !== void 0) {
4632
+ checkString(value.amountFormat, `${path}.amountFormat`, issues, {
4633
+ max: PANEL_THEME_AMOUNT_FORMAT_MAX_LENGTH
4634
+ });
4635
+ if (typeof value.amountFormat === "string" && !value.amountFormat.includes("{n}")) {
4636
+ issues.push(`${path}.amountFormat: must contain the placeholder {n}`);
4637
+ }
4638
+ }
4639
+ return issues.length === 0 ? { ok: true, issues: [] } : { ok: false, issues };
4640
+ }
4641
+ function validatePanelDefinition(value, path = "panel") {
4642
+ const issues = [];
4643
+ if (!isRecord(value)) return { ok: false, issues: [`${path}: expected an object`] };
4644
+ rejectUnknownKeys(value, ["id", "title", "elements", "presentation"], path, issues);
4645
+ checkString(value.id, `${path}.id`, issues, {
4646
+ max: 64,
4647
+ pattern: PANEL_ID_PATTERN,
4648
+ hint: "must be 2\u201364 chars: lowercase letters, digits and hyphens, not starting with a hyphen"
4649
+ });
4650
+ checkString(value.title, `${path}.title`, issues, { max: PANEL_TITLE_MAX_LENGTH });
4651
+ if (!Array.isArray(value.elements)) {
4652
+ issues.push(`${path}.elements: expected an array`);
4653
+ } else {
4654
+ if (value.elements.length < PANEL_MIN_ELEMENTS) {
4655
+ issues.push(`${path}.elements: needs at least ${PANEL_MIN_ELEMENTS} element`);
4656
+ }
4657
+ if (value.elements.length > PANEL_MAX_ELEMENTS) {
4658
+ issues.push(`${path}.elements: more than ${PANEL_MAX_ELEMENTS} elements`);
4659
+ }
4660
+ value.elements.forEach(
4661
+ (element, index) => validateElement(element, `${path}.elements[${index}]`, issues)
4662
+ );
4663
+ }
4664
+ if (value.presentation !== void 0) {
4665
+ if (!isRecord(value.presentation)) {
4666
+ issues.push(`${path}.presentation: expected an object`);
4667
+ } else {
4668
+ rejectUnknownKeys(
4669
+ value.presentation,
4670
+ ["size", "dismissible", "priority"],
4671
+ `${path}.presentation`,
4672
+ issues
4673
+ );
4674
+ checkOptionalEnum(value.presentation.size, `${path}.presentation.size`, issues, SIZES);
4675
+ checkOptionalBoolean(
4676
+ value.presentation.dismissible,
4677
+ `${path}.presentation.dismissible`,
4678
+ issues
4679
+ );
4680
+ checkOptionalIntRange(
4681
+ value.presentation.priority,
4682
+ `${path}.presentation.priority`,
4683
+ issues,
4684
+ -PANEL_PRIORITY_ABS_MAX,
4685
+ PANEL_PRIORITY_ABS_MAX
4686
+ );
4687
+ }
4688
+ }
4689
+ return issues.length === 0 ? { ok: true, issues: [] } : { ok: false, issues };
4690
+ }
4691
+ function validatePanelCatalog(value) {
4692
+ const issues = [];
4693
+ if (!isRecord(value)) return { ok: false, issues: ["catalog: expected an object"] };
4694
+ rejectUnknownKeys(value, ["panels", "theme"], "catalog", issues);
4695
+ if (value.theme !== void 0) {
4696
+ const theme = validatePanelTheme(value.theme, "catalog.theme");
4697
+ if (!theme.ok) issues.push(...theme.issues);
4698
+ }
4699
+ if (!Array.isArray(value.panels)) {
4700
+ issues.push("catalog.panels: expected an array");
4701
+ return { ok: false, issues };
4702
+ }
4703
+ const seen = /* @__PURE__ */ new Set();
4704
+ value.panels.forEach((panel, index) => {
4705
+ const path = `catalog.panels[${index}]`;
4706
+ const result = validatePanelDefinition(panel, path);
4707
+ if (!result.ok) issues.push(...result.issues);
4708
+ if (isRecord(panel) && typeof panel.id === "string") {
4709
+ if (seen.has(panel.id)) issues.push(`${path}.id: duplicate panel id "${panel.id}"`);
4710
+ seen.add(panel.id);
4711
+ }
4712
+ });
4713
+ return issues.length === 0 ? { ok: true, issues: [] } : { ok: false, issues };
4714
+ }
4216
4715
  export {
4217
4716
  AUDIO_AUDIO_ISOLATION_SURFACE_KEY,
4218
4717
  AUDIO_FORCED_ALIGNMENT_SURFACE_KEY,
@@ -4245,6 +4744,21 @@ export {
4245
4744
  LIGHT_THEME,
4246
4745
  NLLB_LANGUAGES,
4247
4746
  NLLB_TRANSLATION_ENTITY_ID,
4747
+ PANEL_BUTTON_LABEL_MAX_LENGTH,
4748
+ PANEL_ELEMENT_KINDS,
4749
+ PANEL_EMIT_TOKEN_MAX_LENGTH,
4750
+ PANEL_IMAGE_ROOTS,
4751
+ PANEL_ITEMS_SLOT_MAX_LENGTH,
4752
+ PANEL_MAX_ELEMENTS,
4753
+ PANEL_MIN_ELEMENTS,
4754
+ PANEL_PRIORITY_ABS_MAX,
4755
+ PANEL_SHOW_IF_MAX_LENGTH,
4756
+ PANEL_TEXT_MAX_LENGTH,
4757
+ PANEL_THEME_AMOUNT_FORMAT_MAX_LENGTH,
4758
+ PANEL_THEME_FONT_MAX_LENGTH,
4759
+ PANEL_THEME_LENGTH_MAX_INTEGER_DIGITS,
4760
+ PANEL_THEME_Z_MAX,
4761
+ PANEL_TITLE_MAX_LENGTH,
4248
4762
  PLUGIN_PERMISSION_DESCRIPTIONS,
4249
4763
  SDK_BRIDGE_PROTOCOL_VERSION,
4250
4764
  SDK_REQUIRES_HOST_PROTOCOL_VERSION,
@@ -4273,6 +4787,7 @@ export {
4273
4787
  useElevenLabsVoiceChanger,
4274
4788
  useElevenLabsVoiceDesign,
4275
4789
  useEntityInvocation,
4790
+ useFiasAIActions,
4276
4791
  useFiasDataStore,
4277
4792
  useFiasFonts,
4278
4793
  useFiasNavigation,
@@ -4302,5 +4817,9 @@ export {
4302
4817
  useStepNavigation,
4303
4818
  useSurface,
4304
4819
  useVaultDocuments,
4305
- useVaultUserDocuments
4820
+ useVaultUserDocuments,
4821
+ validatePanelAction,
4822
+ validatePanelCatalog,
4823
+ validatePanelDefinition,
4824
+ validatePanelTheme
4306
4825
  };