@ai-gui/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -239,6 +239,261 @@ function sanitizeHtml(html, options = {}) {
239
239
  return dompurify.default.sanitize(html);
240
240
  }
241
241
 
242
+ //#endregion
243
+ //#region src/debug-events.ts
244
+ const DEFAULT_MAX_STRING_LENGTH = 2048;
245
+ const DEFAULT_MAX_DEPTH = 8;
246
+ const DEFAULT_MAX_NODES = 1e3;
247
+ const SENSITIVE_KEY_PARTS = [
248
+ "authorization",
249
+ "auth",
250
+ "accesstoken",
251
+ "refreshtoken",
252
+ "clientsecret",
253
+ "credentials",
254
+ "cookie",
255
+ "setcookie",
256
+ "proxyauthorization",
257
+ "password",
258
+ "passwd",
259
+ "apikey",
260
+ "secret",
261
+ "token"
262
+ ];
263
+ var DebugEmitter = class {
264
+ enabled;
265
+ source;
266
+ limits;
267
+ listeners = new Set();
268
+ sequence = 0;
269
+ constructor(source, options = {}) {
270
+ this.source = source;
271
+ this.enabled = options.debug === true;
272
+ this.limits = {
273
+ maxStringLength: positiveInteger(options.maxStringLength, DEFAULT_MAX_STRING_LENGTH, "maxStringLength"),
274
+ maxDepth: positiveInteger(options.maxDepth, DEFAULT_MAX_DEPTH, "maxDepth"),
275
+ maxNodes: positiveInteger(options.maxNodes, DEFAULT_MAX_NODES, "maxNodes"),
276
+ redact: options.redact
277
+ };
278
+ if (this.enabled && options.onDebugEvent) this.listeners.add(options.onDebugEvent);
279
+ }
280
+ get active() {
281
+ return this.enabled && this.listeners.size > 0;
282
+ }
283
+ get available() {
284
+ return this.enabled;
285
+ }
286
+ subscribe(listener) {
287
+ if (!this.enabled) return () => {};
288
+ this.listeners.add(listener);
289
+ return () => this.listeners.delete(listener);
290
+ }
291
+ emit(type, data = {}) {
292
+ if (!this.active) return;
293
+ const event = Object.freeze({
294
+ type,
295
+ source: this.source,
296
+ timestamp: Date.now(),
297
+ sequence: ++this.sequence,
298
+ data: safeDebugValue(data, this.limits)
299
+ });
300
+ for (const listener of this.listeners) try {
301
+ listener(event);
302
+ } catch {}
303
+ }
304
+ };
305
+ function safeDebugValue(value, options = {}) {
306
+ const maxStringLength = positiveInteger(options.maxStringLength, DEFAULT_MAX_STRING_LENGTH, "maxStringLength");
307
+ const maxDepth = positiveInteger(options.maxDepth, DEFAULT_MAX_DEPTH, "maxDepth");
308
+ const maxNodes = positiveInteger(options.maxNodes, DEFAULT_MAX_NODES, "maxNodes");
309
+ const seen = new WeakSet();
310
+ let nodes = 0;
311
+ const visit = (input, key = "", path = [], depth = 0) => {
312
+ if (++nodes > maxNodes) return "[MAX_NODES]";
313
+ if (isSensitiveKey(key) || options.redact?.({
314
+ key,
315
+ path,
316
+ value: input
317
+ })) return "[REDACTED]";
318
+ if (typeof input === "string") return limitString(redactText(input), maxStringLength);
319
+ if (input === null || typeof input === "boolean") return input;
320
+ if (typeof input === "number") return Number.isFinite(input) ? input : String(input);
321
+ if (typeof input === "bigint" || typeof input === "symbol" || typeof input === "function" || typeof input === "undefined") return String(input);
322
+ if (input instanceof Error) return {
323
+ name: limitString(input.name || "Error", maxStringLength),
324
+ message: limitString(redactText(input.message), maxStringLength)
325
+ };
326
+ if (depth >= maxDepth) return "[MAX_DEPTH]";
327
+ if (seen.has(input)) return "[CIRCULAR]";
328
+ seen.add(input);
329
+ try {
330
+ if (Array.isArray(input)) return input.map((item, index) => visit(item, "", [...path, index], depth + 1));
331
+ const output = {};
332
+ for (const [childKey, child] of Object.entries(input)) output[childKey] = visit(child, childKey, [...path, childKey], depth + 1);
333
+ return output;
334
+ } finally {
335
+ seen.delete(input);
336
+ }
337
+ };
338
+ return visit(value);
339
+ }
340
+ function isSensitiveKey(key) {
341
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
342
+ return normalized !== "" && SENSITIVE_KEY_PARTS.some((part) => normalized.includes(part));
343
+ }
344
+ function redactText(value) {
345
+ return value.replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [REDACTED]").replace(/([?&](?:access[_-]?token|refresh[_-]?token|client[_-]?secret|api[_-]?key|password|passwd|token|secret)=)[^&#\s]*/gi, "$1[REDACTED]").replace(/\b((?:access[_-]?token|refresh[_-]?token|client[_-]?secret|api[_-]?key|password|passwd|token|secret)\s*[:=]\s*)[^\s,;]+/gi, "$1[REDACTED]");
346
+ }
347
+ function limitString(value, maxLength) {
348
+ if (value.includes("[REDACTED]") && maxLength < 20) return `[REDACTED][TRUNCATED length=${value.length}]`;
349
+ return value.length > maxLength ? `${value.slice(0, maxLength)}[TRUNCATED length=${value.length}]` : value;
350
+ }
351
+ function positiveInteger(value, fallback, name) {
352
+ if (value === void 0) return fallback;
353
+ if (!Number.isSafeInteger(value) || value <= 0) throw new TypeError(`${name} must be a positive integer`);
354
+ return value;
355
+ }
356
+
357
+ //#endregion
358
+ //#region src/json-schema.ts
359
+ /** Small, dependency-free validator for the JSON Schema subset used by AIGUI definitions. */
360
+ function validateJSONSchema(schema, value) {
361
+ const issues = [];
362
+ validate(schema, value, "$", issues);
363
+ return {
364
+ valid: issues.length === 0,
365
+ issues
366
+ };
367
+ }
368
+ function validate(schema, value, path, issues) {
369
+ if (Object.hasOwn(schema, "const") && !jsonEqual(value, schema.const)) {
370
+ issues.push(`${path} must equal ${describeJSONValue(schema.const)}`);
371
+ return;
372
+ }
373
+ if (schema.enum && !schema.enum.some((candidate) => jsonEqual(candidate, value))) {
374
+ issues.push(`${path} must be one of the allowed values`);
375
+ return;
376
+ }
377
+ if (schema.type === "object") {
378
+ if (!isJSONObject(value)) {
379
+ issues.push(`${path} must be an object`);
380
+ return;
381
+ }
382
+ for (const required of schema.required ?? []) if (!Object.hasOwn(value, required)) issues.push(`${path}.${required} is required`);
383
+ for (const [key, childSchema] of Object.entries(schema.properties ?? {})) if (Object.hasOwn(value, key)) validate(childSchema, value[key], `${path}.${key}`, issues);
384
+ if (schema.additionalProperties === false) {
385
+ const allowed = new Set(Object.keys(schema.properties ?? {}));
386
+ for (const key of Object.keys(value)) if (!allowed.has(key)) issues.push(`${path}.${key} is not allowed`);
387
+ } else if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) {
388
+ const allowed = new Set(Object.keys(schema.properties ?? {}));
389
+ for (const key of Object.keys(value)) if (!allowed.has(key)) validate(schema.additionalProperties, value[key], `${path}.${key}`, issues);
390
+ }
391
+ return;
392
+ }
393
+ if (schema.type === "array") {
394
+ if (!Array.isArray(value)) {
395
+ issues.push(`${path} must be an array`);
396
+ return;
397
+ }
398
+ if (schema.minItems !== void 0 && value.length < schema.minItems) issues.push(`${path} must contain at least ${schema.minItems} items`);
399
+ if (schema.maxItems !== void 0 && value.length > schema.maxItems) issues.push(`${path} must contain at most ${schema.maxItems} items`);
400
+ if (schema.items) value.forEach((item, index) => validate(schema.items, item, `${path}[${index}]`, issues));
401
+ return;
402
+ }
403
+ if (schema.type === "string") {
404
+ if (typeof value !== "string") {
405
+ issues.push(`${path} must be a string`);
406
+ return;
407
+ }
408
+ if (schema.minLength !== void 0 && value.length < schema.minLength) issues.push(`${path} must contain at least ${schema.minLength} characters`);
409
+ if (schema.maxLength !== void 0 && value.length > schema.maxLength) issues.push(`${path} must contain at most ${schema.maxLength} characters`);
410
+ if (schema.pattern !== void 0) try {
411
+ if (!new RegExp(schema.pattern).test(value)) issues.push(`${path} must match ${schema.pattern}`);
412
+ } catch {
413
+ issues.push(`${path} has an invalid schema pattern`);
414
+ }
415
+ return;
416
+ }
417
+ if (schema.type === "number" || schema.type === "integer") {
418
+ if (typeof value !== "number" || !Number.isFinite(value) || schema.type === "integer" && !Number.isInteger(value)) {
419
+ issues.push(`${path} must be ${schema.type === "integer" ? "an integer" : "a number"}`);
420
+ return;
421
+ }
422
+ if (schema.minimum !== void 0 && value < schema.minimum) issues.push(`${path} must be at least ${schema.minimum}`);
423
+ if (schema.maximum !== void 0 && value > schema.maximum) issues.push(`${path} must be at most ${schema.maximum}`);
424
+ return;
425
+ }
426
+ if (schema.type === "boolean" && typeof value !== "boolean") issues.push(`${path} must be a boolean`);
427
+ if (schema.type === "null" && value !== null) issues.push(`${path} must be null`);
428
+ }
429
+ function isJSONObject(value) {
430
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
431
+ const prototype = Object.getPrototypeOf(value);
432
+ if (prototype !== Object.prototype && prototype !== null) return false;
433
+ if (Object.getOwnPropertySymbols(value).length > 0) return false;
434
+ return Object.values(Object.getOwnPropertyDescriptors(value)).every((descriptor) => descriptor.enumerable && "value" in descriptor);
435
+ }
436
+ function jsonEqual(left, right) {
437
+ return compareJSON(left, right, new WeakSet(), new WeakSet());
438
+ }
439
+ function compareJSON(left, right, leftPath, rightPath) {
440
+ if (left === null || right === null) return left === right;
441
+ if (typeof left !== typeof right) return false;
442
+ if (typeof left === "string" || typeof left === "boolean") return left === right;
443
+ if (typeof left === "number") return Number.isFinite(left) && Number.isFinite(right) && left === right;
444
+ if (typeof left !== "object" || typeof right !== "object") return false;
445
+ if (leftPath.has(left) || rightPath.has(right)) return false;
446
+ const leftArray = Array.isArray(left);
447
+ if (leftArray !== Array.isArray(right)) return false;
448
+ if (leftArray) {
449
+ if (!isJSONArray(left) || !isJSONArray(right)) return false;
450
+ } else if (!isJSONObject(left) || !isJSONObject(right)) return false;
451
+ leftPath.add(left);
452
+ rightPath.add(right);
453
+ try {
454
+ if (leftArray) {
455
+ const rightArray = right;
456
+ if (left.length !== rightArray.length) return false;
457
+ for (let index = 0; index < left.length; index++) if (!compareJSON(left[index], rightArray[index], leftPath, rightPath)) return false;
458
+ return true;
459
+ }
460
+ const leftRecord = left;
461
+ const rightRecord = right;
462
+ const leftKeys = Object.keys(leftRecord);
463
+ const rightKeys = Object.keys(rightRecord);
464
+ if (leftKeys.length !== rightKeys.length) return false;
465
+ for (const key of leftKeys) {
466
+ if (!Object.hasOwn(rightRecord, key)) return false;
467
+ if (!compareJSON(leftRecord[key], rightRecord[key], leftPath, rightPath)) return false;
468
+ }
469
+ return true;
470
+ } finally {
471
+ leftPath.delete(left);
472
+ rightPath.delete(right);
473
+ }
474
+ }
475
+ function describeJSONValue(value) {
476
+ try {
477
+ const serialized = JSON.stringify(value);
478
+ return serialized === void 0 ? "a valid JSON value" : serialized;
479
+ } catch {
480
+ return "a valid JSON value";
481
+ }
482
+ }
483
+ function isJSONArray(value) {
484
+ if (Object.getOwnPropertySymbols(value).length > 0) return false;
485
+ const descriptors = Object.getOwnPropertyDescriptors(value);
486
+ for (const [key, descriptor] of Object.entries(descriptors)) {
487
+ if (key === "length") continue;
488
+ if (!isArrayIndex(key) || !descriptor.enumerable || !("value" in descriptor)) return false;
489
+ }
490
+ return Object.keys(value).length === value.length;
491
+ }
492
+ function isArrayIndex(key) {
493
+ const index = Number(key);
494
+ return Number.isInteger(index) && index >= 0 && index < 2 ** 32 - 1 && String(index) === key;
495
+ }
496
+
242
497
  //#endregion
243
498
  //#region src/card-registry.ts
244
499
  var CardRegistry = class {
@@ -252,6 +507,12 @@ var CardRegistry = class {
252
507
  getRender(type) {
253
508
  return this.cards.get(type)?.render;
254
509
  }
510
+ get(type) {
511
+ return this.cards.get(type);
512
+ }
513
+ list() {
514
+ return [...this.cards.values()];
515
+ }
255
516
  parse(type, rawJson) {
256
517
  const def = this.cards.get(type);
257
518
  const { data, complete } = parsePartialJSON(rawJson);
@@ -260,17 +521,21 @@ var CardRegistry = class {
260
521
  complete,
261
522
  valid: false
262
523
  };
263
- const valid = complete && this.validate(def, data);
524
+ const valid = complete && this.validateDefinition(def, data);
264
525
  return {
265
526
  data,
266
527
  complete,
267
528
  valid
268
529
  };
269
530
  }
270
- validate(def, data) {
531
+ validate(type, data) {
532
+ const def = this.cards.get(type);
533
+ return def ? this.validateDefinition(def, data) : false;
534
+ }
535
+ validateDefinition(def, data) {
271
536
  try {
272
537
  if (def.validate) return def.validate(data);
273
- if (def.schema) return validateSchema(def.schema, data);
538
+ if (def.schema) return validateJSONSchema(def.schema, data).valid;
274
539
  return true;
275
540
  } catch {
276
541
  return false;
@@ -301,42 +566,908 @@ var CardRegistry = class {
301
566
  };
302
567
  }
303
568
  };
304
- /** Minimal JSON Schema validation: covers type / required / properties, enough for cards. */
305
- function validateSchema(schema, data) {
306
- if (schema.type === "object") {
307
- if (typeof data !== "object" || data === null || Array.isArray(data)) return false;
308
- const obj = data;
309
- for (const req of schema.required ?? []) if (!(req in obj)) return false;
310
- for (const [key, sub] of Object.entries(schema.properties ?? {})) if (key in obj && !validateSchema(sub, obj[key])) return false;
569
+
570
+ //#endregion
571
+ //#region src/card-store.ts
572
+ const CARD_ID_MAX_LENGTH = 256;
573
+ const CARD_JSON_MAX_DEPTH = 32;
574
+ const CARD_JSON_MAX_NODES = 1e4;
575
+ const CARD_PATCH_BATCH_MAX_SIZE = 100;
576
+ var CardStore = class {
577
+ debugSource = "card-store";
578
+ registry;
579
+ cards = new Map();
580
+ lastMutationEpoch = new Map();
581
+ mutationEpoch = 0;
582
+ listeners = new Map();
583
+ allListeners = new Set();
584
+ debug;
585
+ constructor(options = {}) {
586
+ this.registry = options.registry;
587
+ this.debug = new DebugEmitter(this.debugSource, options);
588
+ }
589
+ register(input) {
590
+ validateCardId(input.id);
591
+ validateCardType(input.type);
592
+ assertRecordId(input.id, input.data);
593
+ const existing = this.cards.get(input.id);
594
+ if (existing) {
595
+ if (existing.type !== input.type) throw new CardTypeConflictError(input.id, existing.type, input.type);
596
+ return existing;
597
+ }
598
+ const data = cloneJSON(input.data);
599
+ this.assertValid(input.type, data);
600
+ const record = makeRecord(input.id, input.type, data, 0, { status: "idle" });
601
+ this.cards.set(input.id, record);
602
+ this.lastMutationEpoch.set(input.id, this.nextMutationEpoch());
603
+ this.notify(input.id);
604
+ return record;
605
+ }
606
+ get(id) {
607
+ return this.cards.get(id);
608
+ }
609
+ list() {
610
+ return [...this.cards.values()];
611
+ }
612
+ subscribe(id, listener) {
613
+ let listeners = this.listeners.get(id);
614
+ if (!listeners) {
615
+ listeners = new Set();
616
+ this.listeners.set(id, listeners);
617
+ }
618
+ listeners.add(listener);
619
+ return () => {
620
+ listeners?.delete(listener);
621
+ if (listeners?.size === 0) this.listeners.delete(id);
622
+ };
623
+ }
624
+ subscribeAll(listener) {
625
+ this.allListeners.add(listener);
626
+ return () => this.allListeners.delete(listener);
627
+ }
628
+ subscribeDebug(listener) {
629
+ return this.debug.subscribe(listener);
630
+ }
631
+ apply(patch) {
632
+ const next = this.preparePatch(this.cards, patch);
633
+ this.cards.set(next.id, next);
634
+ this.lastMutationEpoch.set(next.id, this.nextMutationEpoch());
635
+ this.notify(next.id);
636
+ if (this.debug.active) this.debug.emit("card-store-patch", {
637
+ patch,
638
+ cards: this.list()
639
+ });
640
+ return next;
641
+ }
642
+ applyAll(patches) {
643
+ if (patches.length > CARD_PATCH_BATCH_MAX_SIZE) throw new CardLimitError(`Card patch batches cannot exceed ${CARD_PATCH_BATCH_MAX_SIZE} operations`);
644
+ const nextCards = new Map(this.cards);
645
+ const changed = new Map();
646
+ for (const patch of patches) {
647
+ const next = this.preparePatch(nextCards, patch);
648
+ nextCards.set(next.id, next);
649
+ changed.set(next.id, next);
650
+ }
651
+ this.cards = nextCards;
652
+ for (const id of changed.keys()) this.lastMutationEpoch.set(id, this.nextMutationEpoch());
653
+ for (const id of changed.keys()) this.notifyOne(id);
654
+ if (changed.size) this.notifyAll();
655
+ if (changed.size && this.debug.active) {
656
+ const cards = this.list();
657
+ this.debug.emit("card-store-change", {
658
+ cardIds: [...changed.keys()],
659
+ cards
660
+ });
661
+ this.debug.emit("card-store-patch", {
662
+ patch: {
663
+ op: "batch",
664
+ patches
665
+ },
666
+ cards
667
+ });
668
+ }
669
+ return [...changed.values()];
670
+ }
671
+ delete(id) {
672
+ if (!this.cards.delete(id)) return false;
673
+ this.lastMutationEpoch.delete(id);
674
+ this.nextMutationEpoch();
675
+ this.notify(id);
311
676
  return true;
312
677
  }
313
- if (schema.type === "array") {
314
- if (!Array.isArray(data)) return false;
315
- return schema.items ? data.every((d) => validateSchema(schema.items, d)) : true;
678
+ clear() {
679
+ if (this.cards.size === 0) return;
680
+ const ids = [...this.cards.keys()];
681
+ this.cards.clear();
682
+ this.lastMutationEpoch.clear();
683
+ this.nextMutationEpoch();
684
+ for (const id of ids) this.notifyOne(id);
685
+ this.notifyAll();
686
+ if (this.debug.active) this.debug.emit("card-store-change", {
687
+ operation: "clear",
688
+ cardIds: ids,
689
+ cards: []
690
+ });
691
+ }
692
+ snapshot() {
693
+ return {
694
+ version: 1,
695
+ cards: this.list().map(({ id, type, data, revision }) => ({
696
+ id,
697
+ type,
698
+ data: cloneJSON(data),
699
+ revision
700
+ }))
701
+ };
702
+ }
703
+ restore(snapshot) {
704
+ if (!snapshot || snapshot.version !== 1 || !Array.isArray(snapshot.cards)) throw new CardSnapshotError("Unsupported card snapshot");
705
+ const restored = new Map();
706
+ for (const input of snapshot.cards) {
707
+ if (!input || typeof input !== "object") throw new CardSnapshotError("Invalid card snapshot record");
708
+ validateCardId(input.id);
709
+ validateCardType(input.type);
710
+ if (!Number.isSafeInteger(input.revision) || input.revision < 0) throw new CardSnapshotError(`Invalid revision for card "${input.id}"`);
711
+ if (restored.has(input.id)) throw new CardSnapshotError(`Duplicate card id "${input.id}" in snapshot`);
712
+ const data = cloneJSON(input.data);
713
+ try {
714
+ assertRecordId(input.id, data);
715
+ } catch (cause) {
716
+ throw new CardSnapshotError(`Card snapshot data id does not match record id "${input.id}"`, { cause });
717
+ }
718
+ this.assertValid(input.type, data);
719
+ restored.set(input.id, makeRecord(input.id, input.type, data, input.revision, { status: "idle" }));
720
+ }
721
+ const ids = new Set([...this.cards.keys(), ...restored.keys()]);
722
+ this.cards = restored;
723
+ this.lastMutationEpoch = new Map([...restored.keys()].map((id) => [id, this.nextMutationEpoch()]));
724
+ if (restored.size === 0 && ids.size > 0) this.nextMutationEpoch();
725
+ for (const id of ids) this.notifyOne(id);
726
+ if (ids.size) this.notifyAll();
727
+ if (ids.size && this.debug.active) this.debug.emit("card-store-change", {
728
+ operation: "restore",
729
+ cardIds: [...ids],
730
+ cards: this.list()
731
+ });
732
+ }
733
+ beginAction(id, actionId) {
734
+ return this.setAction(id, actionId, {
735
+ status: "loading",
736
+ actionId
737
+ }, true);
738
+ }
739
+ succeedAction(id, actionId) {
740
+ return this.setAction(id, actionId, {
741
+ status: "success",
742
+ actionId
743
+ });
744
+ }
745
+ failAction(id, actionId, error) {
746
+ return this.setAction(id, actionId, {
747
+ status: "error",
748
+ actionId,
749
+ error: normalizeCardActionError(error)
750
+ });
751
+ }
752
+ cancelAction(id, actionId) {
753
+ return this.setAction(id, actionId, { status: "idle" });
754
+ }
755
+ isActionCurrent(id, actionId) {
756
+ const action = this.cards.get(id)?.action;
757
+ return action?.status === "loading" && action.actionId === actionId;
758
+ }
759
+ revisions() {
760
+ return new Map([...this.cards].map(([id, card]) => [id, card.revision]));
761
+ }
762
+ captureMutationEpoch() {
763
+ return this.mutationEpoch;
764
+ }
765
+ applyActionResult(result, startedEpoch) {
766
+ const patches = result.op === "batch" ? result.patches : [result];
767
+ if (patches.length > CARD_PATCH_BATCH_MAX_SIZE) throw new CardLimitError(`Card patch batches cannot exceed ${CARD_PATCH_BATCH_MAX_SIZE} operations`);
768
+ for (const cardId of new Set(patches.map((patch) => patch.cardId))) {
769
+ const current = this.cards.get(cardId);
770
+ if (!current) throw new CardNotFoundError(cardId);
771
+ const lastMutationEpoch = this.lastMutationEpoch.get(cardId);
772
+ if (lastMutationEpoch === void 0 || lastMutationEpoch > startedEpoch) throw new CardRevisionConflictError(cardId, startedEpoch, lastMutationEpoch ?? this.mutationEpoch);
773
+ }
774
+ return result.op === "batch" ? this.applyAll(result.patches) : [this.apply(result)];
775
+ }
776
+ preparePatch(cards, patch) {
777
+ assertPatch(patch);
778
+ const current = cards.get(patch.cardId);
779
+ if (!current) throw new CardNotFoundError(patch.cardId);
780
+ if (patch.revision !== void 0 && patch.revision !== current.revision) throw new CardRevisionConflictError(patch.cardId, patch.revision, current.revision);
781
+ const patchData = cloneJSON(patch.data);
782
+ assertPatchId(current.id, patchData);
783
+ const data = patch.op === "replace" ? patchData : mergeJSON(current.data, patchData);
784
+ assertStableId(current.id, current.data, data);
785
+ this.assertValid(current.type, data);
786
+ return makeRecord(current.id, current.type, data, current.revision + 1, current.action);
787
+ }
788
+ assertValid(type, data) {
789
+ if (this.registry && !this.registry.validate(type, data)) throw new CardValidationError(type);
790
+ }
791
+ setAction(id, actionId, action, start = false) {
792
+ const current = this.cards.get(id);
793
+ if (!current) throw new CardNotFoundError(id);
794
+ if (!start && (current.action.status !== "loading" || current.action.actionId !== actionId)) return false;
795
+ this.cards.set(id, makeRecord(current.id, current.type, current.data, current.revision, action));
796
+ this.notify(id);
797
+ return true;
798
+ }
799
+ nextMutationEpoch() {
800
+ return ++this.mutationEpoch;
801
+ }
802
+ notify(id) {
803
+ this.notifyOne(id);
804
+ this.notifyAll();
805
+ if (this.debug.active) this.debug.emit("card-store-change", {
806
+ cardId: id,
807
+ cards: this.list()
808
+ });
809
+ }
810
+ notifyOne(id) {
811
+ const card = this.cards.get(id);
812
+ for (const listener of this.listeners.get(id) ?? []) safeCall(listener, card);
813
+ }
814
+ notifyAll() {
815
+ const cards = this.list();
816
+ for (const listener of this.allListeners) safeCall(listener, cards);
817
+ }
818
+ };
819
+ var CardStoreError = class extends Error {
820
+ constructor(message, options) {
821
+ super(message, options);
822
+ this.name = new.target.name;
823
+ }
824
+ };
825
+ var CardNotFoundError = class extends CardStoreError {
826
+ constructor(id) {
827
+ super(`Card "${id}" was not found`);
828
+ }
829
+ };
830
+ var CardTypeConflictError = class extends CardStoreError {
831
+ constructor(id, currentType, nextType) {
832
+ super(`Card "${id}" is already registered as "${currentType}", not "${nextType}"`);
833
+ }
834
+ };
835
+ var CardValidationError = class extends CardStoreError {
836
+ constructor(type) {
837
+ super(`Card data is invalid for type "${type}"`);
838
+ }
839
+ };
840
+ var CardRevisionConflictError = class extends CardStoreError {
841
+ constructor(id, expected, actual) {
842
+ super(`Card "${id}" revision conflict: expected ${expected}, actual ${actual}`);
843
+ }
844
+ };
845
+ var CardJSONError = class extends CardStoreError {};
846
+ var CardLimitError = class extends CardStoreError {};
847
+ var CardSnapshotError = class extends CardStoreError {};
848
+ function isCardPatchResult(value) {
849
+ if (!isPlainObject(value) || typeof value.op !== "string") return false;
850
+ if (value.op === "batch") return Array.isArray(value.patches) && value.patches.every(isCardPatch);
851
+ return isCardPatch(value);
852
+ }
853
+ function isCardPatch(value) {
854
+ return isPlainObject(value) && (value.op === "merge" || value.op === "replace") && typeof value.cardId === "string" && Object.hasOwn(value, "data") && (value.revision === void 0 || Number.isSafeInteger(value.revision));
855
+ }
856
+ function assertPatch(patch) {
857
+ if (!isCardPatch(patch)) throw new CardStoreError("Invalid card patch");
858
+ validateCardId(patch.cardId);
859
+ if (patch.revision !== void 0 && patch.revision < 0) throw new CardStoreError("Card patch revision must be non-negative");
860
+ }
861
+ function validateCardId(id) {
862
+ if (typeof id !== "string" || id.trim().length === 0 || id.length > CARD_ID_MAX_LENGTH) throw new CardStoreError(`Card id must be a non-empty string up to ${CARD_ID_MAX_LENGTH} characters`);
863
+ }
864
+ function validateCardType(type) {
865
+ if (typeof type !== "string" || type.trim().length === 0) throw new CardStoreError("Card type must be a non-empty string");
866
+ }
867
+ function cloneJSON(value) {
868
+ let nodes = 0;
869
+ const path = new WeakSet();
870
+ const clone = (input, depth) => {
871
+ nodes++;
872
+ if (nodes > CARD_JSON_MAX_NODES) throw new CardLimitError(`Card JSON cannot exceed ${CARD_JSON_MAX_NODES} nodes`);
873
+ if (depth > CARD_JSON_MAX_DEPTH) throw new CardLimitError(`Card JSON cannot exceed depth ${CARD_JSON_MAX_DEPTH}`);
874
+ if (input === null || typeof input === "string" || typeof input === "boolean") return input;
875
+ if (typeof input === "number" && Number.isFinite(input)) return input;
876
+ if (typeof input !== "object") throw new CardJSONError("Card data must contain only JSON values");
877
+ if (path.has(input)) throw new CardJSONError("Card data cannot contain cycles");
878
+ path.add(input);
879
+ try {
880
+ if (Array.isArray(input)) {
881
+ if (Object.keys(input).length !== input.length) throw new CardJSONError("Card arrays must not be sparse");
882
+ return Object.freeze(input.map((item) => clone(item, depth + 1)));
883
+ }
884
+ if (!isPlainObject(input)) throw new CardJSONError("Card objects must be plain JSON objects");
885
+ const output = {};
886
+ for (const key of Object.keys(input)) {
887
+ if (isDangerousKey(key)) throw new CardJSONError(`Card data contains dangerous key "${key}"`);
888
+ Object.defineProperty(output, key, {
889
+ value: clone(input[key], depth + 1),
890
+ enumerable: true,
891
+ writable: false,
892
+ configurable: false
893
+ });
894
+ }
895
+ return Object.freeze(output);
896
+ } finally {
897
+ path.delete(input);
898
+ }
899
+ };
900
+ return clone(value, 0);
901
+ }
902
+ function mergeJSON(current, patch) {
903
+ if (!isPlainObject(current) || !isPlainObject(patch)) return patch;
904
+ const output = {};
905
+ for (const key of Object.keys(current)) output[key] = current[key];
906
+ for (const key of Object.keys(patch)) output[key] = mergeJSON(current[key], patch[key]);
907
+ return cloneJSON(output);
908
+ }
909
+ function assertPatchId(cardId, data) {
910
+ if (isPlainObject(data) && Object.hasOwn(data, "id") && data.id !== cardId) throw new CardStoreError(`Card patch cannot change id from "${cardId}"`);
911
+ }
912
+ function assertRecordId(cardId, data) {
913
+ if (isPlainObject(data) && Object.hasOwn(data, "id") && data.id !== cardId) throw new CardStoreError(`Card data id must match record id "${cardId}"`);
914
+ }
915
+ function assertStableId(cardId, current, next) {
916
+ const currentHasId = isPlainObject(current) && Object.hasOwn(current, "id");
917
+ const nextHasId = isPlainObject(next) && Object.hasOwn(next, "id");
918
+ if (currentHasId !== nextHasId || nextHasId && next.id !== cardId) throw new CardStoreError(`Card patch cannot change id from "${cardId}"`);
919
+ }
920
+ function makeRecord(id, type, data, revision, action) {
921
+ return Object.freeze({
922
+ id,
923
+ type,
924
+ data,
925
+ revision,
926
+ action: Object.freeze(action)
927
+ });
928
+ }
929
+ function normalizeCardActionError(error) {
930
+ if (error instanceof Error) return Object.freeze({
931
+ name: error.name || "Error",
932
+ message: error.message
933
+ });
934
+ return Object.freeze({
935
+ name: "Error",
936
+ message: typeof error === "string" ? error : "Unknown error"
937
+ });
938
+ }
939
+ function isPlainObject(value) {
940
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
941
+ const prototype = Object.getPrototypeOf(value);
942
+ if (prototype !== Object.prototype && prototype !== null) return false;
943
+ if (Object.getOwnPropertySymbols(value).length) return false;
944
+ return Object.values(Object.getOwnPropertyDescriptors(value)).every((descriptor) => descriptor.enumerable && "value" in descriptor);
945
+ }
946
+ function isDangerousKey(key) {
947
+ return key === "__proto__" || key === "prototype" || key === "constructor";
948
+ }
949
+ function safeCall(listener, ...args) {
950
+ try {
951
+ listener(...args);
952
+ } catch {}
953
+ }
954
+
955
+ //#endregion
956
+ //#region src/actions.ts
957
+ let nextRuntimeId = 0;
958
+ var ActionRegistry = class {
959
+ actions = new Map();
960
+ register(definition, options = {}) {
961
+ if (typeof definition?.type !== "string" || definition.type.trim() === "") throw new TypeError("Action type must be a non-empty string");
962
+ if (typeof definition.run !== "function") throw new TypeError(`Action "${definition.type}" run must be a function`);
963
+ if (this.actions.has(definition.type) && !options.override) throw new ActionAlreadyRegisteredError(definition.type);
964
+ this.actions.set(definition.type, definition);
965
+ }
966
+ has(type) {
967
+ return this.actions.has(type);
968
+ }
969
+ get(type) {
970
+ return this.actions.get(type);
971
+ }
972
+ list() {
973
+ return [...this.actions.values()];
974
+ }
975
+ };
976
+ var ActionRuntime = class {
977
+ debugSource = "action-runtime";
978
+ registry;
979
+ cardStore;
980
+ defaultTimeoutMs;
981
+ onActionStart;
982
+ onActionSuccess;
983
+ onActionError;
984
+ states = new Map();
985
+ pending = new Map();
986
+ listeners = new Set();
987
+ defaultOwner = {};
988
+ runtimeId = ++nextRuntimeId;
989
+ generation = 0;
990
+ nextActionId = 0;
991
+ destroyed = false;
992
+ debug;
993
+ constructor(options) {
994
+ this.debug = new DebugEmitter(this.debugSource, options);
995
+ this.registry = options.registry;
996
+ this.cardStore = options.cardStore;
997
+ this.defaultTimeoutMs = options.timeoutMs;
998
+ this.onActionStart = options.onActionStart;
999
+ this.onActionSuccess = options.onActionSuccess;
1000
+ this.onActionError = options.onActionError;
1001
+ }
1002
+ dispatch(request, options = {}) {
1003
+ if (this.destroyed) return Promise.reject(new ActionDestroyedError());
1004
+ const key = getActionKey(request.type, request.cardType, request.cardId);
1005
+ const owner = options.owner ?? this.defaultOwner;
1006
+ const existing = this.pending.get(key)?.get(owner);
1007
+ if (existing) return existing.promise;
1008
+ const actionId = `r${this.runtimeId}:${request.type}:${++this.nextActionId}`;
1009
+ const event = {
1010
+ key,
1011
+ type: request.type,
1012
+ params: request.params,
1013
+ actionId,
1014
+ cardType: request.cardType,
1015
+ cardId: request.cardId
1016
+ };
1017
+ if (this.debug.active) this.debug.emit("action-dispatched", { ...event });
1018
+ const definition = this.registry.get(request.type);
1019
+ if (!definition) return this.rejectPreflight(event, new ActionNotFoundError(request.type));
1020
+ if (definition.schema !== void 0) try {
1021
+ const validation = validateJSONSchema(definition.schema, request.params);
1022
+ if (!validation.valid) return this.rejectPreflight(event, new ActionValidationError(request.type, validation.issues));
1023
+ } catch (cause) {
1024
+ return this.rejectPreflight(event, new ActionExecutionError(request.type, cause));
1025
+ }
1026
+ let startedMutationEpoch;
1027
+ try {
1028
+ startedMutationEpoch = this.cardStore?.captureMutationEpoch();
1029
+ } catch (cause) {
1030
+ return this.rejectPreflight(event, new ActionExecutionError(request.type, cause));
1031
+ }
1032
+ if (request.cardId && this.cardStore) try {
1033
+ this.cardStore.beginAction(request.cardId, actionId);
1034
+ } catch (cause) {
1035
+ return this.rejectPreflight(event, new ActionExecutionError(request.type, cause));
1036
+ }
1037
+ const generation = this.generation;
1038
+ const controller = new AbortController();
1039
+ const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
1040
+ let timer;
1041
+ let timedOut = false;
1042
+ let settled = false;
1043
+ let resolvePromise;
1044
+ let rejectPromise;
1045
+ const promise = new Promise((resolve, reject) => {
1046
+ resolvePromise = resolve;
1047
+ rejectPromise = reject;
1048
+ });
1049
+ const state = {
1050
+ status: "pending",
1051
+ key,
1052
+ type: request.type,
1053
+ cardType: request.cardType,
1054
+ cardId: request.cardId,
1055
+ actionId
1056
+ };
1057
+ const pending = {
1058
+ controller,
1059
+ promise
1060
+ };
1061
+ const runtime = this;
1062
+ let cleanupExternalSignal = () => {};
1063
+ let cleanupAbortSignal = () => {};
1064
+ let owners = this.pending.get(key);
1065
+ if (!owners) {
1066
+ owners = new Map();
1067
+ this.pending.set(key, owners);
1068
+ }
1069
+ owners.set(owner, pending);
1070
+ const cleanup = () => {
1071
+ if (timer !== void 0) clearTimeout(timer);
1072
+ cleanupExternalSignal();
1073
+ cleanupAbortSignal();
1074
+ const currentOwners = this.pending.get(key);
1075
+ if (currentOwners?.get(owner) === pending) {
1076
+ currentOwners.delete(owner);
1077
+ if (currentOwners.size === 0) this.pending.delete(key);
1078
+ }
1079
+ };
1080
+ const finishSuccess = (result) => {
1081
+ if (settled) return;
1082
+ const wasPublic = this.isPublic(key, actionId, generation);
1083
+ const canAffectCard = !request.cardId || !this.cardStore || this.cardStore.isActionCurrent(request.cardId, actionId);
1084
+ const canApplyResult = request.cardId ? canAffectCard : wasPublic;
1085
+ if (canApplyResult && this.cardStore && startedMutationEpoch !== void 0 && isCardPatchResult(result)) try {
1086
+ this.cardStore.applyActionResult(result, startedMutationEpoch);
1087
+ } catch (error) {
1088
+ finishError(error);
1089
+ return;
1090
+ }
1091
+ settled = true;
1092
+ cleanup();
1093
+ if (wasPublic) {
1094
+ this.commit({
1095
+ status: "success",
1096
+ key,
1097
+ type: request.type,
1098
+ cardType: request.cardType,
1099
+ cardId: request.cardId,
1100
+ actionId,
1101
+ result
1102
+ });
1103
+ callEventHandler(this.onActionSuccess, {
1104
+ ...event,
1105
+ result
1106
+ });
1107
+ if (this.debug.active) this.debug.emit("action-success", {
1108
+ ...event,
1109
+ result
1110
+ });
1111
+ }
1112
+ if (request.cardId && this.cardStore && canAffectCard) settleCardAction(() => this.cardStore?.succeedAction(request.cardId, actionId));
1113
+ resolvePromise(result);
1114
+ };
1115
+ function finishError(cause) {
1116
+ if (settled) return;
1117
+ settled = true;
1118
+ const wasPublic = runtime.isPublic(key, actionId, generation);
1119
+ cleanup();
1120
+ const error = normalizeError(request.type, cause, controller.signal.aborted, timedOut, timeoutMs);
1121
+ if (request.cardId && runtime.cardStore) settleCardAction(() => error instanceof ActionAbortedError ? runtime.cardStore?.cancelAction(request.cardId, actionId) : runtime.cardStore?.failAction(request.cardId, actionId, error));
1122
+ if (wasPublic) {
1123
+ const state$1 = error instanceof ActionAbortedError ? {
1124
+ status: "cancelled",
1125
+ key,
1126
+ type: request.type,
1127
+ cardType: request.cardType,
1128
+ actionId,
1129
+ error
1130
+ } : {
1131
+ status: "error",
1132
+ key,
1133
+ type: request.type,
1134
+ cardType: request.cardType,
1135
+ cardId: request.cardId,
1136
+ actionId,
1137
+ error
1138
+ };
1139
+ if (request.cardId) state$1.cardId = request.cardId;
1140
+ runtime.commit(state$1);
1141
+ callEventHandler(runtime.onActionError, {
1142
+ ...event,
1143
+ error
1144
+ });
1145
+ if (runtime.debug.active) runtime.debug.emit("action-error", {
1146
+ ...event,
1147
+ error
1148
+ });
1149
+ }
1150
+ rejectPromise(error);
1151
+ }
1152
+ this.commit(state);
1153
+ callEventHandler(this.onActionStart, event);
1154
+ cleanupExternalSignal = forwardAbort(options.signal, controller);
1155
+ cleanupAbortSignal = listenForAbort(controller.signal, (cause) => finishError(cause));
1156
+ if (timeoutMs !== void 0 && timeoutMs > 0 && !settled) timer = setTimeout(() => {
1157
+ timedOut = true;
1158
+ controller.abort(new ActionTimeoutError(request.type, timeoutMs));
1159
+ }, timeoutMs);
1160
+ if (controller.signal.aborted || this.generation !== generation || this.destroyed) {
1161
+ finishError(controller.signal.reason ?? new ActionAbortedError(request.type));
1162
+ return promise;
1163
+ }
1164
+ try {
1165
+ const result = definition.run(request.params, {
1166
+ signal: controller.signal,
1167
+ actionId,
1168
+ cardType: request.cardType,
1169
+ cardId: request.cardId
1170
+ });
1171
+ Promise.resolve(result).then(finishSuccess, finishError);
1172
+ } catch (error) {
1173
+ finishError(error);
1174
+ }
1175
+ return promise;
1176
+ }
1177
+ getState(key) {
1178
+ return this.states.get(key) ?? getIdleActionState(key);
1179
+ }
1180
+ /** Check the runtime allowlist without exposing executable action definitions. */
1181
+ hasAction(type) {
1182
+ return !this.destroyed && this.registry.has(type);
1183
+ }
1184
+ /** List registered action names without exposing executable definitions. */
1185
+ listActionTypes() {
1186
+ return this.destroyed ? [] : Object.freeze(this.registry.list().map((action) => action.type));
1187
+ }
1188
+ subscribe(listener) {
1189
+ if (this.destroyed) return () => {};
1190
+ this.listeners.add(listener);
1191
+ return () => this.listeners.delete(listener);
1192
+ }
1193
+ subscribeDebug(listener) {
1194
+ return this.debug.subscribe(listener);
1195
+ }
1196
+ cancel(key) {
1197
+ const actions = this.pending.get(key);
1198
+ if (!actions?.size) return false;
1199
+ const type = this.states.get(key)?.type ?? getIdleActionState(key).type;
1200
+ for (const action of [...actions.values()]) action.controller.abort(new ActionAbortedError(type));
1201
+ return true;
1202
+ }
1203
+ reset() {
1204
+ const oldStates = [...this.states.values()];
1205
+ this.generation++;
1206
+ const actions = [...this.pending.values()].flatMap((owners) => [...owners.values()]);
1207
+ for (const action of actions) action.controller.abort(new ActionAbortedError("Action"));
1208
+ this.pending.clear();
1209
+ this.states.clear();
1210
+ for (const state of oldStates) this.notify(idleStateFrom(state));
1211
+ }
1212
+ destroy() {
1213
+ if (this.destroyed) return;
1214
+ this.reset();
1215
+ this.destroyed = true;
1216
+ this.listeners.clear();
1217
+ }
1218
+ rejectPreflight(event, error) {
1219
+ const errorState = {
1220
+ status: "error",
1221
+ key: event.key,
1222
+ type: event.type,
1223
+ cardType: event.cardType,
1224
+ cardId: event.cardId,
1225
+ actionId: event.actionId,
1226
+ error
1227
+ };
1228
+ this.commit(errorState);
1229
+ callEventHandler(this.onActionError, {
1230
+ ...event,
1231
+ error
1232
+ });
1233
+ if (this.debug.active) this.debug.emit("action-error", {
1234
+ ...event,
1235
+ error
1236
+ });
1237
+ return Promise.reject(error);
1238
+ }
1239
+ isPublic(key, actionId, generation) {
1240
+ return !this.destroyed && this.generation === generation && this.states.get(key)?.actionId === actionId;
1241
+ }
1242
+ commit(state) {
1243
+ this.states.set(state.key, state);
1244
+ if (this.debug.active) this.debug.emit("action-state", { ...state });
1245
+ this.notify(state);
1246
+ }
1247
+ notify(state) {
1248
+ for (const listener of this.listeners) try {
1249
+ listener(state);
1250
+ } catch {}
1251
+ }
1252
+ };
1253
+ function createActionRuntime(options) {
1254
+ return new ActionRuntime(options);
1255
+ }
1256
+ var ActionRuntimeError = class extends Error {
1257
+ constructor(message, options) {
1258
+ super(message, options);
1259
+ this.name = new.target.name;
1260
+ }
1261
+ };
1262
+ var ActionAlreadyRegisteredError = class extends ActionRuntimeError {
1263
+ constructor(type) {
1264
+ super(`Action "${type}" is already registered`);
1265
+ }
1266
+ };
1267
+ var ActionNotFoundError = class extends ActionRuntimeError {
1268
+ constructor(type) {
1269
+ super(`Action "${type}" is not registered`);
1270
+ }
1271
+ };
1272
+ var ActionValidationError = class extends ActionRuntimeError {
1273
+ constructor(actionType, issues) {
1274
+ super(`Action "${actionType}" parameters are invalid: ${issues.join(", ")}`);
1275
+ this.actionType = actionType;
1276
+ this.issues = issues;
1277
+ }
1278
+ };
1279
+ var ActionExecutionError = class extends ActionRuntimeError {
1280
+ constructor(type, cause) {
1281
+ super(`Action "${type}" failed`, { cause });
1282
+ }
1283
+ };
1284
+ var ActionAbortedError = class extends ActionRuntimeError {
1285
+ constructor(type) {
1286
+ super(`Action "${type}" was cancelled`);
1287
+ }
1288
+ };
1289
+ var ActionTimeoutError = class extends ActionRuntimeError {
1290
+ constructor(type, timeoutMs) {
1291
+ super(`Action "${type}" timed out after ${timeoutMs}ms`);
1292
+ this.timeoutMs = timeoutMs;
1293
+ }
1294
+ };
1295
+ var ActionDestroyedError = class extends ActionRuntimeError {
1296
+ constructor() {
1297
+ super("Action runtime has been destroyed");
1298
+ }
1299
+ };
1300
+ function getActionKey(type, cardType, cardId) {
1301
+ if (cardId !== void 0) return `::${JSON.stringify([
1302
+ cardType ?? null,
1303
+ type,
1304
+ cardId
1305
+ ])}`;
1306
+ if (!type.includes(":") && !cardType?.includes(":")) return cardType ? `${cardType}:${type}` : type;
1307
+ return `::${JSON.stringify([cardType ?? null, type])}`;
1308
+ }
1309
+ function getIdleActionState(key) {
1310
+ if (key.startsWith("::")) try {
1311
+ const [cardType, type, cardId] = JSON.parse(key.slice(2));
1312
+ if (cardId !== void 0) return cardType === null ? {
1313
+ status: "idle",
1314
+ key,
1315
+ type,
1316
+ cardId
1317
+ } : {
1318
+ status: "idle",
1319
+ key,
1320
+ cardType,
1321
+ type,
1322
+ cardId
1323
+ };
1324
+ return cardType === null ? {
1325
+ status: "idle",
1326
+ key,
1327
+ type
1328
+ } : {
1329
+ status: "idle",
1330
+ key,
1331
+ cardType,
1332
+ type
1333
+ };
1334
+ } catch {
1335
+ return {
1336
+ status: "idle",
1337
+ key,
1338
+ type: key
1339
+ };
316
1340
  }
317
- if (schema.type === "string") return typeof data === "string";
318
- if (schema.type === "number") return typeof data === "number";
319
- if (schema.type === "boolean") return typeof data === "boolean";
320
- return true;
1341
+ const separator = key.indexOf(":");
1342
+ return separator === -1 ? {
1343
+ status: "idle",
1344
+ key,
1345
+ type: key
1346
+ } : {
1347
+ status: "idle",
1348
+ key,
1349
+ cardType: key.slice(0, separator),
1350
+ type: key.slice(separator + 1)
1351
+ };
1352
+ }
1353
+ function idleStateFrom(state) {
1354
+ return {
1355
+ status: "idle",
1356
+ key: state.key,
1357
+ type: state.type,
1358
+ ...state.cardType === void 0 ? {} : { cardType: state.cardType },
1359
+ ...state.cardId === void 0 ? {} : { cardId: state.cardId }
1360
+ };
1361
+ }
1362
+ function listenForAbort(signal, onAbort) {
1363
+ const abort = () => onAbort(signal.reason);
1364
+ if (signal.aborted) abort();
1365
+ else signal.addEventListener("abort", abort, { once: true });
1366
+ return () => signal.removeEventListener("abort", abort);
1367
+ }
1368
+ function forwardAbort(signal, controller) {
1369
+ if (!signal) return () => {};
1370
+ const abort = () => controller.abort(signal.reason);
1371
+ if (signal.aborted) abort();
1372
+ else signal.addEventListener("abort", abort, { once: true });
1373
+ return () => signal.removeEventListener("abort", abort);
1374
+ }
1375
+ function normalizeError(type, cause, aborted, timedOut, timeoutMs) {
1376
+ if (cause instanceof ActionRuntimeError) return cause;
1377
+ if (timedOut) return new ActionTimeoutError(type, timeoutMs ?? 0);
1378
+ if (aborted || isAbortError(cause)) return new ActionAbortedError(type);
1379
+ return new ActionExecutionError(type, cause);
1380
+ }
1381
+ function isAbortError(error) {
1382
+ return error instanceof DOMException && error.name === "AbortError";
1383
+ }
1384
+ function callEventHandler(handler, event) {
1385
+ try {
1386
+ handler?.(event);
1387
+ } catch {}
1388
+ }
1389
+ function settleCardAction(settle) {
1390
+ try {
1391
+ settle();
1392
+ } catch {}
321
1393
  }
322
1394
 
323
1395
  //#endregion
324
1396
  //#region src/plugins.ts
325
1397
  /** Merge every plugin's `nodeRenderers` into a single map (later plugins win). */
326
- function collectNodeRenderers(plugins = []) {
1398
+ function collectNodeRenderers(plugins = [], debugOptions = {}) {
327
1399
  const map = {};
328
- for (const p of plugins) for (const [k, render] of Object.entries(p.nodeRenderers ?? {})) map[k] = (node) => node.complete === false ? {
329
- kind: "html",
330
- html: `<div data-aigui-block-loading="" data-block-type="${escapeAttr(node.type)}"></div>`
331
- } : render(node);
1400
+ const target = debugOptions.debugTarget;
1401
+ const debug = target ? void 0 : debugOptions.debug === true ? new DebugEmitter("renderer", debugOptions) : void 0;
1402
+ for (const p of plugins) for (const [k, render] of Object.entries(p.nodeRenderers ?? {})) {
1403
+ if (!(target?.debugEnabled || debugOptions.debug === true)) {
1404
+ map[k] = render;
1405
+ continue;
1406
+ }
1407
+ map[k] = (node) => {
1408
+ const emit = (type, data) => target?.emitDebug(type, data) ?? debug?.emit(type, data);
1409
+ emit("plugin-render-started", {
1410
+ plugin: p.name,
1411
+ nodeType: node.type,
1412
+ nodeKey: node.key
1413
+ });
1414
+ try {
1415
+ const output = render(node);
1416
+ if (isPromise(output)) return output.then((resolved) => {
1417
+ emit("async-output-resolved", {
1418
+ plugin: p.name,
1419
+ nodeType: node.type,
1420
+ nodeKey: node.key,
1421
+ outputKind: resolved.kind
1422
+ });
1423
+ emit("plugin-render-completed", {
1424
+ plugin: p.name,
1425
+ nodeType: node.type,
1426
+ nodeKey: node.key,
1427
+ outputKind: resolved.kind
1428
+ });
1429
+ return resolved;
1430
+ }, (error) => {
1431
+ emit("async-output-rejected", {
1432
+ plugin: p.name,
1433
+ nodeType: node.type,
1434
+ nodeKey: node.key,
1435
+ error
1436
+ });
1437
+ emit("plugin-render-failed", {
1438
+ plugin: p.name,
1439
+ nodeType: node.type,
1440
+ nodeKey: node.key,
1441
+ error
1442
+ });
1443
+ throw error;
1444
+ });
1445
+ emit("plugin-render-completed", {
1446
+ plugin: p.name,
1447
+ nodeType: node.type,
1448
+ nodeKey: node.key,
1449
+ outputKind: output.kind
1450
+ });
1451
+ return output;
1452
+ } catch (error) {
1453
+ emit("plugin-render-failed", {
1454
+ plugin: p.name,
1455
+ nodeType: node.type,
1456
+ nodeKey: node.key,
1457
+ error
1458
+ });
1459
+ throw error;
1460
+ }
1461
+ };
1462
+ }
332
1463
  return map;
333
1464
  }
334
1465
  /** The set of node types claimed by the given plugins. */
335
1466
  function pluginNodeTypes(plugins = []) {
336
1467
  return new Set(Object.keys(collectNodeRenderers(plugins)));
337
1468
  }
338
- function escapeAttr(value) {
339
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
1469
+ function isPromise(value) {
1470
+ return typeof value?.then === "function";
340
1471
  }
341
1472
 
342
1473
  //#endregion
@@ -390,13 +1521,15 @@ function createParserWithMetadata(options = {}) {
390
1521
  const cardType = info.slice(5);
391
1522
  const res = options.registry.parse(cardType, t.content);
392
1523
  const complete = res.complete && isClosedFence(rawSrc, t.map, t.markup);
1524
+ const idResult = complete && res.valid ? getCardId(res.data) : void 0;
393
1525
  addNode({
394
1526
  type: "card",
395
1527
  card: {
396
1528
  type: cardType,
397
1529
  data: res.data,
398
1530
  complete,
399
- valid: complete && res.valid
1531
+ valid: complete && res.valid && idResult !== false,
1532
+ ...typeof idResult === "string" ? { id: idResult } : {}
400
1533
  }
401
1534
  }, t.map);
402
1535
  } else if (pluginTypes.has(info)) {
@@ -502,6 +1635,11 @@ function createParserWithMetadata(options = {}) {
502
1635
  };
503
1636
  };
504
1637
  }
1638
+ function getCardId(data) {
1639
+ if (typeof data !== "object" || data === null || Array.isArray(data) || !Object.hasOwn(data, "id")) return void 0;
1640
+ const id = data.id;
1641
+ return typeof id === "string" && id.trim().length > 0 && id.length <= 256 ? id : false;
1642
+ }
505
1643
  function getLineOffsets(src) {
506
1644
  const offsets = [0];
507
1645
  for (let i = 0; i < src.length; i++) if (src[i] === "\r") {
@@ -612,6 +1750,7 @@ function nodeEqual(a, b) {
612
1750
  * the resulting patches via `onPatch`.
613
1751
  */
614
1752
  var Renderer = class {
1753
+ debugSource = "renderer";
615
1754
  buffer = "";
616
1755
  prevAst = [];
617
1756
  parse;
@@ -622,8 +1761,10 @@ var Renderer = class {
622
1761
  activeFeed;
623
1762
  renderScheduled = false;
624
1763
  scheduleGeneration = 0;
1764
+ debug;
625
1765
  constructor(options = {}) {
626
1766
  this.options = options;
1767
+ this.debug = new DebugEmitter(this.debugSource, options);
627
1768
  this.sanitize = options.sanitize === false ? false : typeof options.sanitize === "object" ? options.sanitize : {};
628
1769
  if (options.registry) for (const plugin of options.plugins ?? []) for (const card of plugin.cards ?? []) options.registry.register(card);
629
1770
  this.parse = createParserWithMetadata({
@@ -631,21 +1772,36 @@ var Renderer = class {
631
1772
  plugins: options.plugins
632
1773
  });
633
1774
  }
1775
+ get debugEnabled() {
1776
+ return this.debug.available;
1777
+ }
1778
+ emitDebug(type, data = {}) {
1779
+ this.debug.emit(type, data);
1780
+ }
634
1781
  push(chunk) {
635
1782
  this.buffer += chunk;
1783
+ if (this.debug.active) this.debug.emit("chunk-received", {
1784
+ chunk,
1785
+ length: chunk.length,
1786
+ bufferLength: this.buffer.length
1787
+ });
636
1788
  this.scheduleRender();
637
1789
  }
1790
+ subscribeDebug(listener) {
1791
+ return this.debug.subscribe(listener);
1792
+ }
638
1793
  async feed(source, options = {}) {
639
1794
  const generation = ++this.generation;
640
1795
  this.cancelActiveFeed(createAbortError("Superseded by a newer feed"));
1796
+ if (this.debug.active) this.debug.emit("feed-started", { generation });
641
1797
  const decoder = new TextDecoder();
642
1798
  const signal = options.signal;
643
- if (signal?.aborted) throw abortReason(signal);
1799
+ if (signal?.aborted) throw abortReason$1(signal);
644
1800
  let aborted = false;
645
1801
  let abortError;
646
1802
  const abort = () => {
647
1803
  aborted = true;
648
- abortError = abortReason(signal);
1804
+ abortError = abortReason$1(signal);
649
1805
  if (this.activeFeed?.generation === generation) this.cancelActiveFeed(abortError);
650
1806
  };
651
1807
  signal?.addEventListener("abort", abort, { once: true });
@@ -655,7 +1811,7 @@ var Renderer = class {
655
1811
  if (text) this.push(text);
656
1812
  };
657
1813
  try {
658
- if (isReadableStream$1(source)) {
1814
+ if (isReadableStream$2(source)) {
659
1815
  const reader = source.getReader();
660
1816
  let cancelled = false;
661
1817
  this.activeFeed = {
@@ -704,8 +1860,22 @@ var Renderer = class {
704
1860
  if (tail) this.push(tail);
705
1861
  this.flush();
706
1862
  }
707
- if (aborted) throw abortError;
1863
+ if (aborted) {
1864
+ if (this.debug.active) this.debug.emit("feed-cancelled", {
1865
+ generation,
1866
+ error: abortError
1867
+ });
1868
+ throw abortError;
1869
+ }
1870
+ if (generation === this.generation && this.debug.active) this.debug.emit("feed-completed", {
1871
+ generation,
1872
+ bufferLength: this.buffer.length
1873
+ });
708
1874
  } finally {
1875
+ if (!aborted && generation !== this.generation && this.debug.active) this.debug.emit("feed-cancelled", {
1876
+ generation,
1877
+ reason: "superseded-or-reset"
1878
+ });
709
1879
  signal?.removeEventListener("abort", abort);
710
1880
  if (this.activeFeed?.generation === generation) this.activeFeed = void 0;
711
1881
  }
@@ -721,6 +1891,7 @@ var Renderer = class {
721
1891
  this.prevAst = [];
722
1892
  this.parsed = void 0;
723
1893
  this.options.onPatch?.(patches, []);
1894
+ if (this.debug.active) this.debug.emit("renderer-reset", { patches });
724
1895
  }
725
1896
  /** Immediately render pending buffered input, bypassing the scheduler. */
726
1897
  flush() {
@@ -749,16 +1920,34 @@ var Renderer = class {
749
1920
  Promise.resolve(active.cancel(reason)).catch(() => {});
750
1921
  }
751
1922
  render() {
1923
+ const debug = this.debug.active;
1924
+ const renderStarted = debug ? now() : 0;
752
1925
  const previous = this.parsed;
753
1926
  let next;
1927
+ let mode = "full";
754
1928
  if (previous?.incrementalSafe && previous.blocks.length > 1) {
755
1929
  const mutable = previous.blocks.at(-1);
756
1930
  const stableBlocks = previous.blocks.slice(0, -1);
757
1931
  const stableNodeEnd = mutable.nodeStart;
758
1932
  const rawTail = this.buffer.slice(mutable.start);
759
- const tail = this.parse(repairMarkdown(rawTail), rawTail, mutable.start);
1933
+ const repaired = repairMarkdown(rawTail);
1934
+ if (debug) this.debug.emit("markdown-repaired", {
1935
+ mode: "mutable-tail",
1936
+ raw: rawTail,
1937
+ rawLength: rawTail.length,
1938
+ repaired,
1939
+ repairedLength: repaired.length,
1940
+ sourceOffset: mutable.start
1941
+ });
1942
+ const parseStarted = debug ? now() : 0;
1943
+ const tail = this.parse(repaired, rawTail, mutable.start);
1944
+ if (debug) this.debug.emit("mutable-tail-reparsed", {
1945
+ sourceOffset: mutable.start,
1946
+ durationMs: now() - parseStarted
1947
+ });
760
1948
  if (tail.incrementalSafe) {
761
- if (this.sanitize !== false) sanitizeNodes(tail.nodes, this.sanitize);
1949
+ mode = "mutable-tail";
1950
+ if (this.sanitize !== false) this.sanitizeNodesWithDebug(tail.nodes);
762
1951
  next = {
763
1952
  nodes: [...previous.nodes.slice(0, stableNodeEnd), ...tail.nodes],
764
1953
  blocks: [...stableBlocks, ...tail.blocks.map((block) => ({
@@ -768,19 +1957,100 @@ var Renderer = class {
768
1957
  }))],
769
1958
  incrementalSafe: true
770
1959
  };
1960
+ if (debug) this.debug.emit("stable-prefix-committed", {
1961
+ blocks: stableBlocks.length,
1962
+ nodes: stableNodeEnd
1963
+ });
771
1964
  } else {
772
- next = this.parse(repairMarkdown(this.buffer), this.buffer);
773
- if (this.sanitize !== false) sanitizeNodes(next.nodes, this.sanitize);
1965
+ const fullRepaired = repairMarkdown(this.buffer);
1966
+ if (debug) this.debug.emit("markdown-repaired", {
1967
+ mode: "full",
1968
+ raw: this.buffer,
1969
+ rawLength: this.buffer.length,
1970
+ repaired: fullRepaired,
1971
+ repairedLength: fullRepaired.length
1972
+ });
1973
+ const fullParseStarted = debug ? now() : 0;
1974
+ next = this.parse(fullRepaired, this.buffer);
1975
+ if (debug) this.debug.emit("parse-completed", {
1976
+ mode: "full",
1977
+ durationMs: now() - fullParseStarted
1978
+ });
1979
+ if (this.sanitize !== false) this.sanitizeNodesWithDebug(next.nodes);
774
1980
  }
775
1981
  } else {
776
- next = this.parse(repairMarkdown(this.buffer), this.buffer);
777
- if (this.sanitize !== false) sanitizeNodes(next.nodes, this.sanitize);
1982
+ const repaired = repairMarkdown(this.buffer);
1983
+ if (debug) this.debug.emit("markdown-repaired", {
1984
+ mode: "full",
1985
+ raw: this.buffer,
1986
+ rawLength: this.buffer.length,
1987
+ repaired,
1988
+ repairedLength: repaired.length
1989
+ });
1990
+ const parseStarted = debug ? now() : 0;
1991
+ next = this.parse(repaired, this.buffer);
1992
+ if (debug) this.debug.emit("parse-completed", {
1993
+ mode: "full",
1994
+ durationMs: now() - parseStarted
1995
+ });
1996
+ if (this.sanitize !== false) this.sanitizeNodesWithDebug(next.nodes);
778
1997
  }
779
1998
  const nextAst = next.nodes;
1999
+ for (const plugin of this.options.plugins ?? []) {
2000
+ if (!plugin.onASTCommit) continue;
2001
+ try {
2002
+ plugin.onASTCommit(nextAst, {
2003
+ generation: this.generation,
2004
+ emitDebug: (type, data = {}) => this.debug.emit(type, {
2005
+ plugin: plugin.name,
2006
+ ...data
2007
+ })
2008
+ });
2009
+ } catch (error) {
2010
+ if (debug) this.debug.emit("plugin-commit-failed", {
2011
+ plugin: plugin.name,
2012
+ error
2013
+ });
2014
+ }
2015
+ }
2016
+ const diffStarted = debug ? now() : 0;
780
2017
  const patches = diffAst(this.prevAst, nextAst);
2018
+ const diffDurationMs = debug ? now() - diffStarted : 0;
781
2019
  this.parsed = next;
782
2020
  this.prevAst = nextAst;
2021
+ const patchDispatchStarted = debug ? now() : 0;
783
2022
  if (patches.length > 0) this.options.onPatch?.(patches, nextAst);
2023
+ const patchDispatchDurationMs = debug ? now() - patchDispatchStarted : 0;
2024
+ if (debug) {
2025
+ this.debug.emit("ast-snapshot", {
2026
+ mode,
2027
+ nodes: nextAst
2028
+ });
2029
+ this.debug.emit("ast-patches", {
2030
+ patches,
2031
+ durationMs: diffDurationMs
2032
+ });
2033
+ this.debug.emit("patch-dispatched", {
2034
+ patches: patches.length,
2035
+ durationMs: patchDispatchDurationMs,
2036
+ renderDurationMs: now() - renderStarted
2037
+ });
2038
+ }
2039
+ }
2040
+ sanitizeNodesWithDebug(nodes) {
2041
+ if (this.sanitize === false) return;
2042
+ if (!this.debug.active) {
2043
+ sanitizeNodes(nodes, this.sanitize);
2044
+ return;
2045
+ }
2046
+ const inputSize = nodeMarkupSize(nodes);
2047
+ const started = now();
2048
+ sanitizeNodes(nodes, this.sanitize);
2049
+ this.debug.emit("sanitizer-completed", {
2050
+ inputSize,
2051
+ outputSize: nodeMarkupSize(nodes),
2052
+ durationMs: now() - started
2053
+ });
784
2054
  }
785
2055
  };
786
2056
  /**
@@ -794,10 +2064,10 @@ function sanitizeNodes(nodes, options) {
794
2064
  if (node.children) sanitizeNodes(node.children, options);
795
2065
  }
796
2066
  }
797
- function isReadableStream$1(source) {
2067
+ function isReadableStream$2(source) {
798
2068
  return "getReader" in source && typeof source.getReader === "function";
799
2069
  }
800
- function abortReason(signal) {
2070
+ function abortReason$1(signal) {
801
2071
  return signal?.reason ?? createAbortError("The operation was aborted");
802
2072
  }
803
2073
  function createAbortError(message) {
@@ -805,6 +2075,18 @@ function createAbortError(message) {
805
2075
  error.name = "AbortError";
806
2076
  return error;
807
2077
  }
2078
+ function now() {
2079
+ return typeof performance === "undefined" ? Date.now() : performance.now();
2080
+ }
2081
+ function nodeMarkupSize(nodes) {
2082
+ let size = 0;
2083
+ for (const node of nodes) {
2084
+ size += node.content?.length ?? 0;
2085
+ size += node.html?.length ?? 0;
2086
+ if (node.children) size += nodeMarkupSize(node.children);
2087
+ }
2088
+ return size;
2089
+ }
808
2090
 
809
2091
  //#endregion
810
2092
  //#region src/stream-router.ts
@@ -877,7 +2159,7 @@ var StreamRouter = class {
877
2159
  buffer = buffer.slice(newline + 1);
878
2160
  }
879
2161
  };
880
- if (isReadableStream(source)) {
2162
+ if (isReadableStream$1(source)) {
881
2163
  const reader = source.getReader();
882
2164
  try {
883
2165
  for (;;) {
@@ -925,7 +2207,7 @@ function tryParseJson(text) {
925
2207
  function isEnvelope(value) {
926
2208
  return typeof value === "object" && value !== null && typeof value.ch === "string";
927
2209
  }
928
- function isReadableStream(source) {
2210
+ function isReadableStream$1(source) {
929
2211
  return "getReader" in source && typeof source.getReader === "function";
930
2212
  }
931
2213
 
@@ -941,21 +2223,345 @@ function buildSystemPrompt(options = {}) {
941
2223
  if (options.base) parts.push(options.base);
942
2224
  const cardSpec = options.registry?.toPromptSpec();
943
2225
  if (cardSpec) parts.push(cardSpec);
944
- for (const p of options.plugins ?? []) if (p.promptSpec) parts.push(p.promptSpec);
2226
+ for (const plugin of options.plugins ?? []) {
2227
+ const spec = typeof plugin.promptSpec === "function" ? plugin.promptSpec() : plugin.promptSpec;
2228
+ if (spec) parts.push(spec);
2229
+ }
945
2230
  return parts.join("\n\n");
946
2231
  }
947
2232
 
948
2233
  //#endregion
2234
+ //#region src/model-stream.ts
2235
+ async function* parseSSE(source, options = {}) {
2236
+ let buffer = "";
2237
+ let eventName;
2238
+ let id;
2239
+ let retry;
2240
+ let data = [];
2241
+ const doneData = options.doneData === void 0 ? "[DONE]" : options.doneData;
2242
+ const dispatch = () => {
2243
+ if (data.length === 0) {
2244
+ eventName = void 0;
2245
+ retry = void 0;
2246
+ return void 0;
2247
+ }
2248
+ const raw = data.join("\n");
2249
+ data = [];
2250
+ const result = { data: raw };
2251
+ if (eventName !== void 0) result.event = eventName;
2252
+ if (id !== void 0) result.id = id;
2253
+ if (retry !== void 0) result.retry = retry;
2254
+ eventName = void 0;
2255
+ retry = void 0;
2256
+ if (doneData !== false && raw === doneData) return "done";
2257
+ if (options.parseJSON) try {
2258
+ result.data = JSON.parse(raw);
2259
+ } catch (cause) {
2260
+ const error = malformedError("Invalid SSE JSON", raw, cause);
2261
+ if (options.onMalformed?.(error, raw) === "skip") return void 0;
2262
+ throw error;
2263
+ }
2264
+ return result;
2265
+ };
2266
+ const processLine = (line) => {
2267
+ if (line === "") return dispatch();
2268
+ if (line.startsWith(":")) return void 0;
2269
+ const colon = line.indexOf(":");
2270
+ const field = colon < 0 ? line : line.slice(0, colon);
2271
+ let value = colon < 0 ? "" : line.slice(colon + 1);
2272
+ if (value.startsWith(" ")) value = value.slice(1);
2273
+ if (field === "data") data.push(value);
2274
+ else if (field === "event") eventName = value;
2275
+ else if (field === "id" && !value.includes("\0")) id = value;
2276
+ else if (field === "retry" && /^\d+$/.test(value)) retry = Number(value);
2277
+ return void 0;
2278
+ };
2279
+ const drainLines = function* (eof = false) {
2280
+ for (;;) {
2281
+ const cr = buffer.indexOf("\r");
2282
+ const lf = buffer.indexOf("\n");
2283
+ let newline = cr < 0 ? lf : lf < 0 ? cr : Math.min(cr, lf);
2284
+ if (newline < 0) break;
2285
+ if (!eof && buffer[newline] === "\r" && newline === buffer.length - 1) break;
2286
+ const width = buffer[newline] === "\r" && buffer[newline + 1] === "\n" ? 2 : 1;
2287
+ const line = buffer.slice(0, newline);
2288
+ buffer = buffer.slice(newline + width);
2289
+ const event$1 = processLine(line);
2290
+ if (event$1) yield event$1;
2291
+ }
2292
+ };
2293
+ for await (const chunk of decodedChunks(source, options.signal)) {
2294
+ buffer += chunk;
2295
+ for (const event$1 of drainLines()) {
2296
+ if (event$1 === "done") return;
2297
+ yield event$1;
2298
+ }
2299
+ }
2300
+ for (const event$1 of drainLines(true)) {
2301
+ if (event$1 === "done") return;
2302
+ yield event$1;
2303
+ }
2304
+ if (buffer) processLine(buffer);
2305
+ const event = dispatch();
2306
+ if (event && event !== "done") yield event;
2307
+ }
2308
+ async function* jsonLines(source, options = {}) {
2309
+ let buffer = "";
2310
+ for await (const chunk of decodedChunks(source, options.signal)) {
2311
+ buffer += chunk;
2312
+ for (;;) {
2313
+ const newline = buffer.indexOf("\n");
2314
+ if (newline < 0) break;
2315
+ const line = buffer.slice(0, newline).replace(/\r$/, "");
2316
+ buffer = buffer.slice(newline + 1);
2317
+ const value$1 = parseJSONLine(line, options);
2318
+ if (value$1 !== SKIP) yield value$1;
2319
+ }
2320
+ }
2321
+ const value = parseJSONLine(buffer.replace(/\r$/, ""), options);
2322
+ if (value !== SKIP) yield value;
2323
+ }
2324
+ const ndjson = jsonLines;
2325
+ async function* textLines(source, options = {}) {
2326
+ let buffer = "";
2327
+ for await (const chunk of decodedChunks(source, options.signal)) {
2328
+ buffer += chunk;
2329
+ for (;;) {
2330
+ const newline = buffer.indexOf("\n");
2331
+ if (newline < 0) break;
2332
+ yield buffer.slice(0, newline).replace(/\r$/, "");
2333
+ buffer = buffer.slice(newline + 1);
2334
+ }
2335
+ }
2336
+ if (buffer) yield buffer.replace(/\r$/, "");
2337
+ }
2338
+ async function* contentDeltas(events) {
2339
+ for await (const event of events) if (event.type === "content") yield event.delta;
2340
+ }
2341
+ async function* mockModelStream(events, options = {}) {
2342
+ for await (const event of events) {
2343
+ throwIfAborted(options.signal);
2344
+ if (options.delayMs && options.delayMs > 0) await delay(options.delayMs, options.signal);
2345
+ yield event;
2346
+ }
2347
+ }
2348
+ function readableBytes(chunks) {
2349
+ const iterator = toAsyncIterator(chunks);
2350
+ const encoder = new TextEncoder();
2351
+ return new ReadableStream({
2352
+ async pull(controller) {
2353
+ try {
2354
+ const result = await iterator.next();
2355
+ if (result.done) controller.close();
2356
+ else controller.enqueue(typeof result.value === "string" ? encoder.encode(result.value) : result.value);
2357
+ } catch (error) {
2358
+ controller.error(error);
2359
+ }
2360
+ },
2361
+ async cancel(reason) {
2362
+ await iterator.return?.(reason);
2363
+ }
2364
+ });
2365
+ }
2366
+ async function* decodedChunks(source, signal) {
2367
+ throwIfAborted(signal);
2368
+ const decoder = new TextDecoder();
2369
+ const body = isResponse(source) ? source.body : source;
2370
+ if (!body) throw new TypeError("Response body is null");
2371
+ if (isReadableStream(body)) {
2372
+ const reader = body.getReader();
2373
+ let done$1 = false;
2374
+ let primaryError$1;
2375
+ let cancelPromise;
2376
+ const cancel = (reason) => {
2377
+ if (done$1) return Promise.resolve();
2378
+ cancelPromise ??= Promise.resolve().then(() => reader.cancel(reason));
2379
+ return cancelPromise;
2380
+ };
2381
+ const abort = () => {
2382
+ cancel(abortReason(signal)).catch(() => {});
2383
+ };
2384
+ signal?.addEventListener("abort", abort, { once: true });
2385
+ try {
2386
+ for (;;) {
2387
+ throwIfAborted(signal);
2388
+ const result = await reader.read();
2389
+ throwIfAborted(signal);
2390
+ if (result.done) {
2391
+ done$1 = true;
2392
+ break;
2393
+ }
2394
+ const text = decoder.decode(result.value, { stream: true });
2395
+ if (text) yield text;
2396
+ }
2397
+ throwIfAborted(signal);
2398
+ const tail = decoder.decode();
2399
+ if (tail) yield tail;
2400
+ } catch (error) {
2401
+ primaryError$1 = error;
2402
+ throw error;
2403
+ } finally {
2404
+ signal?.removeEventListener("abort", abort);
2405
+ try {
2406
+ if (!done$1) await cancel(abortReason(signal, "Stream iteration stopped"));
2407
+ } catch (error) {
2408
+ if (primaryError$1 === void 0) throw error;
2409
+ } finally {
2410
+ reader.releaseLock();
2411
+ }
2412
+ }
2413
+ return;
2414
+ }
2415
+ const iterator = body[Symbol.asyncIterator]();
2416
+ let done = false;
2417
+ let primaryError;
2418
+ try {
2419
+ for (;;) {
2420
+ throwIfAborted(signal);
2421
+ const result = await nextWithAbort(iterator, signal);
2422
+ throwIfAborted(signal);
2423
+ if (result.done) {
2424
+ done = true;
2425
+ break;
2426
+ }
2427
+ const text = typeof result.value === "string" ? decoder.decode() + result.value : decoder.decode(result.value, { stream: true });
2428
+ if (text) yield text;
2429
+ }
2430
+ const tail = decoder.decode();
2431
+ if (tail) yield tail;
2432
+ } catch (error) {
2433
+ primaryError = error;
2434
+ throw error;
2435
+ } finally {
2436
+ if (!done) try {
2437
+ await iterator.return?.();
2438
+ } catch (error) {
2439
+ if (primaryError === void 0) throw error;
2440
+ }
2441
+ }
2442
+ }
2443
+ async function nextWithAbort(iterator, signal) {
2444
+ if (!signal) return iterator.next();
2445
+ throwIfAborted(signal);
2446
+ const next = Promise.resolve(iterator.next());
2447
+ let abort;
2448
+ const aborted = new Promise((_resolve, reject) => {
2449
+ abort = () => reject(abortReason(signal));
2450
+ signal.addEventListener("abort", abort, { once: true });
2451
+ });
2452
+ try {
2453
+ return await Promise.race([next, aborted]);
2454
+ } finally {
2455
+ signal.removeEventListener("abort", abort);
2456
+ }
2457
+ }
2458
+ const SKIP = Symbol("skip");
2459
+ function parseJSONLine(line, options) {
2460
+ if (!line.trim()) return SKIP;
2461
+ try {
2462
+ return JSON.parse(line);
2463
+ } catch (cause) {
2464
+ const error = malformedError("Invalid JSON line", line, cause);
2465
+ if (options.onMalformed?.(error, line) === "skip") return SKIP;
2466
+ throw error;
2467
+ }
2468
+ }
2469
+ function malformedError(message, input, cause) {
2470
+ const error = new SyntaxError(`${message}: ${input}`);
2471
+ error.cause = cause;
2472
+ return error;
2473
+ }
2474
+ function isResponse(value) {
2475
+ return typeof Response !== "undefined" && value instanceof Response;
2476
+ }
2477
+ function isReadableStream(value) {
2478
+ return typeof value === "object" && value !== null && "getReader" in value && typeof value.getReader === "function";
2479
+ }
2480
+ function toAsyncIterator(source) {
2481
+ if (Symbol.asyncIterator in source) return source[Symbol.asyncIterator]();
2482
+ const iterator = source[Symbol.iterator]();
2483
+ return {
2484
+ next: async () => iterator.next(),
2485
+ return: iterator.return ? async (value) => iterator.return(value) : void 0
2486
+ };
2487
+ }
2488
+ function throwIfAborted(signal) {
2489
+ if (signal?.aborted) throw abortReason(signal);
2490
+ }
2491
+ function abortReason(signal, fallback = "The operation was aborted") {
2492
+ if (signal?.reason !== void 0) return signal.reason;
2493
+ const error = new Error(fallback);
2494
+ error.name = "AbortError";
2495
+ return error;
2496
+ }
2497
+ function delay(ms, signal) {
2498
+ return new Promise((resolve, reject) => {
2499
+ if (signal?.aborted) {
2500
+ reject(abortReason(signal));
2501
+ return;
2502
+ }
2503
+ const finish = () => {
2504
+ signal?.removeEventListener("abort", abort);
2505
+ resolve();
2506
+ };
2507
+ const timer = setTimeout(finish, ms);
2508
+ const abort = () => {
2509
+ clearTimeout(timer);
2510
+ signal?.removeEventListener("abort", abort);
2511
+ reject(abortReason(signal));
2512
+ };
2513
+ signal?.addEventListener("abort", abort, { once: true });
2514
+ });
2515
+ }
2516
+
2517
+ //#endregion
2518
+ exports.ActionAbortedError = ActionAbortedError
2519
+ exports.ActionAlreadyRegisteredError = ActionAlreadyRegisteredError
2520
+ exports.ActionDestroyedError = ActionDestroyedError
2521
+ exports.ActionExecutionError = ActionExecutionError
2522
+ exports.ActionNotFoundError = ActionNotFoundError
2523
+ exports.ActionRegistry = ActionRegistry
2524
+ exports.ActionRuntime = ActionRuntime
2525
+ exports.ActionRuntimeError = ActionRuntimeError
2526
+ exports.ActionTimeoutError = ActionTimeoutError
2527
+ exports.ActionValidationError = ActionValidationError
2528
+ exports.CARD_ID_MAX_LENGTH = CARD_ID_MAX_LENGTH
2529
+ exports.CARD_JSON_MAX_DEPTH = CARD_JSON_MAX_DEPTH
2530
+ exports.CARD_JSON_MAX_NODES = CARD_JSON_MAX_NODES
2531
+ exports.CARD_PATCH_BATCH_MAX_SIZE = CARD_PATCH_BATCH_MAX_SIZE
2532
+ exports.CardJSONError = CardJSONError
2533
+ exports.CardLimitError = CardLimitError
2534
+ exports.CardNotFoundError = CardNotFoundError
949
2535
  exports.CardRegistry = CardRegistry
2536
+ exports.CardRevisionConflictError = CardRevisionConflictError
2537
+ exports.CardSnapshotError = CardSnapshotError
2538
+ exports.CardStore = CardStore
2539
+ exports.CardStoreError = CardStoreError
2540
+ exports.CardTypeConflictError = CardTypeConflictError
2541
+ exports.CardValidationError = CardValidationError
2542
+ exports.DebugEmitter = DebugEmitter
950
2543
  exports.Renderer = Renderer
951
2544
  exports.StreamRouter = StreamRouter
952
2545
  exports.applyPatches = applyPatches
953
2546
  exports.buildSystemPrompt = buildSystemPrompt
954
2547
  exports.collectNodeRenderers = collectNodeRenderers
2548
+ exports.contentDeltas = contentDeltas
2549
+ exports.createActionRuntime = createActionRuntime
955
2550
  exports.createParser = createParser
956
2551
  exports.createParserWithMetadata = createParserWithMetadata
957
2552
  exports.diffAst = diffAst
2553
+ exports.getActionKey = getActionKey
2554
+ exports.getIdleActionState = getIdleActionState
2555
+ exports.isCardPatchResult = isCardPatchResult
2556
+ exports.jsonLines = jsonLines
2557
+ exports.mockModelStream = mockModelStream
2558
+ exports.ndjson = ndjson
958
2559
  exports.parsePartialJSON = parsePartialJSON
2560
+ exports.parseSSE = parseSSE
959
2561
  exports.pluginNodeTypes = pluginNodeTypes
2562
+ exports.readableBytes = readableBytes
960
2563
  exports.repairMarkdown = repairMarkdown
961
- exports.sanitizeHtml = sanitizeHtml
2564
+ exports.safeDebugValue = safeDebugValue
2565
+ exports.sanitizeHtml = sanitizeHtml
2566
+ exports.textLines = textLines
2567
+ exports.validateJSONSchema = validateJSONSchema