@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/bridge.d.ts +27 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +58 -0
- package/dist/bridge.js.map +1 -1
- package/dist/bridge.test.js +93 -0
- package/dist/bridge.test.js.map +1 -1
- package/dist/fias.d.ts +9 -4
- package/dist/fias.d.ts.map +1 -1
- package/dist/fias.js +15 -8
- package/dist/fias.js.map +1 -1
- package/dist/generated/permissions.d.ts +1 -1
- package/dist/generated/permissions.d.ts.map +1 -1
- package/dist/generated/permissions.js +2 -0
- package/dist/generated/permissions.js.map +1 -1
- package/dist/hooks.d.ts +34 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +76 -3
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.test.js +67 -0
- package/dist/hooks.test.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +538 -19
- package/dist/panels.d.ts +213 -0
- package/dist/panels.d.ts.map +1 -0
- package/dist/panels.js +422 -0
- package/dist/panels.js.map +1 -0
- package/dist/panels.test.d.ts +2 -0
- package/dist/panels.test.d.ts.map +1 -0
- package/dist/panels.test.js +127 -0
- package/dist/panels.test.js.map +1 -0
- package/dist/types.d.ts +174 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/default/AGENTS.md +131 -0
- package/templates/default/CLAUDE.md +131 -0
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(
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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(
|
|
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(
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
-
|
|
2255
|
-
|
|
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
|
};
|