@promptbook/node 0.112.0-73 → 0.112.0-80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +9 -9
  2. package/esm/index.es.js +823 -374
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  5. package/esm/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  6. package/esm/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  7. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  8. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  9. package/esm/src/book-components/Chat/save/index.d.ts +2 -2
  10. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  11. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  13. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  14. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  15. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  16. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  18. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  19. package/esm/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  20. package/esm/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  21. package/esm/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  22. package/esm/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  23. package/esm/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  24. package/esm/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  25. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  26. package/esm/src/cli/cli-commands/agents-server.d.ts +8 -0
  27. package/esm/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  28. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  29. package/esm/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  30. package/esm/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  31. package/esm/src/utils/color/Color.d.ts +4 -44
  32. package/esm/src/utils/color/ColorValue.d.ts +55 -0
  33. package/esm/src/utils/color/isHexColorString.d.ts +10 -0
  34. package/esm/src/utils/color/parseColorString.d.ts +11 -0
  35. package/esm/src/version.d.ts +1 -1
  36. package/package.json +2 -2
  37. package/umd/index.umd.js +823 -374
  38. package/umd/index.umd.js.map +1 -1
  39. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  40. package/umd/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  41. package/umd/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  42. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  43. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  44. package/umd/src/book-components/Chat/save/index.d.ts +2 -2
  45. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  46. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  47. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  48. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  49. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  50. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  51. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  52. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  53. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  54. package/umd/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  55. package/umd/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  56. package/umd/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  57. package/umd/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  58. package/umd/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  59. package/umd/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  60. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  61. package/umd/src/cli/cli-commands/agents-server.d.ts +8 -0
  62. package/umd/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  63. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  64. package/umd/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  65. package/umd/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  66. package/umd/src/utils/color/Color.d.ts +4 -44
  67. package/umd/src/utils/color/ColorValue.d.ts +55 -0
  68. package/umd/src/utils/color/isHexColorString.d.ts +10 -0
  69. package/umd/src/utils/color/parseColorString.d.ts +11 -0
  70. package/umd/src/version.d.ts +1 -1
  71. package/esm/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  72. package/esm/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
  73. package/umd/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  74. package/umd/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
package/esm/index.es.js CHANGED
@@ -35,7 +35,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
35
35
  * @generated
36
36
  * @see https://github.com/webgptorg/promptbook
37
37
  */
38
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
38
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-80';
39
39
  /**
40
40
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
41
41
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -285,6 +285,111 @@ function checkChannelValue(channelName, value) {
285
285
  }
286
286
  }
287
287
 
288
+ /**
289
+ * Shared immutable channel storage and serialization helpers for `Color`.
290
+ *
291
+ * @private base class of Color
292
+ */
293
+ class ColorValue {
294
+ constructor(red, green, blue, alpha = 255) {
295
+ this.red = red;
296
+ this.green = green;
297
+ this.blue = blue;
298
+ this.alpha = alpha;
299
+ checkChannelValue('Red', red);
300
+ checkChannelValue('Green', green);
301
+ checkChannelValue('Blue', blue);
302
+ checkChannelValue('Alpha', alpha);
303
+ }
304
+ /**
305
+ * Shortcut for `red` property
306
+ * Number from 0 to 255
307
+ * @alias red
308
+ */
309
+ get r() {
310
+ return this.red;
311
+ }
312
+ /**
313
+ * Shortcut for `green` property
314
+ * Number from 0 to 255
315
+ * @alias green
316
+ */
317
+ get g() {
318
+ return this.green;
319
+ }
320
+ /**
321
+ * Shortcut for `blue` property
322
+ * Number from 0 to 255
323
+ * @alias blue
324
+ */
325
+ get b() {
326
+ return this.blue;
327
+ }
328
+ /**
329
+ * Shortcut for `alpha` property
330
+ * Number from 0 (transparent) to 255 (opaque)
331
+ * @alias alpha
332
+ */
333
+ get a() {
334
+ return this.alpha;
335
+ }
336
+ /**
337
+ * Shortcut for `alpha` property
338
+ * Number from 0 (transparent) to 255 (opaque)
339
+ * @alias alpha
340
+ */
341
+ get opacity() {
342
+ return this.alpha;
343
+ }
344
+ /**
345
+ * Shortcut for 1-`alpha` property
346
+ */
347
+ get transparency() {
348
+ return 255 - this.alpha;
349
+ }
350
+ clone() {
351
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
352
+ }
353
+ toString() {
354
+ return this.toHex();
355
+ }
356
+ toHex() {
357
+ if (this.alpha === 255) {
358
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
359
+ .toString(16)
360
+ .padStart(2, '0')}`;
361
+ }
362
+ else {
363
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
364
+ .toString(16)
365
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
366
+ }
367
+ }
368
+ toRgb() {
369
+ if (this.alpha === 255) {
370
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
371
+ }
372
+ else {
373
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
374
+ }
375
+ }
376
+ toHsl() {
377
+ throw new Error(`Getting HSL is not implemented`);
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Checks if the given value is a valid hex color string
383
+ *
384
+ * @param value - value to check
385
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
386
+ *
387
+ * @private function of Color
388
+ */
389
+ function isHexColorString(value) {
390
+ return (typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
391
+ }
392
+
288
393
  /**
289
394
  * Constant for short hex lengths.
290
395
  */
@@ -496,16 +601,53 @@ function parseAlphaValue(value) {
496
601
 
497
602
  /**
498
603
  * Pattern matching hsl regex.
604
+ *
605
+ * @private function of Color
499
606
  */
500
607
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
501
608
  /**
502
609
  * Pattern matching RGB regex.
610
+ *
611
+ * @private function of Color
503
612
  */
504
613
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
505
614
  /**
506
615
  * Pattern matching rgba regex.
616
+ *
617
+ * @private function of Color
507
618
  */
508
619
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
620
+ /**
621
+ * Parses a supported color string into RGBA channels.
622
+ *
623
+ * @param color as a string for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`, `red`, `darkgrey`,...
624
+ * @returns RGBA channel values.
625
+ *
626
+ * @private function of Color
627
+ */
628
+ function parseColorString(color) {
629
+ const trimmed = color.trim();
630
+ const cssColor = CSS_COLORS[trimmed];
631
+ if (cssColor) {
632
+ return parseColorString(cssColor);
633
+ }
634
+ else if (isHexColorString(trimmed)) {
635
+ return parseHexColor(trimmed);
636
+ }
637
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
638
+ return parseHslColor(trimmed);
639
+ }
640
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
641
+ return parseRgbColor(trimmed);
642
+ }
643
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
644
+ return parseRgbaColor(trimmed);
645
+ }
646
+ else {
647
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
648
+ }
649
+ }
650
+
509
651
  /**
510
652
  * Color object represents an RGB color with alpha channel
511
653
  *
@@ -513,7 +655,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
513
655
  *
514
656
  * @public exported from `@promptbook/color`
515
657
  */
516
- class Color {
658
+ class Color extends ColorValue {
517
659
  /**
518
660
  * Creates a new Color instance from miscellaneous formats
519
661
  * - It can receive Color instance and just return the same instance
@@ -586,25 +728,7 @@ class Color {
586
728
  * @returns Color object
587
729
  */
588
730
  static fromString(color) {
589
- const trimmed = color.trim();
590
- if (CSS_COLORS[trimmed]) {
591
- return Color.fromString(CSS_COLORS[trimmed]);
592
- }
593
- else if (Color.isHexColorString(trimmed)) {
594
- return Color.fromHex(trimmed);
595
- }
596
- if (HSL_REGEX_PATTERN.test(trimmed)) {
597
- return Color.fromHsl(trimmed);
598
- }
599
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
600
- return Color.fromRgbString(trimmed);
601
- }
602
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
603
- return Color.fromRgbaString(trimmed);
604
- }
605
- else {
606
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
607
- }
731
+ return Color.fromColorChannels(parseColorString(color));
608
732
  }
609
733
  /**
610
734
  * Gets common color
@@ -634,8 +758,7 @@ class Color {
634
758
  * @returns Color object
635
759
  */
636
760
  static fromHex(hex) {
637
- const { red, green, blue, alpha } = parseHexColor(hex);
638
- return take(new Color(red, green, blue, alpha));
761
+ return Color.fromColorChannels(parseHexColor(hex));
639
762
  }
640
763
  /**
641
764
  * Creates a new Color instance from color in hsl format
@@ -644,8 +767,7 @@ class Color {
644
767
  * @returns Color object
645
768
  */
646
769
  static fromHsl(hsl) {
647
- const { red, green, blue, alpha } = parseHslColor(hsl);
648
- return take(new Color(red, green, blue, alpha));
770
+ return Color.fromColorChannels(parseHslColor(hsl));
649
771
  }
650
772
  /**
651
773
  * Creates a new Color instance from color in rgb format
@@ -654,8 +776,7 @@ class Color {
654
776
  * @returns Color object
655
777
  */
656
778
  static fromRgbString(rgb) {
657
- const { red, green, blue, alpha } = parseRgbColor(rgb);
658
- return take(new Color(red, green, blue, alpha));
779
+ return Color.fromColorChannels(parseRgbColor(rgb));
659
780
  }
660
781
  /**
661
782
  * Creates a new Color instance from color in rbga format
@@ -664,8 +785,7 @@ class Color {
664
785
  * @returns Color object
665
786
  */
666
787
  static fromRgbaString(rgba) {
667
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
668
- return take(new Color(red, green, blue, alpha));
788
+ return Color.fromColorChannels(parseRgbaColor(rgba));
669
789
  }
670
790
  /**
671
791
  * Creates a new Color for color channels values
@@ -677,7 +797,7 @@ class Color {
677
797
  * @returns Color object
678
798
  */
679
799
  static fromValues(red, green, blue, alpha = 255) {
680
- return take(new Color(red, green, blue, alpha));
800
+ return Color.fromColorChannels({ red, green, blue, alpha });
681
801
  }
682
802
  /**
683
803
  * Checks if the given value is a valid Color object.
@@ -710,8 +830,7 @@ class Color {
710
830
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
711
831
  */
712
832
  static isHexColorString(value) {
713
- return (typeof value === 'string' &&
714
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
833
+ return isHexColorString(value);
715
834
  }
716
835
  /**
717
836
  * Creates new Color object
@@ -724,89 +843,13 @@ class Color {
724
843
  * @param alpha number from 0 (transparent) to 255 (opaque)
725
844
  */
726
845
  constructor(red, green, blue, alpha = 255) {
727
- this.red = red;
728
- this.green = green;
729
- this.blue = blue;
730
- this.alpha = alpha;
731
- checkChannelValue('Red', red);
732
- checkChannelValue('Green', green);
733
- checkChannelValue('Blue', blue);
734
- checkChannelValue('Alpha', alpha);
735
- }
736
- /**
737
- * Shortcut for `red` property
738
- * Number from 0 to 255
739
- * @alias red
740
- */
741
- get r() {
742
- return this.red;
743
- }
744
- /**
745
- * Shortcut for `green` property
746
- * Number from 0 to 255
747
- * @alias green
748
- */
749
- get g() {
750
- return this.green;
751
- }
752
- /**
753
- * Shortcut for `blue` property
754
- * Number from 0 to 255
755
- * @alias blue
756
- */
757
- get b() {
758
- return this.blue;
759
- }
760
- /**
761
- * Shortcut for `alpha` property
762
- * Number from 0 (transparent) to 255 (opaque)
763
- * @alias alpha
764
- */
765
- get a() {
766
- return this.alpha;
767
- }
768
- /**
769
- * Shortcut for `alpha` property
770
- * Number from 0 (transparent) to 255 (opaque)
771
- * @alias alpha
772
- */
773
- get opacity() {
774
- return this.alpha;
775
- }
776
- /**
777
- * Shortcut for 1-`alpha` property
778
- */
779
- get transparency() {
780
- return 255 - this.alpha;
781
- }
782
- clone() {
783
- return take(new Color(this.red, this.green, this.blue, this.alpha));
784
- }
785
- toString() {
786
- return this.toHex();
787
- }
788
- toHex() {
789
- if (this.alpha === 255) {
790
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
791
- .toString(16)
792
- .padStart(2, '0')}`;
793
- }
794
- else {
795
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
796
- .toString(16)
797
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
798
- }
846
+ super(red, green, blue, alpha);
799
847
  }
800
- toRgb() {
801
- if (this.alpha === 255) {
802
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
803
- }
804
- else {
805
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
806
- }
848
+ createColor(red, green, blue, alpha) {
849
+ return new Color(red, green, blue, alpha);
807
850
  }
808
- toHsl() {
809
- throw new Error(`Getting HSL is not implemented`);
851
+ static fromColorChannels({ red, green, blue, alpha }) {
852
+ return take(new Color(red, green, blue, alpha));
810
853
  }
811
854
  }
812
855
 
@@ -1491,120 +1534,183 @@ function assertsError(whatWasThrown) {
1491
1534
  * @public exported from `@promptbook/utils`
1492
1535
  */
1493
1536
  function checkSerializableAsJson(options) {
1494
- const { value, name, message } = options;
1537
+ checkSerializableValue(options);
1538
+ }
1539
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1540
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1541
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1542
+ /**
1543
+ * Checks one value and dispatches to the appropriate specialized validator.
1544
+ *
1545
+ * @private function of `checkSerializableAsJson`
1546
+ */
1547
+ function checkSerializableValue(options) {
1548
+ const { value } = options;
1549
+ if (isSerializablePrimitive(value)) {
1550
+ return;
1551
+ }
1495
1552
  if (value === undefined) {
1496
- throw new UnexpectedError(`${name} is undefined`);
1553
+ throw new UnexpectedError(`${options.name} is undefined`);
1497
1554
  }
1498
- else if (value === null) {
1499
- return;
1555
+ if (typeof value === 'symbol') {
1556
+ throw new UnexpectedError(`${options.name} is symbol`);
1500
1557
  }
1501
- else if (typeof value === 'boolean') {
1502
- return;
1558
+ if (typeof value === 'function') {
1559
+ throw new UnexpectedError(`${options.name} is function`);
1503
1560
  }
1504
- else if (typeof value === 'number' && !isNaN(value)) {
1561
+ if (Array.isArray(value)) {
1562
+ checkSerializableArray(options, value);
1505
1563
  return;
1506
1564
  }
1507
- else if (typeof value === 'string') {
1565
+ if (value !== null && typeof value === 'object') {
1566
+ checkSerializableObject(options, value);
1508
1567
  return;
1509
1568
  }
1510
- else if (typeof value === 'symbol') {
1511
- throw new UnexpectedError(`${name} is symbol`);
1512
- }
1513
- else if (typeof value === 'function') {
1514
- throw new UnexpectedError(`${name} is function`);
1515
- }
1516
- else if (typeof value === 'object' && Array.isArray(value)) {
1517
- for (let i = 0; i < value.length; i++) {
1518
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1519
- }
1569
+ throwUnknownTypeError(options);
1570
+ }
1571
+ /**
1572
+ * Checks the primitive values that are directly JSON serializable.
1573
+ *
1574
+ * @private function of `checkSerializableAsJson`
1575
+ */
1576
+ function isSerializablePrimitive(value) {
1577
+ return (value === null ||
1578
+ typeof value === 'boolean' ||
1579
+ (typeof value === 'number' && !isNaN(value)) ||
1580
+ typeof value === 'string');
1581
+ }
1582
+ /**
1583
+ * Recursively checks JSON array items.
1584
+ *
1585
+ * @private function of `checkSerializableAsJson`
1586
+ */
1587
+ function checkSerializableArray(context, arrayValue) {
1588
+ for (let index = 0; index < arrayValue.length; index++) {
1589
+ checkSerializableAsJson({
1590
+ ...context,
1591
+ name: `${context.name}[${index}]`,
1592
+ value: arrayValue[index],
1593
+ });
1520
1594
  }
1521
- else if (typeof value === 'object') {
1522
- if (value instanceof Date) {
1523
- throw new UnexpectedError(spaceTrim$1((block) => `
1524
- \`${name}\` is Date
1595
+ }
1596
+ /**
1597
+ * Checks object-like values and dispatches special unsupported built-ins.
1598
+ *
1599
+ * @private function of `checkSerializableAsJson`
1600
+ */
1601
+ function checkSerializableObject(context, objectValue) {
1602
+ checkUnsupportedObjectType(context, objectValue);
1603
+ checkSerializableObjectEntries(context, objectValue);
1604
+ assertJsonStringificationSucceeds(context, objectValue);
1605
+ }
1606
+ /**
1607
+ * Rejects built-in objects that must be converted before JSON serialization.
1608
+ *
1609
+ * @private function of `checkSerializableAsJson`
1610
+ */
1611
+ function checkUnsupportedObjectType(context, objectValue) {
1612
+ if (objectValue instanceof Date) {
1613
+ throw new UnexpectedError(spaceTrim$1((block) => `
1614
+ \`${context.name}\` is Date
1525
1615
 
1526
- Use \`string_date_iso8601\` instead
1616
+ Use \`string_date_iso8601\` instead
1527
1617
 
1528
- Additional message for \`${name}\`:
1529
- ${block(message || '(nothing)')}
1530
- `));
1531
- }
1532
- else if (value instanceof Map) {
1533
- throw new UnexpectedError(`${name} is Map`);
1534
- }
1535
- else if (value instanceof Set) {
1536
- throw new UnexpectedError(`${name} is Set`);
1537
- }
1538
- else if (value instanceof RegExp) {
1539
- throw new UnexpectedError(`${name} is RegExp`);
1540
- }
1541
- else if (value instanceof Error) {
1542
- throw new UnexpectedError(spaceTrim$1((block) => `
1543
- \`${name}\` is unserialized Error
1618
+ Additional message for \`${context.name}\`:
1619
+ ${block(context.message || '(nothing)')}
1620
+ `));
1621
+ }
1622
+ if (objectValue instanceof Map) {
1623
+ throw new UnexpectedError(`${context.name} is Map`);
1624
+ }
1625
+ if (objectValue instanceof Set) {
1626
+ throw new UnexpectedError(`${context.name} is Set`);
1627
+ }
1628
+ if (objectValue instanceof RegExp) {
1629
+ throw new UnexpectedError(`${context.name} is RegExp`);
1630
+ }
1631
+ if (objectValue instanceof Error) {
1632
+ throw new UnexpectedError(spaceTrim$1((block) => `
1633
+ \`${context.name}\` is unserialized Error
1544
1634
 
1545
- Use function \`serializeError\`
1635
+ Use function \`serializeError\`
1546
1636
 
1547
- Additional message for \`${name}\`:
1548
- ${block(message || '(nothing)')}
1637
+ Additional message for \`${context.name}\`:
1638
+ ${block(context.message || '(nothing)')}
1549
1639
 
1550
- `));
1640
+ `));
1641
+ }
1642
+ }
1643
+ /**
1644
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1645
+ *
1646
+ * @private function of `checkSerializableAsJson`
1647
+ */
1648
+ function checkSerializableObjectEntries(context, objectValue) {
1649
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1650
+ if (subValue === undefined) {
1651
+ // Note: undefined in object is serializable - it is just omitted
1652
+ continue;
1551
1653
  }
1552
- else {
1553
- for (const [subName, subValue] of Object.entries(value)) {
1554
- if (subValue === undefined) {
1555
- // Note: undefined in object is serializable - it is just omitted
1556
- continue;
1557
- }
1558
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1559
- }
1560
- try {
1561
- JSON.stringify(value); // <- TODO: [0]
1562
- }
1563
- catch (error) {
1564
- assertsError(error);
1565
- throw new UnexpectedError(spaceTrim$1((block) => `
1566
- \`${name}\` is not serializable
1654
+ checkSerializableAsJson({
1655
+ ...context,
1656
+ name: `${context.name}.${subName}`,
1657
+ value: subValue,
1658
+ });
1659
+ }
1660
+ }
1661
+ /**
1662
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1663
+ *
1664
+ * @private function of `checkSerializableAsJson`
1665
+ */
1666
+ function assertJsonStringificationSucceeds(context, objectValue) {
1667
+ try {
1668
+ JSON.stringify(objectValue); // <- TODO: [0]
1669
+ }
1670
+ catch (error) {
1671
+ assertsError(error);
1672
+ throw new UnexpectedError(spaceTrim$1((block) => `
1673
+ \`${context.name}\` is not serializable
1567
1674
 
1568
- ${block(error.stack || error.message)}
1675
+ ${block(error.stack || error.message)}
1569
1676
 
1570
- Additional message for \`${name}\`:
1571
- ${block(message || '(nothing)')}
1572
- `));
1677
+ Additional message for \`${context.name}\`:
1678
+ ${block(context.message || '(nothing)')}
1679
+ `));
1680
+ }
1681
+ /*
1682
+ TODO: [0] Is there some more elegant way to check circular references?
1683
+ const seen = new Set();
1684
+ const stack = [{ value }];
1685
+ while (stack.length > 0) {
1686
+ const { value } = stack.pop()!;
1687
+ if (typeof value === 'object' && value !== null) {
1688
+ if (seen.has(value)) {
1689
+ throw new UnexpectedError(`${name} has circular reference`);
1573
1690
  }
1574
- /*
1575
- TODO: [0] Is there some more elegant way to check circular references?
1576
- const seen = new Set();
1577
- const stack = [{ value }];
1578
- while (stack.length > 0) {
1579
- const { value } = stack.pop()!;
1580
- if (typeof value === 'object' && value !== null) {
1581
- if (seen.has(value)) {
1582
- throw new UnexpectedError(`${name} has circular reference`);
1583
- }
1584
- seen.add(value);
1585
- if (Array.isArray(value)) {
1586
- stack.push(...value.map((value) => ({ value })));
1587
- } else {
1588
- stack.push(...Object.values(value).map((value) => ({ value })));
1589
- }
1590
- }
1691
+ seen.add(value);
1692
+ if (Array.isArray(value)) {
1693
+ stack.push(...value.map((value) => ({ value })));
1694
+ } else {
1695
+ stack.push(...Object.values(value).map((value) => ({ value })));
1591
1696
  }
1592
- */
1593
- return;
1594
1697
  }
1595
1698
  }
1596
- else {
1597
- throw new UnexpectedError(spaceTrim$1((block) => `
1598
- \`${name}\` is unknown type
1699
+ */
1700
+ }
1701
+ /**
1702
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
1703
+ *
1704
+ * @private function of `checkSerializableAsJson`
1705
+ */
1706
+ function throwUnknownTypeError(context) {
1707
+ throw new UnexpectedError(spaceTrim$1((block) => `
1708
+ \`${context.name}\` is unknown type
1599
1709
 
1600
- Additional message for \`${name}\`:
1601
- ${block(message || '(nothing)')}
1602
- `));
1603
- }
1710
+ Additional message for \`${context.name}\`:
1711
+ ${block(context.message || '(nothing)')}
1712
+ `));
1604
1713
  }
1605
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1606
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1607
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1608
1714
 
1609
1715
  /**
1610
1716
  * Creates a deep clone of the given object
@@ -2264,8 +2370,7 @@ function hasTaskJokers(task) {
2264
2370
  * @private internal utility of `validatePipeline`
2265
2371
  */
2266
2372
  function validateTaskSupportsJokers(task, pipelineIdentification) {
2267
- if (task.format ||
2268
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2373
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2269
2374
  return;
2270
2375
  }
2271
2376
  throw new PipelineLogicError(spaceTrim$1((block) => `
@@ -2830,7 +2935,7 @@ function createJokerCommands(task) {
2830
2935
  */
2831
2936
  function createPostprocessingCommands(task) {
2832
2937
  var _a;
2833
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
2938
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
2834
2939
  }
2835
2940
  /**
2836
2941
  * Collects expectation commands.
@@ -5946,9 +6051,7 @@ function createFailuresSummary($failedResults) {
5946
6051
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
5947
6052
 
5948
6053
  Result:
5949
- ${block(failure.result === null
5950
- ? 'null'
5951
- : quoteMultilineText(spaceTrim$1(failure.result)))}
6054
+ ${block(failure.result === null ? 'null' : quoteMultilineText(spaceTrim$1(failure.result)))}
5952
6055
  `;
5953
6056
  }))
5954
6057
  .join('\n\n---\n\n');
@@ -17455,7 +17558,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
17455
17558
  *
17456
17559
  * @private helper of `minecraft2AvatarVisual`
17457
17560
  */
17458
- const LIGHT_DIRECTION$1 = normalizeVector3({
17561
+ const LIGHT_DIRECTION$2 = normalizeVector3({
17459
17562
  x: 0.4,
17460
17563
  y: -0.65,
17461
17564
  z: 0.92,
@@ -17667,7 +17770,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
17667
17770
  corners: projectedCorners,
17668
17771
  texture: faceDefinition.texture,
17669
17772
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
17670
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
17773
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
17671
17774
  outlineColor: cuboid.outlineColor,
17672
17775
  };
17673
17776
  });
@@ -18727,13 +18830,138 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
18727
18830
  context.restore();
18728
18831
  }
18729
18832
 
18833
+ /* eslint-disable no-magic-numbers */
18834
+ /**
18835
+ * Draws one projected eye on a rotated octopus surface.
18836
+ *
18837
+ * @private helper of the 3D octopus avatar visuals
18838
+ */
18839
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
18840
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18841
+ if (centerScenePoint.z <= center.z) {
18842
+ return;
18843
+ }
18844
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
18845
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
18846
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
18847
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
18848
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
18849
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
18850
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
18851
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
18852
+ return;
18853
+ }
18854
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
18855
+ radiusX: projectedRadiusX,
18856
+ radiusY: projectedRadiusY,
18857
+ timeMs,
18858
+ phase,
18859
+ interaction,
18860
+ });
18861
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
18862
+ context.save();
18863
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
18864
+ context.rotate(rotation);
18865
+ context.beginPath();
18866
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
18867
+ context.fillStyle = '#f8fbff';
18868
+ context.fill();
18869
+ context.clip();
18870
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
18871
+ irisGradient.addColorStop(0, palette.highlight);
18872
+ irisGradient.addColorStop(0.56, palette.secondary);
18873
+ irisGradient.addColorStop(1, palette.shadow);
18874
+ context.beginPath();
18875
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
18876
+ context.fillStyle = irisGradient;
18877
+ context.fill();
18878
+ context.beginPath();
18879
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
18880
+ context.fillStyle = palette.ink;
18881
+ context.fill();
18882
+ context.beginPath();
18883
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
18884
+ context.fillStyle = '#ffffff';
18885
+ context.fill();
18886
+ context.restore();
18887
+ context.save();
18888
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
18889
+ context.rotate(rotation);
18890
+ context.beginPath();
18891
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
18892
+ context.strokeStyle = `${palette.shadow}cc`;
18893
+ context.lineWidth = projectedRadiusX * 0.16;
18894
+ context.stroke();
18895
+ context.beginPath();
18896
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
18897
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
18898
+ context.strokeStyle = `${palette.shadow}73`;
18899
+ context.lineWidth = projectedRadiusX * 0.14;
18900
+ context.lineCap = 'round';
18901
+ context.stroke();
18902
+ if (eyeStyle.lowerLidOpacity > 0) {
18903
+ context.beginPath();
18904
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
18905
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
18906
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
18907
+ context.lineWidth = projectedRadiusX * 0.08;
18908
+ context.lineCap = 'round';
18909
+ context.stroke();
18910
+ }
18911
+ context.restore();
18912
+ }
18913
+ /**
18914
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
18915
+ *
18916
+ * @private helper of the 3D octopus avatar visuals
18917
+ */
18918
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
18919
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
18920
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
18921
+ return;
18922
+ }
18923
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
18924
+ context.beginPath();
18925
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
18926
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
18927
+ context.strokeStyle = `${palette.ink}b8`;
18928
+ context.lineWidth = Math.max(1.1, size * 0.009);
18929
+ context.lineCap = 'round';
18930
+ context.stroke();
18931
+ }
18932
+ /**
18933
+ * Draws one filled projected quad.
18934
+ *
18935
+ * @private helper of the 3D octopus avatar visuals
18936
+ */
18937
+ function drawProjectedQuad(context, corners, fillStyle) {
18938
+ context.beginPath();
18939
+ context.moveTo(corners[0].x, corners[0].y);
18940
+ context.lineTo(corners[1].x, corners[1].y);
18941
+ context.lineTo(corners[2].x, corners[2].y);
18942
+ context.lineTo(corners[3].x, corners[3].y);
18943
+ context.closePath();
18944
+ context.fillStyle = fillStyle;
18945
+ context.fill();
18946
+ }
18947
+ /**
18948
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
18949
+ *
18950
+ * @private helper of the 3D octopus avatar visuals
18951
+ */
18952
+ function formatAlphaHex(opacity) {
18953
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
18954
+ .toString(16)
18955
+ .padStart(2, '0');
18956
+ }
18957
+
18730
18958
  /* eslint-disable no-magic-numbers */
18731
18959
  /**
18732
18960
  * Light direction used by the organic 3D octopus shading.
18733
18961
  *
18734
18962
  * @private helper of `octopus3dAvatarVisual`
18735
18963
  */
18736
- const LIGHT_DIRECTION = normalizeVector3({
18964
+ const LIGHT_DIRECTION$1 = normalizeVector3({
18737
18965
  x: 0.48,
18738
18966
  y: -0.62,
18739
18967
  z: 0.94,
@@ -18846,17 +19074,17 @@ const octopus3dAvatarVisual = {
18846
19074
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
18847
19075
  drawTentacleStroke(context, tentacleStroke, palette);
18848
19076
  }
18849
- drawProjectedEye(context, {
19077
+ drawProjectedOrganicEye(context, {
18850
19078
  x: -faceEyeSpacing,
18851
19079
  y: faceEyeYOffset,
18852
19080
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
18853
19081
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
18854
- drawProjectedEye(context, {
19082
+ drawProjectedOrganicEye(context, {
18855
19083
  x: faceEyeSpacing,
18856
19084
  y: faceEyeYOffset,
18857
19085
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
18858
19086
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
18859
- drawProjectedMouth(context, [
19087
+ drawProjectedOrganicMouth(context, [
18860
19088
  {
18861
19089
  x: -mouthHalfWidth,
18862
19090
  y: mouthY,
@@ -18943,7 +19171,7 @@ function resolveVisibleEllipsoidPatches(options) {
18943
19171
  corners: projectedCorners,
18944
19172
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
18945
19173
  transformedCorners.length,
18946
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
19174
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
18947
19175
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
18948
19176
  outlineColor,
18949
19177
  });
@@ -19125,128 +19353,260 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
19125
19353
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
19126
19354
  return Math.sqrt(remainingDepthRatio) * radiusZ;
19127
19355
  }
19356
+
19357
+ /* eslint-disable no-magic-numbers */
19128
19358
  /**
19129
- * Draws one projected eye on the turned octopus mantle.
19359
+ * Light direction used by the single-mesh octopus shading.
19130
19360
  *
19131
- * @private helper of `octopus3dAvatarVisual`
19361
+ * @private helper of `octopus3d2AvatarVisual`
19132
19362
  */
19133
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
19134
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
19135
- if (centerScenePoint.z <= center.z) {
19136
- return;
19137
- }
19138
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
19139
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
19140
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
19141
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
19142
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
19143
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
19144
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
19145
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
19146
- return;
19147
- }
19148
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
19149
- radiusX: projectedRadiusX,
19150
- radiusY: projectedRadiusY,
19151
- timeMs,
19152
- phase,
19153
- interaction,
19154
- });
19155
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
19363
+ const LIGHT_DIRECTION = normalizeVector3({
19364
+ x: 0.38,
19365
+ y: -0.6,
19366
+ z: 0.98,
19367
+ });
19368
+ /**
19369
+ * Octopus 3D 2 avatar visual.
19370
+ *
19371
+ * @private built-in avatar visual
19372
+ */
19373
+ const octopus3d2AvatarVisual = {
19374
+ id: 'octopus3d2',
19375
+ title: 'Octopus 3D 2',
19376
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
19377
+ isAnimated: true,
19378
+ supportsPointerTracking: true,
19379
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
19380
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
19381
+ const animationRandom = createRandom('octopus3d2-animation-profile');
19382
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
19383
+ const animationPhase = animationRandom() * Math.PI * 2;
19384
+ const sceneCenterX = size * 0.5;
19385
+ const sceneCenterY = size * 0.575;
19386
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
19387
+ const meshCenter = {
19388
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
19389
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
19390
+ z: interaction.intensity * size * 0.018,
19391
+ };
19392
+ const rotationY = -0.14 +
19393
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
19394
+ interaction.bodyOffsetX * 0.2 +
19395
+ interaction.gazeX * 0.78;
19396
+ const rotationX = -0.06 +
19397
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
19398
+ interaction.bodyOffsetY * 0.08 -
19399
+ interaction.gazeY * 0.34;
19400
+ const surfaceOptions = {
19401
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
19402
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
19403
+ radiusZ: size *
19404
+ morphologyProfile.body.bodyRadiusRatio *
19405
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
19406
+ morphologyProfile,
19407
+ timeMs,
19408
+ animationPhase,
19409
+ };
19410
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
19411
+ ...surfaceOptions,
19412
+ center: meshCenter,
19413
+ rotationX,
19414
+ rotationY,
19415
+ sceneCenterX,
19416
+ sceneCenterY,
19417
+ size,
19418
+ palette,
19419
+ });
19420
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
19421
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
19422
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
19423
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
19424
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
19425
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
19426
+ drawAvatarFrame(context, size, palette);
19427
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
19428
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
19429
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
19430
+ drawBlobbySurfacePatch(context, surfacePatch);
19431
+ }
19432
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
19433
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
19434
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
19435
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
19436
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
19437
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
19438
+ drawProjectedOrganicMouth(context, [
19439
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
19440
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
19441
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
19442
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
19443
+ },
19444
+ };
19445
+ /**
19446
+ * Draws the deep-water glow behind the continuous octopus mesh.
19447
+ *
19448
+ * @private helper of `octopus3d2AvatarVisual`
19449
+ */
19450
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
19451
+ const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.11, sceneCenterY - size * 0.17 + interaction.gazeY * size * 0.05, size * 0.05, sceneCenterX, sceneCenterY - size * 0.03, size * 0.66);
19452
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
19453
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
19454
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
19455
+ context.fillStyle = glowGradient;
19456
+ context.fillRect(0, 0, size, size);
19457
+ const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1650) * size * 0.045, sceneCenterY + size * 0.28, size * 0.06, sceneCenterX, sceneCenterY + size * 0.28, size * 0.52);
19458
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
19459
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
19460
+ context.fillStyle = lowerGradient;
19461
+ context.fillRect(0, 0, size, size);
19462
+ }
19463
+ /**
19464
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
19465
+ *
19466
+ * @private helper of `octopus3d2AvatarVisual`
19467
+ */
19468
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
19156
19469
  context.save();
19157
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
19158
- context.rotate(rotation);
19159
- context.beginPath();
19160
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
19161
- context.fillStyle = '#f8fbff';
19162
- context.fill();
19163
- context.clip();
19164
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
19165
- irisGradient.addColorStop(0, palette.highlight);
19166
- irisGradient.addColorStop(0.56, palette.secondary);
19167
- irisGradient.addColorStop(1, palette.shadow);
19168
- context.beginPath();
19169
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
19170
- context.fillStyle = irisGradient;
19171
- context.fill();
19470
+ context.fillStyle = `${palette.shadow}66`;
19471
+ context.filter = `blur(${size * 0.024}px)`;
19172
19472
  context.beginPath();
19173
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
19174
- context.fillStyle = palette.ink;
19473
+ context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.88 + Math.sin(timeMs / 940) * size * 0.008, size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018), size * 0.062, 0, 0, Math.PI * 2);
19175
19474
  context.fill();
19176
- context.beginPath();
19177
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
19178
- context.fillStyle = '#ffffff';
19179
- context.fill();
19180
- context.restore();
19181
- context.save();
19182
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
19183
- context.rotate(rotation);
19184
- context.beginPath();
19185
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
19186
- context.strokeStyle = `${palette.shadow}cc`;
19187
- context.lineWidth = projectedRadiusX * 0.16;
19188
- context.stroke();
19189
- context.beginPath();
19190
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
19191
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
19192
- context.strokeStyle = `${palette.shadow}73`;
19193
- context.lineWidth = projectedRadiusX * 0.14;
19194
- context.lineCap = 'round';
19195
- context.stroke();
19196
- if (eyeStyle.lowerLidOpacity > 0) {
19197
- context.beginPath();
19198
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
19199
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
19200
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
19201
- context.lineWidth = projectedRadiusX * 0.08;
19202
- context.lineCap = 'round';
19203
- context.stroke();
19204
- }
19205
19475
  context.restore();
19206
19476
  }
19207
19477
  /**
19208
- * Draws a subtle projected mouth arc across the front of the mantle.
19478
+ * Resolves all visible projected patches for the single blobby octopus mesh.
19209
19479
  *
19210
- * @private helper of `octopus3dAvatarVisual`
19480
+ * @private helper of `octopus3d2AvatarVisual`
19211
19481
  */
19212
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
19213
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
19214
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
19215
- return;
19482
+ function resolveVisibleBlobbyOctopusPatches(options) {
19483
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
19484
+ const latitudePatchCount = 12;
19485
+ const longitudePatchCount = 24;
19486
+ const surfacePatches = [];
19487
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
19488
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
19489
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
19490
+ const centerLatitude = (startLatitude + endLatitude) / 2;
19491
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
19492
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
19493
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
19494
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
19495
+ const centerLongitude = (startLongitude + endLongitude) / 2;
19496
+ const localCorners = [
19497
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
19498
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
19499
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
19500
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
19501
+ ];
19502
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
19503
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
19504
+ if (surfaceNormal.z <= 0.01) {
19505
+ continue;
19506
+ }
19507
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
19508
+ surfacePatches.push({
19509
+ corners: projectedCorners,
19510
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
19511
+ transformedCorners.length,
19512
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
19513
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
19514
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
19515
+ });
19516
+ }
19216
19517
  }
19217
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
19218
- context.beginPath();
19219
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
19220
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
19221
- context.strokeStyle = `${palette.ink}b8`;
19222
- context.lineWidth = Math.max(1.1, size * 0.009);
19223
- context.lineCap = 'round';
19224
- context.stroke();
19518
+ return surfacePatches;
19225
19519
  }
19226
19520
  /**
19227
- * Draws one filled projected quad.
19521
+ * Samples one point on the continuous Octopus 3D 2 surface.
19522
+ *
19523
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
19524
+ * instead of switching to separately rendered tentacles.
19525
+ *
19526
+ * @private helper of `octopus3d2AvatarVisual`
19527
+ */
19528
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
19529
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
19530
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
19531
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
19532
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
19533
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
19534
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
19535
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
19536
+ const horizontalScale = 1.02 +
19537
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
19538
+ upperBlend * 0.08;
19539
+ const depthScale = 1.04 +
19540
+ upperBlend * 0.16 +
19541
+ Math.max(0, Math.cos(longitude)) * 0.1 +
19542
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
19543
+ Math.max(0, -Math.cos(longitude)) * 0.04;
19544
+ const lowerDrop = skirtEnvelope *
19545
+ radiusY *
19546
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
19547
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
19548
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
19549
+ return {
19550
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
19551
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
19552
+ upperBlend * radiusY * 0.1 +
19553
+ lowerDrop +
19554
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
19555
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
19556
+ };
19557
+ }
19558
+ /**
19559
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
19228
19560
  *
19229
- * @private helper of `octopus3dAvatarVisual`
19561
+ * @private helper of `octopus3d2AvatarVisual`
19230
19562
  */
19231
- function drawProjectedQuad(context, corners, fillStyle) {
19232
- context.beginPath();
19233
- context.moveTo(corners[0].x, corners[0].y);
19234
- context.lineTo(corners[1].x, corners[1].y);
19235
- context.lineTo(corners[2].x, corners[2].y);
19236
- context.lineTo(corners[3].x, corners[3].y);
19237
- context.closePath();
19238
- context.fillStyle = fillStyle;
19239
- context.fill();
19563
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
19564
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
19565
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
19240
19566
  }
19241
19567
  /**
19242
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
19568
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
19243
19569
  *
19244
- * @private helper of `octopus3dAvatarVisual`
19570
+ * @private helper of `octopus3d2AvatarVisual`
19245
19571
  */
19246
- function formatAlphaHex(opacity) {
19247
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
19248
- .toString(16)
19249
- .padStart(2, '0');
19572
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
19573
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
19574
+ if (tonalProgress < 0.16) {
19575
+ return palette.highlight;
19576
+ }
19577
+ if (tonalProgress < 0.34) {
19578
+ return palette.secondary;
19579
+ }
19580
+ if (tonalProgress < 0.72) {
19581
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
19582
+ }
19583
+ return `${palette.shadow}f2`;
19584
+ }
19585
+ /**
19586
+ * Draws one projected patch with soft octopus shading.
19587
+ *
19588
+ * @private helper of `octopus3d2AvatarVisual`
19589
+ */
19590
+ function drawBlobbySurfacePatch(context, surfacePatch) {
19591
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
19592
+ if (surfacePatch.lightIntensity > 0) {
19593
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
19594
+ }
19595
+ else if (surfacePatch.lightIntensity < 0) {
19596
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
19597
+ }
19598
+ context.save();
19599
+ context.beginPath();
19600
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
19601
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
19602
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
19603
+ }
19604
+ context.closePath();
19605
+ context.strokeStyle = surfacePatch.outlineColor;
19606
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
19607
+ context.lineJoin = 'round';
19608
+ context.stroke();
19609
+ context.restore();
19250
19610
  }
19251
19611
 
19252
19612
  /* eslint-disable no-magic-numbers */
@@ -20018,6 +20378,7 @@ const AVATAR_VISUALS = [
20018
20378
  octopus2AvatarVisual,
20019
20379
  octopus3AvatarVisual,
20020
20380
  octopus3dAvatarVisual,
20381
+ octopus3d2AvatarVisual,
20021
20382
  asciiOctopusAvatarVisual,
20022
20383
  minecraftAvatarVisual,
20023
20384
  minecraft2AvatarVisual,
@@ -25593,11 +25954,11 @@ function buildCandidateEncodings(options) {
25593
25954
  });
25594
25955
  }
25595
25956
  /**
25596
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
25957
+ * Prepares one attachment for best-effort text decoding.
25597
25958
  *
25598
- * @private internal utility for shared text decoding
25959
+ * @private function of decodeAttachmentAsText
25599
25960
  */
25600
- function decodeAttachmentAsText(input, options = {}) {
25961
+ function createDecodeAttachmentPreparation(input, options) {
25601
25962
  var _a;
25602
25963
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
25603
25964
  const forceText = options.forceText === true;
@@ -25611,54 +25972,102 @@ function decodeAttachmentAsText(input, options = {}) {
25611
25972
  const inspection = inspectBytes(truncatedBytes);
25612
25973
  const trustedTextMime = isTrustedTextMimeType(mimeType);
25613
25974
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
25975
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
25614
25976
  if (isTruncated) {
25615
25977
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
25616
25978
  }
25617
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
25618
- if (shouldTreatAsBinary && !forceText) {
25619
- warnings.push('File content looks binary, so text decoding was skipped.');
25620
- return {
25621
- text: '',
25622
- encodingUsed: 'binary',
25623
- confidence: 1,
25624
- warnings,
25625
- wasBinary: true,
25626
- isTruncated,
25627
- };
25979
+ return {
25980
+ warnings,
25981
+ charset,
25982
+ bom,
25983
+ inspection,
25984
+ truncatedBytes,
25985
+ isTruncated,
25986
+ forceText,
25987
+ shouldTreatAsBinary,
25988
+ };
25989
+ }
25990
+ /**
25991
+ * Returns an early result when the attachment should stay classified as binary.
25992
+ *
25993
+ * @private function of decodeAttachmentAsText
25994
+ */
25995
+ function createBinaryDecodeResult(preparation) {
25996
+ if (!preparation.shouldTreatAsBinary) {
25997
+ return null;
25628
25998
  }
25629
- if (shouldTreatAsBinary && forceText) {
25630
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
25999
+ if (preparation.forceText) {
26000
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
26001
+ return null;
25631
26002
  }
25632
- if (charset && !isSupportedEncoding(charset)) {
25633
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
26003
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
26004
+ return {
26005
+ text: '',
26006
+ encodingUsed: 'binary',
26007
+ confidence: 1,
26008
+ warnings: preparation.warnings,
26009
+ wasBinary: true,
26010
+ isTruncated: preparation.isTruncated,
26011
+ };
26012
+ }
26013
+ /**
26014
+ * Warns when the declared charset cannot be used by the runtime decoder.
26015
+ *
26016
+ * @private function of decodeAttachmentAsText
26017
+ */
26018
+ function addUnsupportedCharsetWarning(preparation) {
26019
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
26020
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
25634
26021
  }
25635
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
25636
- const candidates = buildCandidateEncodings({
25637
- mimeType,
25638
- charset: charset && isSupportedEncoding(charset) ? charset : null,
25639
- bom,
25640
- inspection,
25641
- });
25642
- const decodedCandidates = candidates
26022
+ }
26023
+ /**
26024
+ * Returns the byte slice that should actually be decoded as text.
26025
+ *
26026
+ * @private function of decodeAttachmentAsText
26027
+ */
26028
+ function getBytesToDecode(preparation) {
26029
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
26030
+ }
26031
+ /**
26032
+ * Decodes all candidate encodings and sorts the successful results by score.
26033
+ *
26034
+ * @private function of decodeAttachmentAsText
26035
+ */
26036
+ function decodeAttachmentCandidates(preparation) {
26037
+ return buildCandidateEncodings({
26038
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
26039
+ bom: preparation.bom,
26040
+ inspection: preparation.inspection,
26041
+ })
25643
26042
  .map(({ encoding, source }) => {
25644
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
26043
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
25645
26044
  return decoded ? { ...decoded, source } : null;
25646
26045
  })
25647
26046
  .filter((candidate) => candidate !== null)
25648
26047
  .sort((left, right) => left.score - right.score);
25649
- const bestCandidate = decodedCandidates[0];
25650
- if (!bestCandidate) {
25651
- warnings.push('No supported text decoder was available.');
25652
- return {
25653
- text: '',
25654
- encodingUsed: 'binary',
25655
- confidence: 0,
25656
- warnings,
25657
- wasBinary: true,
25658
- isTruncated,
25659
- };
25660
- }
25661
- const secondBestCandidate = decodedCandidates[1];
26048
+ }
26049
+ /**
26050
+ * Returns the fallback result used when no text decoder could be applied.
26051
+ *
26052
+ * @private function of decodeAttachmentAsText
26053
+ */
26054
+ function createNoDecoderAvailableResult(preparation) {
26055
+ preparation.warnings.push('No supported text decoder was available.');
26056
+ return {
26057
+ text: '',
26058
+ encodingUsed: 'binary',
26059
+ confidence: 0,
26060
+ warnings: preparation.warnings,
26061
+ wasBinary: true,
26062
+ isTruncated: preparation.isTruncated,
26063
+ };
26064
+ }
26065
+ /**
26066
+ * Estimates confidence for the winning decoded text candidate.
26067
+ *
26068
+ * @private function of decodeAttachmentAsText
26069
+ */
26070
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
25662
26071
  const baseConfidence = bestCandidate.source === 'bom'
25663
26072
  ? 1
25664
26073
  : bestCandidate.source === 'charset'
@@ -25671,27 +26080,62 @@ function decodeAttachmentAsText(input, options = {}) {
25671
26080
  ? 0.82
25672
26081
  : 0.62;
25673
26082
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
25674
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
26083
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
26084
+ }
26085
+ /**
26086
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
26087
+ *
26088
+ * @private function of decodeAttachmentAsText
26089
+ */
26090
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
25675
26091
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
25676
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
26092
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
25677
26093
  }
25678
26094
  if (bestCandidate.source === 'heuristic' &&
25679
26095
  bestCandidate.encoding === 'utf-8' &&
25680
26096
  bestCandidate.replacementCount > 0) {
25681
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
26097
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
25682
26098
  }
25683
26099
  if (confidence < 0.6) {
25684
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
26100
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
25685
26101
  }
26102
+ }
26103
+ /**
26104
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
26105
+ *
26106
+ * @private function of decodeAttachmentAsText
26107
+ */
26108
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
25686
26109
  return {
25687
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
26110
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
25688
26111
  encodingUsed: bestCandidate.encoding,
25689
26112
  confidence,
25690
- warnings,
26113
+ warnings: preparation.warnings,
25691
26114
  wasBinary: false,
25692
- isTruncated,
26115
+ isTruncated: preparation.isTruncated,
25693
26116
  };
25694
26117
  }
26118
+ /**
26119
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
26120
+ *
26121
+ * @private internal utility for shared text decoding
26122
+ */
26123
+ function decodeAttachmentAsText(input, options = {}) {
26124
+ const preparation = createDecodeAttachmentPreparation(input, options);
26125
+ const binaryResult = createBinaryDecodeResult(preparation);
26126
+ if (binaryResult) {
26127
+ return binaryResult;
26128
+ }
26129
+ addUnsupportedCharsetWarning(preparation);
26130
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
26131
+ const bestCandidate = decodedCandidates[0];
26132
+ if (!bestCandidate) {
26133
+ return createNoDecoderAvailableResult(preparation);
26134
+ }
26135
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
26136
+ addDecodeWarnings(bestCandidate, confidence, preparation);
26137
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
26138
+ }
25695
26139
 
25696
26140
  /**
25697
26141
  * Base GitHub API URL.
@@ -30237,10 +30681,7 @@ function createAvailableProviderMessage(llmToolStatus, index, env) {
30237
30681
  * @private internal function of `$registeredLlmToolsMessage`
30238
30682
  */
30239
30683
  function createProviderStatusMessages(llmToolStatus, env) {
30240
- return [
30241
- createInstallationStatusMessage(llmToolStatus),
30242
- createConfigurationStatusMessage(llmToolStatus, env),
30243
- ];
30684
+ return [createInstallationStatusMessage(llmToolStatus), createConfigurationStatusMessage(llmToolStatus, env)];
30244
30685
  }
30245
30686
  /**
30246
30687
  * Creates the installation-status sentence for one provider.
@@ -37756,7 +38197,10 @@ class OpenAiCompatibleModelCatalog {
37756
38197
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
37757
38198
 
37758
38199
  Available models:
37759
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
38200
+ ${block(this.options
38201
+ .getHardcodedModels()
38202
+ .map(({ modelName }) => `- "${modelName}"`)
38203
+ .join('\n'))}
37760
38204
 
37761
38205
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
37762
38206
 
@@ -38110,7 +38554,8 @@ class OpenAiCompatibleNonChatPromptCaller {
38110
38554
  };
38111
38555
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
38112
38556
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
38113
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
38557
+ rawPromptContent +=
38558
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
38114
38559
  }
38115
38560
  const rawRequest = {
38116
38561
  ...modelSettings,
@@ -38215,7 +38660,9 @@ class OpenAiCompatibleRequestManager {
38215
38660
  * Schedules one request through the shared limiter and retry policy.
38216
38661
  */
38217
38662
  async executeRateLimitedRequest(requestFn) {
38218
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
38663
+ return this.limiter
38664
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
38665
+ .catch((error) => {
38219
38666
  assertsError(error);
38220
38667
  if (this.options.isVerbose) {
38221
38668
  console.info(colors.bgRed('error'), error);
@@ -39422,7 +39869,9 @@ class OpenAiVectorStoreFileBatchPoller {
39422
39869
  pollingState.lastProgressAtMs = nowMs;
39423
39870
  pollingState.lastProgressKey = progressKey;
39424
39871
  }
39425
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
39872
+ if (this.options.isVerbose &&
39873
+ (statusCountsKey !== pollingState.lastCountsKey ||
39874
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
39426
39875
  console.info('[🤰]', 'Vector store file batch status', {
39427
39876
  vectorStoreId,
39428
39877
  batchId,