@promptbook/node 0.112.0-73 → 0.112.0-79

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/umd/index.umd.js CHANGED
@@ -48,7 +48,7 @@
48
48
  * @generated
49
49
  * @see https://github.com/webgptorg/promptbook
50
50
  */
51
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
51
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
52
52
  /**
53
53
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
54
54
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -298,6 +298,111 @@
298
298
  }
299
299
  }
300
300
 
301
+ /**
302
+ * Shared immutable channel storage and serialization helpers for `Color`.
303
+ *
304
+ * @private base class of Color
305
+ */
306
+ class ColorValue {
307
+ constructor(red, green, blue, alpha = 255) {
308
+ this.red = red;
309
+ this.green = green;
310
+ this.blue = blue;
311
+ this.alpha = alpha;
312
+ checkChannelValue('Red', red);
313
+ checkChannelValue('Green', green);
314
+ checkChannelValue('Blue', blue);
315
+ checkChannelValue('Alpha', alpha);
316
+ }
317
+ /**
318
+ * Shortcut for `red` property
319
+ * Number from 0 to 255
320
+ * @alias red
321
+ */
322
+ get r() {
323
+ return this.red;
324
+ }
325
+ /**
326
+ * Shortcut for `green` property
327
+ * Number from 0 to 255
328
+ * @alias green
329
+ */
330
+ get g() {
331
+ return this.green;
332
+ }
333
+ /**
334
+ * Shortcut for `blue` property
335
+ * Number from 0 to 255
336
+ * @alias blue
337
+ */
338
+ get b() {
339
+ return this.blue;
340
+ }
341
+ /**
342
+ * Shortcut for `alpha` property
343
+ * Number from 0 (transparent) to 255 (opaque)
344
+ * @alias alpha
345
+ */
346
+ get a() {
347
+ return this.alpha;
348
+ }
349
+ /**
350
+ * Shortcut for `alpha` property
351
+ * Number from 0 (transparent) to 255 (opaque)
352
+ * @alias alpha
353
+ */
354
+ get opacity() {
355
+ return this.alpha;
356
+ }
357
+ /**
358
+ * Shortcut for 1-`alpha` property
359
+ */
360
+ get transparency() {
361
+ return 255 - this.alpha;
362
+ }
363
+ clone() {
364
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
365
+ }
366
+ toString() {
367
+ return this.toHex();
368
+ }
369
+ toHex() {
370
+ if (this.alpha === 255) {
371
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
372
+ .toString(16)
373
+ .padStart(2, '0')}`;
374
+ }
375
+ else {
376
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
377
+ .toString(16)
378
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
379
+ }
380
+ }
381
+ toRgb() {
382
+ if (this.alpha === 255) {
383
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
384
+ }
385
+ else {
386
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
387
+ }
388
+ }
389
+ toHsl() {
390
+ throw new Error(`Getting HSL is not implemented`);
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Checks if the given value is a valid hex color string
396
+ *
397
+ * @param value - value to check
398
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
399
+ *
400
+ * @private function of Color
401
+ */
402
+ function isHexColorString(value) {
403
+ 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));
404
+ }
405
+
301
406
  /**
302
407
  * Constant for short hex lengths.
303
408
  */
@@ -509,16 +614,53 @@
509
614
 
510
615
  /**
511
616
  * Pattern matching hsl regex.
617
+ *
618
+ * @private function of Color
512
619
  */
513
620
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
514
621
  /**
515
622
  * Pattern matching RGB regex.
623
+ *
624
+ * @private function of Color
516
625
  */
517
626
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
518
627
  /**
519
628
  * Pattern matching rgba regex.
629
+ *
630
+ * @private function of Color
520
631
  */
521
632
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
633
+ /**
634
+ * Parses a supported color string into RGBA channels.
635
+ *
636
+ * @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`,...
637
+ * @returns RGBA channel values.
638
+ *
639
+ * @private function of Color
640
+ */
641
+ function parseColorString(color) {
642
+ const trimmed = color.trim();
643
+ const cssColor = CSS_COLORS[trimmed];
644
+ if (cssColor) {
645
+ return parseColorString(cssColor);
646
+ }
647
+ else if (isHexColorString(trimmed)) {
648
+ return parseHexColor(trimmed);
649
+ }
650
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
651
+ return parseHslColor(trimmed);
652
+ }
653
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
654
+ return parseRgbColor(trimmed);
655
+ }
656
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
657
+ return parseRgbaColor(trimmed);
658
+ }
659
+ else {
660
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
661
+ }
662
+ }
663
+
522
664
  /**
523
665
  * Color object represents an RGB color with alpha channel
524
666
  *
@@ -526,7 +668,7 @@
526
668
  *
527
669
  * @public exported from `@promptbook/color`
528
670
  */
529
- class Color {
671
+ class Color extends ColorValue {
530
672
  /**
531
673
  * Creates a new Color instance from miscellaneous formats
532
674
  * - It can receive Color instance and just return the same instance
@@ -599,25 +741,7 @@
599
741
  * @returns Color object
600
742
  */
601
743
  static fromString(color) {
602
- const trimmed = color.trim();
603
- if (CSS_COLORS[trimmed]) {
604
- return Color.fromString(CSS_COLORS[trimmed]);
605
- }
606
- else if (Color.isHexColorString(trimmed)) {
607
- return Color.fromHex(trimmed);
608
- }
609
- if (HSL_REGEX_PATTERN.test(trimmed)) {
610
- return Color.fromHsl(trimmed);
611
- }
612
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
613
- return Color.fromRgbString(trimmed);
614
- }
615
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
616
- return Color.fromRgbaString(trimmed);
617
- }
618
- else {
619
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
620
- }
744
+ return Color.fromColorChannels(parseColorString(color));
621
745
  }
622
746
  /**
623
747
  * Gets common color
@@ -647,8 +771,7 @@
647
771
  * @returns Color object
648
772
  */
649
773
  static fromHex(hex) {
650
- const { red, green, blue, alpha } = parseHexColor(hex);
651
- return take(new Color(red, green, blue, alpha));
774
+ return Color.fromColorChannels(parseHexColor(hex));
652
775
  }
653
776
  /**
654
777
  * Creates a new Color instance from color in hsl format
@@ -657,8 +780,7 @@
657
780
  * @returns Color object
658
781
  */
659
782
  static fromHsl(hsl) {
660
- const { red, green, blue, alpha } = parseHslColor(hsl);
661
- return take(new Color(red, green, blue, alpha));
783
+ return Color.fromColorChannels(parseHslColor(hsl));
662
784
  }
663
785
  /**
664
786
  * Creates a new Color instance from color in rgb format
@@ -667,8 +789,7 @@
667
789
  * @returns Color object
668
790
  */
669
791
  static fromRgbString(rgb) {
670
- const { red, green, blue, alpha } = parseRgbColor(rgb);
671
- return take(new Color(red, green, blue, alpha));
792
+ return Color.fromColorChannels(parseRgbColor(rgb));
672
793
  }
673
794
  /**
674
795
  * Creates a new Color instance from color in rbga format
@@ -677,8 +798,7 @@
677
798
  * @returns Color object
678
799
  */
679
800
  static fromRgbaString(rgba) {
680
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
681
- return take(new Color(red, green, blue, alpha));
801
+ return Color.fromColorChannels(parseRgbaColor(rgba));
682
802
  }
683
803
  /**
684
804
  * Creates a new Color for color channels values
@@ -690,7 +810,7 @@
690
810
  * @returns Color object
691
811
  */
692
812
  static fromValues(red, green, blue, alpha = 255) {
693
- return take(new Color(red, green, blue, alpha));
813
+ return Color.fromColorChannels({ red, green, blue, alpha });
694
814
  }
695
815
  /**
696
816
  * Checks if the given value is a valid Color object.
@@ -723,8 +843,7 @@
723
843
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
724
844
  */
725
845
  static isHexColorString(value) {
726
- return (typeof value === 'string' &&
727
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
846
+ return isHexColorString(value);
728
847
  }
729
848
  /**
730
849
  * Creates new Color object
@@ -737,89 +856,13 @@
737
856
  * @param alpha number from 0 (transparent) to 255 (opaque)
738
857
  */
739
858
  constructor(red, green, blue, alpha = 255) {
740
- this.red = red;
741
- this.green = green;
742
- this.blue = blue;
743
- this.alpha = alpha;
744
- checkChannelValue('Red', red);
745
- checkChannelValue('Green', green);
746
- checkChannelValue('Blue', blue);
747
- checkChannelValue('Alpha', alpha);
748
- }
749
- /**
750
- * Shortcut for `red` property
751
- * Number from 0 to 255
752
- * @alias red
753
- */
754
- get r() {
755
- return this.red;
756
- }
757
- /**
758
- * Shortcut for `green` property
759
- * Number from 0 to 255
760
- * @alias green
761
- */
762
- get g() {
763
- return this.green;
764
- }
765
- /**
766
- * Shortcut for `blue` property
767
- * Number from 0 to 255
768
- * @alias blue
769
- */
770
- get b() {
771
- return this.blue;
772
- }
773
- /**
774
- * Shortcut for `alpha` property
775
- * Number from 0 (transparent) to 255 (opaque)
776
- * @alias alpha
777
- */
778
- get a() {
779
- return this.alpha;
780
- }
781
- /**
782
- * Shortcut for `alpha` property
783
- * Number from 0 (transparent) to 255 (opaque)
784
- * @alias alpha
785
- */
786
- get opacity() {
787
- return this.alpha;
788
- }
789
- /**
790
- * Shortcut for 1-`alpha` property
791
- */
792
- get transparency() {
793
- return 255 - this.alpha;
794
- }
795
- clone() {
796
- return take(new Color(this.red, this.green, this.blue, this.alpha));
797
- }
798
- toString() {
799
- return this.toHex();
800
- }
801
- toHex() {
802
- if (this.alpha === 255) {
803
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
804
- .toString(16)
805
- .padStart(2, '0')}`;
806
- }
807
- else {
808
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
809
- .toString(16)
810
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
811
- }
859
+ super(red, green, blue, alpha);
812
860
  }
813
- toRgb() {
814
- if (this.alpha === 255) {
815
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
816
- }
817
- else {
818
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
819
- }
861
+ createColor(red, green, blue, alpha) {
862
+ return new Color(red, green, blue, alpha);
820
863
  }
821
- toHsl() {
822
- throw new Error(`Getting HSL is not implemented`);
864
+ static fromColorChannels({ red, green, blue, alpha }) {
865
+ return take(new Color(red, green, blue, alpha));
823
866
  }
824
867
  }
825
868
 
@@ -1504,120 +1547,183 @@
1504
1547
  * @public exported from `@promptbook/utils`
1505
1548
  */
1506
1549
  function checkSerializableAsJson(options) {
1507
- const { value, name, message } = options;
1550
+ checkSerializableValue(options);
1551
+ }
1552
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1553
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1554
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1555
+ /**
1556
+ * Checks one value and dispatches to the appropriate specialized validator.
1557
+ *
1558
+ * @private function of `checkSerializableAsJson`
1559
+ */
1560
+ function checkSerializableValue(options) {
1561
+ const { value } = options;
1562
+ if (isSerializablePrimitive(value)) {
1563
+ return;
1564
+ }
1508
1565
  if (value === undefined) {
1509
- throw new UnexpectedError(`${name} is undefined`);
1566
+ throw new UnexpectedError(`${options.name} is undefined`);
1510
1567
  }
1511
- else if (value === null) {
1512
- return;
1568
+ if (typeof value === 'symbol') {
1569
+ throw new UnexpectedError(`${options.name} is symbol`);
1513
1570
  }
1514
- else if (typeof value === 'boolean') {
1515
- return;
1571
+ if (typeof value === 'function') {
1572
+ throw new UnexpectedError(`${options.name} is function`);
1516
1573
  }
1517
- else if (typeof value === 'number' && !isNaN(value)) {
1574
+ if (Array.isArray(value)) {
1575
+ checkSerializableArray(options, value);
1518
1576
  return;
1519
1577
  }
1520
- else if (typeof value === 'string') {
1578
+ if (value !== null && typeof value === 'object') {
1579
+ checkSerializableObject(options, value);
1521
1580
  return;
1522
1581
  }
1523
- else if (typeof value === 'symbol') {
1524
- throw new UnexpectedError(`${name} is symbol`);
1525
- }
1526
- else if (typeof value === 'function') {
1527
- throw new UnexpectedError(`${name} is function`);
1528
- }
1529
- else if (typeof value === 'object' && Array.isArray(value)) {
1530
- for (let i = 0; i < value.length; i++) {
1531
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1532
- }
1582
+ throwUnknownTypeError(options);
1583
+ }
1584
+ /**
1585
+ * Checks the primitive values that are directly JSON serializable.
1586
+ *
1587
+ * @private function of `checkSerializableAsJson`
1588
+ */
1589
+ function isSerializablePrimitive(value) {
1590
+ return (value === null ||
1591
+ typeof value === 'boolean' ||
1592
+ (typeof value === 'number' && !isNaN(value)) ||
1593
+ typeof value === 'string');
1594
+ }
1595
+ /**
1596
+ * Recursively checks JSON array items.
1597
+ *
1598
+ * @private function of `checkSerializableAsJson`
1599
+ */
1600
+ function checkSerializableArray(context, arrayValue) {
1601
+ for (let index = 0; index < arrayValue.length; index++) {
1602
+ checkSerializableAsJson({
1603
+ ...context,
1604
+ name: `${context.name}[${index}]`,
1605
+ value: arrayValue[index],
1606
+ });
1533
1607
  }
1534
- else if (typeof value === 'object') {
1535
- if (value instanceof Date) {
1536
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1537
- \`${name}\` is Date
1608
+ }
1609
+ /**
1610
+ * Checks object-like values and dispatches special unsupported built-ins.
1611
+ *
1612
+ * @private function of `checkSerializableAsJson`
1613
+ */
1614
+ function checkSerializableObject(context, objectValue) {
1615
+ checkUnsupportedObjectType(context, objectValue);
1616
+ checkSerializableObjectEntries(context, objectValue);
1617
+ assertJsonStringificationSucceeds(context, objectValue);
1618
+ }
1619
+ /**
1620
+ * Rejects built-in objects that must be converted before JSON serialization.
1621
+ *
1622
+ * @private function of `checkSerializableAsJson`
1623
+ */
1624
+ function checkUnsupportedObjectType(context, objectValue) {
1625
+ if (objectValue instanceof Date) {
1626
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1627
+ \`${context.name}\` is Date
1538
1628
 
1539
- Use \`string_date_iso8601\` instead
1629
+ Use \`string_date_iso8601\` instead
1540
1630
 
1541
- Additional message for \`${name}\`:
1542
- ${block(message || '(nothing)')}
1543
- `));
1544
- }
1545
- else if (value instanceof Map) {
1546
- throw new UnexpectedError(`${name} is Map`);
1547
- }
1548
- else if (value instanceof Set) {
1549
- throw new UnexpectedError(`${name} is Set`);
1550
- }
1551
- else if (value instanceof RegExp) {
1552
- throw new UnexpectedError(`${name} is RegExp`);
1553
- }
1554
- else if (value instanceof Error) {
1555
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1556
- \`${name}\` is unserialized Error
1631
+ Additional message for \`${context.name}\`:
1632
+ ${block(context.message || '(nothing)')}
1633
+ `));
1634
+ }
1635
+ if (objectValue instanceof Map) {
1636
+ throw new UnexpectedError(`${context.name} is Map`);
1637
+ }
1638
+ if (objectValue instanceof Set) {
1639
+ throw new UnexpectedError(`${context.name} is Set`);
1640
+ }
1641
+ if (objectValue instanceof RegExp) {
1642
+ throw new UnexpectedError(`${context.name} is RegExp`);
1643
+ }
1644
+ if (objectValue instanceof Error) {
1645
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1646
+ \`${context.name}\` is unserialized Error
1557
1647
 
1558
- Use function \`serializeError\`
1648
+ Use function \`serializeError\`
1559
1649
 
1560
- Additional message for \`${name}\`:
1561
- ${block(message || '(nothing)')}
1650
+ Additional message for \`${context.name}\`:
1651
+ ${block(context.message || '(nothing)')}
1562
1652
 
1563
- `));
1653
+ `));
1654
+ }
1655
+ }
1656
+ /**
1657
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1658
+ *
1659
+ * @private function of `checkSerializableAsJson`
1660
+ */
1661
+ function checkSerializableObjectEntries(context, objectValue) {
1662
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1663
+ if (subValue === undefined) {
1664
+ // Note: undefined in object is serializable - it is just omitted
1665
+ continue;
1564
1666
  }
1565
- else {
1566
- for (const [subName, subValue] of Object.entries(value)) {
1567
- if (subValue === undefined) {
1568
- // Note: undefined in object is serializable - it is just omitted
1569
- continue;
1570
- }
1571
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1572
- }
1573
- try {
1574
- JSON.stringify(value); // <- TODO: [0]
1575
- }
1576
- catch (error) {
1577
- assertsError(error);
1578
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1579
- \`${name}\` is not serializable
1667
+ checkSerializableAsJson({
1668
+ ...context,
1669
+ name: `${context.name}.${subName}`,
1670
+ value: subValue,
1671
+ });
1672
+ }
1673
+ }
1674
+ /**
1675
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1676
+ *
1677
+ * @private function of `checkSerializableAsJson`
1678
+ */
1679
+ function assertJsonStringificationSucceeds(context, objectValue) {
1680
+ try {
1681
+ JSON.stringify(objectValue); // <- TODO: [0]
1682
+ }
1683
+ catch (error) {
1684
+ assertsError(error);
1685
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1686
+ \`${context.name}\` is not serializable
1580
1687
 
1581
- ${block(error.stack || error.message)}
1688
+ ${block(error.stack || error.message)}
1582
1689
 
1583
- Additional message for \`${name}\`:
1584
- ${block(message || '(nothing)')}
1585
- `));
1690
+ Additional message for \`${context.name}\`:
1691
+ ${block(context.message || '(nothing)')}
1692
+ `));
1693
+ }
1694
+ /*
1695
+ TODO: [0] Is there some more elegant way to check circular references?
1696
+ const seen = new Set();
1697
+ const stack = [{ value }];
1698
+ while (stack.length > 0) {
1699
+ const { value } = stack.pop()!;
1700
+ if (typeof value === 'object' && value !== null) {
1701
+ if (seen.has(value)) {
1702
+ throw new UnexpectedError(`${name} has circular reference`);
1586
1703
  }
1587
- /*
1588
- TODO: [0] Is there some more elegant way to check circular references?
1589
- const seen = new Set();
1590
- const stack = [{ value }];
1591
- while (stack.length > 0) {
1592
- const { value } = stack.pop()!;
1593
- if (typeof value === 'object' && value !== null) {
1594
- if (seen.has(value)) {
1595
- throw new UnexpectedError(`${name} has circular reference`);
1596
- }
1597
- seen.add(value);
1598
- if (Array.isArray(value)) {
1599
- stack.push(...value.map((value) => ({ value })));
1600
- } else {
1601
- stack.push(...Object.values(value).map((value) => ({ value })));
1602
- }
1603
- }
1704
+ seen.add(value);
1705
+ if (Array.isArray(value)) {
1706
+ stack.push(...value.map((value) => ({ value })));
1707
+ } else {
1708
+ stack.push(...Object.values(value).map((value) => ({ value })));
1604
1709
  }
1605
- */
1606
- return;
1607
1710
  }
1608
1711
  }
1609
- else {
1610
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1611
- \`${name}\` is unknown type
1712
+ */
1713
+ }
1714
+ /**
1715
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
1716
+ *
1717
+ * @private function of `checkSerializableAsJson`
1718
+ */
1719
+ function throwUnknownTypeError(context) {
1720
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1721
+ \`${context.name}\` is unknown type
1612
1722
 
1613
- Additional message for \`${name}\`:
1614
- ${block(message || '(nothing)')}
1615
- `));
1616
- }
1723
+ Additional message for \`${context.name}\`:
1724
+ ${block(context.message || '(nothing)')}
1725
+ `));
1617
1726
  }
1618
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1619
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1620
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1621
1727
 
1622
1728
  /**
1623
1729
  * Creates a deep clone of the given object
@@ -2277,8 +2383,7 @@
2277
2383
  * @private internal utility of `validatePipeline`
2278
2384
  */
2279
2385
  function validateTaskSupportsJokers(task, pipelineIdentification) {
2280
- if (task.format ||
2281
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2386
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2282
2387
  return;
2283
2388
  }
2284
2389
  throw new PipelineLogicError(_spaceTrim.spaceTrim((block) => `
@@ -2843,7 +2948,7 @@
2843
2948
  */
2844
2949
  function createPostprocessingCommands(task) {
2845
2950
  var _a;
2846
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
2951
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
2847
2952
  }
2848
2953
  /**
2849
2954
  * Collects expectation commands.
@@ -5959,9 +6064,7 @@
5959
6064
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
5960
6065
 
5961
6066
  Result:
5962
- ${block(failure.result === null
5963
- ? 'null'
5964
- : quoteMultilineText(_spaceTrim.spaceTrim(failure.result)))}
6067
+ ${block(failure.result === null ? 'null' : quoteMultilineText(_spaceTrim.spaceTrim(failure.result)))}
5965
6068
  `;
5966
6069
  }))
5967
6070
  .join('\n\n---\n\n');
@@ -17468,7 +17571,7 @@
17468
17571
  *
17469
17572
  * @private helper of `minecraft2AvatarVisual`
17470
17573
  */
17471
- const LIGHT_DIRECTION$1 = normalizeVector3({
17574
+ const LIGHT_DIRECTION$2 = normalizeVector3({
17472
17575
  x: 0.4,
17473
17576
  y: -0.65,
17474
17577
  z: 0.92,
@@ -17680,7 +17783,7 @@
17680
17783
  corners: projectedCorners,
17681
17784
  texture: faceDefinition.texture,
17682
17785
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
17683
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
17786
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
17684
17787
  outlineColor: cuboid.outlineColor,
17685
17788
  };
17686
17789
  });
@@ -18740,13 +18843,138 @@
18740
18843
  context.restore();
18741
18844
  }
18742
18845
 
18846
+ /* eslint-disable no-magic-numbers */
18847
+ /**
18848
+ * Draws one projected eye on a rotated octopus surface.
18849
+ *
18850
+ * @private helper of the 3D octopus avatar visuals
18851
+ */
18852
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
18853
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
18854
+ if (centerScenePoint.z <= center.z) {
18855
+ return;
18856
+ }
18857
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
18858
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
18859
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
18860
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
18861
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
18862
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
18863
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
18864
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
18865
+ return;
18866
+ }
18867
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
18868
+ radiusX: projectedRadiusX,
18869
+ radiusY: projectedRadiusY,
18870
+ timeMs,
18871
+ phase,
18872
+ interaction,
18873
+ });
18874
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
18875
+ context.save();
18876
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
18877
+ context.rotate(rotation);
18878
+ context.beginPath();
18879
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
18880
+ context.fillStyle = '#f8fbff';
18881
+ context.fill();
18882
+ context.clip();
18883
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
18884
+ irisGradient.addColorStop(0, palette.highlight);
18885
+ irisGradient.addColorStop(0.56, palette.secondary);
18886
+ irisGradient.addColorStop(1, palette.shadow);
18887
+ context.beginPath();
18888
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
18889
+ context.fillStyle = irisGradient;
18890
+ context.fill();
18891
+ context.beginPath();
18892
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
18893
+ context.fillStyle = palette.ink;
18894
+ context.fill();
18895
+ context.beginPath();
18896
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
18897
+ context.fillStyle = '#ffffff';
18898
+ context.fill();
18899
+ context.restore();
18900
+ context.save();
18901
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
18902
+ context.rotate(rotation);
18903
+ context.beginPath();
18904
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
18905
+ context.strokeStyle = `${palette.shadow}cc`;
18906
+ context.lineWidth = projectedRadiusX * 0.16;
18907
+ context.stroke();
18908
+ context.beginPath();
18909
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
18910
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
18911
+ context.strokeStyle = `${palette.shadow}73`;
18912
+ context.lineWidth = projectedRadiusX * 0.14;
18913
+ context.lineCap = 'round';
18914
+ context.stroke();
18915
+ if (eyeStyle.lowerLidOpacity > 0) {
18916
+ context.beginPath();
18917
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
18918
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
18919
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
18920
+ context.lineWidth = projectedRadiusX * 0.08;
18921
+ context.lineCap = 'round';
18922
+ context.stroke();
18923
+ }
18924
+ context.restore();
18925
+ }
18926
+ /**
18927
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
18928
+ *
18929
+ * @private helper of the 3D octopus avatar visuals
18930
+ */
18931
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
18932
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
18933
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
18934
+ return;
18935
+ }
18936
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
18937
+ context.beginPath();
18938
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
18939
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
18940
+ context.strokeStyle = `${palette.ink}b8`;
18941
+ context.lineWidth = Math.max(1.1, size * 0.009);
18942
+ context.lineCap = 'round';
18943
+ context.stroke();
18944
+ }
18945
+ /**
18946
+ * Draws one filled projected quad.
18947
+ *
18948
+ * @private helper of the 3D octopus avatar visuals
18949
+ */
18950
+ function drawProjectedQuad(context, corners, fillStyle) {
18951
+ context.beginPath();
18952
+ context.moveTo(corners[0].x, corners[0].y);
18953
+ context.lineTo(corners[1].x, corners[1].y);
18954
+ context.lineTo(corners[2].x, corners[2].y);
18955
+ context.lineTo(corners[3].x, corners[3].y);
18956
+ context.closePath();
18957
+ context.fillStyle = fillStyle;
18958
+ context.fill();
18959
+ }
18960
+ /**
18961
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
18962
+ *
18963
+ * @private helper of the 3D octopus avatar visuals
18964
+ */
18965
+ function formatAlphaHex(opacity) {
18966
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
18967
+ .toString(16)
18968
+ .padStart(2, '0');
18969
+ }
18970
+
18743
18971
  /* eslint-disable no-magic-numbers */
18744
18972
  /**
18745
18973
  * Light direction used by the organic 3D octopus shading.
18746
18974
  *
18747
18975
  * @private helper of `octopus3dAvatarVisual`
18748
18976
  */
18749
- const LIGHT_DIRECTION = normalizeVector3({
18977
+ const LIGHT_DIRECTION$1 = normalizeVector3({
18750
18978
  x: 0.48,
18751
18979
  y: -0.62,
18752
18980
  z: 0.94,
@@ -18859,17 +19087,17 @@
18859
19087
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
18860
19088
  drawTentacleStroke(context, tentacleStroke, palette);
18861
19089
  }
18862
- drawProjectedEye(context, {
19090
+ drawProjectedOrganicEye(context, {
18863
19091
  x: -faceEyeSpacing,
18864
19092
  y: faceEyeYOffset,
18865
19093
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
18866
19094
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
18867
- drawProjectedEye(context, {
19095
+ drawProjectedOrganicEye(context, {
18868
19096
  x: faceEyeSpacing,
18869
19097
  y: faceEyeYOffset,
18870
19098
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
18871
19099
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
18872
- drawProjectedMouth(context, [
19100
+ drawProjectedOrganicMouth(context, [
18873
19101
  {
18874
19102
  x: -mouthHalfWidth,
18875
19103
  y: mouthY,
@@ -18956,7 +19184,7 @@
18956
19184
  corners: projectedCorners,
18957
19185
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
18958
19186
  transformedCorners.length,
18959
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
19187
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
18960
19188
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
18961
19189
  outlineColor,
18962
19190
  });
@@ -19138,128 +19366,260 @@
19138
19366
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
19139
19367
  return Math.sqrt(remainingDepthRatio) * radiusZ;
19140
19368
  }
19369
+
19370
+ /* eslint-disable no-magic-numbers */
19141
19371
  /**
19142
- * Draws one projected eye on the turned octopus mantle.
19372
+ * Light direction used by the single-mesh octopus shading.
19143
19373
  *
19144
- * @private helper of `octopus3dAvatarVisual`
19374
+ * @private helper of `octopus3d2AvatarVisual`
19145
19375
  */
19146
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
19147
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
19148
- if (centerScenePoint.z <= center.z) {
19149
- return;
19150
- }
19151
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
19152
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
19153
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
19154
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
19155
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
19156
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
19157
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
19158
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
19159
- return;
19160
- }
19161
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
19162
- radiusX: projectedRadiusX,
19163
- radiusY: projectedRadiusY,
19164
- timeMs,
19165
- phase,
19166
- interaction,
19167
- });
19168
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
19376
+ const LIGHT_DIRECTION = normalizeVector3({
19377
+ x: 0.38,
19378
+ y: -0.6,
19379
+ z: 0.98,
19380
+ });
19381
+ /**
19382
+ * Octopus 3D 2 avatar visual.
19383
+ *
19384
+ * @private built-in avatar visual
19385
+ */
19386
+ const octopus3d2AvatarVisual = {
19387
+ id: 'octopus3d2',
19388
+ title: 'Octopus 3D 2',
19389
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
19390
+ isAnimated: true,
19391
+ supportsPointerTracking: true,
19392
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
19393
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
19394
+ const animationRandom = createRandom('octopus3d2-animation-profile');
19395
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
19396
+ const animationPhase = animationRandom() * Math.PI * 2;
19397
+ const sceneCenterX = size * 0.5;
19398
+ const sceneCenterY = size * 0.575;
19399
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
19400
+ const meshCenter = {
19401
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
19402
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
19403
+ z: interaction.intensity * size * 0.018,
19404
+ };
19405
+ const rotationY = -0.14 +
19406
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
19407
+ interaction.bodyOffsetX * 0.2 +
19408
+ interaction.gazeX * 0.78;
19409
+ const rotationX = -0.06 +
19410
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
19411
+ interaction.bodyOffsetY * 0.08 -
19412
+ interaction.gazeY * 0.34;
19413
+ const surfaceOptions = {
19414
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
19415
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
19416
+ radiusZ: size *
19417
+ morphologyProfile.body.bodyRadiusRatio *
19418
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
19419
+ morphologyProfile,
19420
+ timeMs,
19421
+ animationPhase,
19422
+ };
19423
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
19424
+ ...surfaceOptions,
19425
+ center: meshCenter,
19426
+ rotationX,
19427
+ rotationY,
19428
+ sceneCenterX,
19429
+ sceneCenterY,
19430
+ size,
19431
+ palette,
19432
+ });
19433
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
19434
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
19435
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
19436
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
19437
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
19438
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
19439
+ drawAvatarFrame(context, size, palette);
19440
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
19441
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
19442
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
19443
+ drawBlobbySurfacePatch(context, surfacePatch);
19444
+ }
19445
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
19446
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
19447
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
19448
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
19449
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
19450
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
19451
+ drawProjectedOrganicMouth(context, [
19452
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
19453
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
19454
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
19455
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
19456
+ },
19457
+ };
19458
+ /**
19459
+ * Draws the deep-water glow behind the continuous octopus mesh.
19460
+ *
19461
+ * @private helper of `octopus3d2AvatarVisual`
19462
+ */
19463
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
19464
+ 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);
19465
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
19466
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
19467
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
19468
+ context.fillStyle = glowGradient;
19469
+ context.fillRect(0, 0, size, size);
19470
+ 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);
19471
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
19472
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
19473
+ context.fillStyle = lowerGradient;
19474
+ context.fillRect(0, 0, size, size);
19475
+ }
19476
+ /**
19477
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
19478
+ *
19479
+ * @private helper of `octopus3d2AvatarVisual`
19480
+ */
19481
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
19169
19482
  context.save();
19170
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
19171
- context.rotate(rotation);
19172
- context.beginPath();
19173
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
19174
- context.fillStyle = '#f8fbff';
19175
- context.fill();
19176
- context.clip();
19177
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
19178
- irisGradient.addColorStop(0, palette.highlight);
19179
- irisGradient.addColorStop(0.56, palette.secondary);
19180
- irisGradient.addColorStop(1, palette.shadow);
19181
- context.beginPath();
19182
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
19183
- context.fillStyle = irisGradient;
19184
- context.fill();
19185
- context.beginPath();
19186
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
19187
- context.fillStyle = palette.ink;
19188
- context.fill();
19483
+ context.fillStyle = `${palette.shadow}66`;
19484
+ context.filter = `blur(${size * 0.024}px)`;
19189
19485
  context.beginPath();
19190
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
19191
- context.fillStyle = '#ffffff';
19486
+ 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);
19192
19487
  context.fill();
19193
19488
  context.restore();
19194
- context.save();
19195
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
19196
- context.rotate(rotation);
19197
- context.beginPath();
19198
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
19199
- context.strokeStyle = `${palette.shadow}cc`;
19200
- context.lineWidth = projectedRadiusX * 0.16;
19201
- context.stroke();
19202
- context.beginPath();
19203
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
19204
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
19205
- context.strokeStyle = `${palette.shadow}73`;
19206
- context.lineWidth = projectedRadiusX * 0.14;
19207
- context.lineCap = 'round';
19208
- context.stroke();
19209
- if (eyeStyle.lowerLidOpacity > 0) {
19210
- context.beginPath();
19211
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
19212
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
19213
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
19214
- context.lineWidth = projectedRadiusX * 0.08;
19215
- context.lineCap = 'round';
19216
- context.stroke();
19217
- }
19218
- context.restore();
19219
19489
  }
19220
19490
  /**
19221
- * Draws a subtle projected mouth arc across the front of the mantle.
19491
+ * Resolves all visible projected patches for the single blobby octopus mesh.
19222
19492
  *
19223
- * @private helper of `octopus3dAvatarVisual`
19493
+ * @private helper of `octopus3d2AvatarVisual`
19224
19494
  */
19225
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
19226
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
19227
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
19228
- return;
19495
+ function resolveVisibleBlobbyOctopusPatches(options) {
19496
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
19497
+ const latitudePatchCount = 12;
19498
+ const longitudePatchCount = 24;
19499
+ const surfacePatches = [];
19500
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
19501
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
19502
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
19503
+ const centerLatitude = (startLatitude + endLatitude) / 2;
19504
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
19505
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
19506
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
19507
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
19508
+ const centerLongitude = (startLongitude + endLongitude) / 2;
19509
+ const localCorners = [
19510
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
19511
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
19512
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
19513
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
19514
+ ];
19515
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
19516
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
19517
+ if (surfaceNormal.z <= 0.01) {
19518
+ continue;
19519
+ }
19520
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
19521
+ surfacePatches.push({
19522
+ corners: projectedCorners,
19523
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
19524
+ transformedCorners.length,
19525
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
19526
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
19527
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
19528
+ });
19529
+ }
19229
19530
  }
19230
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
19231
- context.beginPath();
19232
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
19233
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
19234
- context.strokeStyle = `${palette.ink}b8`;
19235
- context.lineWidth = Math.max(1.1, size * 0.009);
19236
- context.lineCap = 'round';
19237
- context.stroke();
19531
+ return surfacePatches;
19238
19532
  }
19239
19533
  /**
19240
- * Draws one filled projected quad.
19534
+ * Samples one point on the continuous Octopus 3D 2 surface.
19535
+ *
19536
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
19537
+ * instead of switching to separately rendered tentacles.
19538
+ *
19539
+ * @private helper of `octopus3d2AvatarVisual`
19540
+ */
19541
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
19542
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
19543
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
19544
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
19545
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
19546
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
19547
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
19548
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
19549
+ const horizontalScale = 1.02 +
19550
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
19551
+ upperBlend * 0.08;
19552
+ const depthScale = 1.04 +
19553
+ upperBlend * 0.16 +
19554
+ Math.max(0, Math.cos(longitude)) * 0.1 +
19555
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
19556
+ Math.max(0, -Math.cos(longitude)) * 0.04;
19557
+ const lowerDrop = skirtEnvelope *
19558
+ radiusY *
19559
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
19560
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
19561
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
19562
+ return {
19563
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
19564
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
19565
+ upperBlend * radiusY * 0.1 +
19566
+ lowerDrop +
19567
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
19568
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
19569
+ };
19570
+ }
19571
+ /**
19572
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
19241
19573
  *
19242
- * @private helper of `octopus3dAvatarVisual`
19574
+ * @private helper of `octopus3d2AvatarVisual`
19243
19575
  */
19244
- function drawProjectedQuad(context, corners, fillStyle) {
19245
- context.beginPath();
19246
- context.moveTo(corners[0].x, corners[0].y);
19247
- context.lineTo(corners[1].x, corners[1].y);
19248
- context.lineTo(corners[2].x, corners[2].y);
19249
- context.lineTo(corners[3].x, corners[3].y);
19250
- context.closePath();
19251
- context.fillStyle = fillStyle;
19252
- context.fill();
19576
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
19577
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
19578
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
19253
19579
  }
19254
19580
  /**
19255
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
19581
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
19256
19582
  *
19257
- * @private helper of `octopus3dAvatarVisual`
19583
+ * @private helper of `octopus3d2AvatarVisual`
19258
19584
  */
19259
- function formatAlphaHex(opacity) {
19260
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
19261
- .toString(16)
19262
- .padStart(2, '0');
19585
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
19586
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
19587
+ if (tonalProgress < 0.16) {
19588
+ return palette.highlight;
19589
+ }
19590
+ if (tonalProgress < 0.34) {
19591
+ return palette.secondary;
19592
+ }
19593
+ if (tonalProgress < 0.72) {
19594
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
19595
+ }
19596
+ return `${palette.shadow}f2`;
19597
+ }
19598
+ /**
19599
+ * Draws one projected patch with soft octopus shading.
19600
+ *
19601
+ * @private helper of `octopus3d2AvatarVisual`
19602
+ */
19603
+ function drawBlobbySurfacePatch(context, surfacePatch) {
19604
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
19605
+ if (surfacePatch.lightIntensity > 0) {
19606
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
19607
+ }
19608
+ else if (surfacePatch.lightIntensity < 0) {
19609
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
19610
+ }
19611
+ context.save();
19612
+ context.beginPath();
19613
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
19614
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
19615
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
19616
+ }
19617
+ context.closePath();
19618
+ context.strokeStyle = surfacePatch.outlineColor;
19619
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
19620
+ context.lineJoin = 'round';
19621
+ context.stroke();
19622
+ context.restore();
19263
19623
  }
19264
19624
 
19265
19625
  /* eslint-disable no-magic-numbers */
@@ -20031,6 +20391,7 @@
20031
20391
  octopus2AvatarVisual,
20032
20392
  octopus3AvatarVisual,
20033
20393
  octopus3dAvatarVisual,
20394
+ octopus3d2AvatarVisual,
20034
20395
  asciiOctopusAvatarVisual,
20035
20396
  minecraftAvatarVisual,
20036
20397
  minecraft2AvatarVisual,
@@ -25606,11 +25967,11 @@
25606
25967
  });
25607
25968
  }
25608
25969
  /**
25609
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
25970
+ * Prepares one attachment for best-effort text decoding.
25610
25971
  *
25611
- * @private internal utility for shared text decoding
25972
+ * @private function of decodeAttachmentAsText
25612
25973
  */
25613
- function decodeAttachmentAsText(input, options = {}) {
25974
+ function createDecodeAttachmentPreparation(input, options) {
25614
25975
  var _a;
25615
25976
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
25616
25977
  const forceText = options.forceText === true;
@@ -25624,54 +25985,102 @@
25624
25985
  const inspection = inspectBytes(truncatedBytes);
25625
25986
  const trustedTextMime = isTrustedTextMimeType(mimeType);
25626
25987
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
25988
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
25627
25989
  if (isTruncated) {
25628
25990
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
25629
25991
  }
25630
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
25631
- if (shouldTreatAsBinary && !forceText) {
25632
- warnings.push('File content looks binary, so text decoding was skipped.');
25633
- return {
25634
- text: '',
25635
- encodingUsed: 'binary',
25636
- confidence: 1,
25637
- warnings,
25638
- wasBinary: true,
25639
- isTruncated,
25640
- };
25992
+ return {
25993
+ warnings,
25994
+ charset,
25995
+ bom,
25996
+ inspection,
25997
+ truncatedBytes,
25998
+ isTruncated,
25999
+ forceText,
26000
+ shouldTreatAsBinary,
26001
+ };
26002
+ }
26003
+ /**
26004
+ * Returns an early result when the attachment should stay classified as binary.
26005
+ *
26006
+ * @private function of decodeAttachmentAsText
26007
+ */
26008
+ function createBinaryDecodeResult(preparation) {
26009
+ if (!preparation.shouldTreatAsBinary) {
26010
+ return null;
25641
26011
  }
25642
- if (shouldTreatAsBinary && forceText) {
25643
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
26012
+ if (preparation.forceText) {
26013
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
26014
+ return null;
25644
26015
  }
25645
- if (charset && !isSupportedEncoding(charset)) {
25646
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
26016
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
26017
+ return {
26018
+ text: '',
26019
+ encodingUsed: 'binary',
26020
+ confidence: 1,
26021
+ warnings: preparation.warnings,
26022
+ wasBinary: true,
26023
+ isTruncated: preparation.isTruncated,
26024
+ };
26025
+ }
26026
+ /**
26027
+ * Warns when the declared charset cannot be used by the runtime decoder.
26028
+ *
26029
+ * @private function of decodeAttachmentAsText
26030
+ */
26031
+ function addUnsupportedCharsetWarning(preparation) {
26032
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
26033
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
25647
26034
  }
25648
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
25649
- const candidates = buildCandidateEncodings({
25650
- mimeType,
25651
- charset: charset && isSupportedEncoding(charset) ? charset : null,
25652
- bom,
25653
- inspection,
25654
- });
25655
- const decodedCandidates = candidates
26035
+ }
26036
+ /**
26037
+ * Returns the byte slice that should actually be decoded as text.
26038
+ *
26039
+ * @private function of decodeAttachmentAsText
26040
+ */
26041
+ function getBytesToDecode(preparation) {
26042
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
26043
+ }
26044
+ /**
26045
+ * Decodes all candidate encodings and sorts the successful results by score.
26046
+ *
26047
+ * @private function of decodeAttachmentAsText
26048
+ */
26049
+ function decodeAttachmentCandidates(preparation) {
26050
+ return buildCandidateEncodings({
26051
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
26052
+ bom: preparation.bom,
26053
+ inspection: preparation.inspection,
26054
+ })
25656
26055
  .map(({ encoding, source }) => {
25657
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
26056
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
25658
26057
  return decoded ? { ...decoded, source } : null;
25659
26058
  })
25660
26059
  .filter((candidate) => candidate !== null)
25661
26060
  .sort((left, right) => left.score - right.score);
25662
- const bestCandidate = decodedCandidates[0];
25663
- if (!bestCandidate) {
25664
- warnings.push('No supported text decoder was available.');
25665
- return {
25666
- text: '',
25667
- encodingUsed: 'binary',
25668
- confidence: 0,
25669
- warnings,
25670
- wasBinary: true,
25671
- isTruncated,
25672
- };
25673
- }
25674
- const secondBestCandidate = decodedCandidates[1];
26061
+ }
26062
+ /**
26063
+ * Returns the fallback result used when no text decoder could be applied.
26064
+ *
26065
+ * @private function of decodeAttachmentAsText
26066
+ */
26067
+ function createNoDecoderAvailableResult(preparation) {
26068
+ preparation.warnings.push('No supported text decoder was available.');
26069
+ return {
26070
+ text: '',
26071
+ encodingUsed: 'binary',
26072
+ confidence: 0,
26073
+ warnings: preparation.warnings,
26074
+ wasBinary: true,
26075
+ isTruncated: preparation.isTruncated,
26076
+ };
26077
+ }
26078
+ /**
26079
+ * Estimates confidence for the winning decoded text candidate.
26080
+ *
26081
+ * @private function of decodeAttachmentAsText
26082
+ */
26083
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
25675
26084
  const baseConfidence = bestCandidate.source === 'bom'
25676
26085
  ? 1
25677
26086
  : bestCandidate.source === 'charset'
@@ -25684,27 +26093,62 @@
25684
26093
  ? 0.82
25685
26094
  : 0.62;
25686
26095
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
25687
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
26096
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
26097
+ }
26098
+ /**
26099
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
26100
+ *
26101
+ * @private function of decodeAttachmentAsText
26102
+ */
26103
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
25688
26104
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
25689
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
26105
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
25690
26106
  }
25691
26107
  if (bestCandidate.source === 'heuristic' &&
25692
26108
  bestCandidate.encoding === 'utf-8' &&
25693
26109
  bestCandidate.replacementCount > 0) {
25694
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
26110
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
25695
26111
  }
25696
26112
  if (confidence < 0.6) {
25697
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
26113
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
25698
26114
  }
26115
+ }
26116
+ /**
26117
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
26118
+ *
26119
+ * @private function of decodeAttachmentAsText
26120
+ */
26121
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
25699
26122
  return {
25700
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
26123
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
25701
26124
  encodingUsed: bestCandidate.encoding,
25702
26125
  confidence,
25703
- warnings,
26126
+ warnings: preparation.warnings,
25704
26127
  wasBinary: false,
25705
- isTruncated,
26128
+ isTruncated: preparation.isTruncated,
25706
26129
  };
25707
26130
  }
26131
+ /**
26132
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
26133
+ *
26134
+ * @private internal utility for shared text decoding
26135
+ */
26136
+ function decodeAttachmentAsText(input, options = {}) {
26137
+ const preparation = createDecodeAttachmentPreparation(input, options);
26138
+ const binaryResult = createBinaryDecodeResult(preparation);
26139
+ if (binaryResult) {
26140
+ return binaryResult;
26141
+ }
26142
+ addUnsupportedCharsetWarning(preparation);
26143
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
26144
+ const bestCandidate = decodedCandidates[0];
26145
+ if (!bestCandidate) {
26146
+ return createNoDecoderAvailableResult(preparation);
26147
+ }
26148
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
26149
+ addDecodeWarnings(bestCandidate, confidence, preparation);
26150
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
26151
+ }
25708
26152
 
25709
26153
  /**
25710
26154
  * Base GitHub API URL.
@@ -30250,10 +30694,7 @@
30250
30694
  * @private internal function of `$registeredLlmToolsMessage`
30251
30695
  */
30252
30696
  function createProviderStatusMessages(llmToolStatus, env) {
30253
- return [
30254
- createInstallationStatusMessage(llmToolStatus),
30255
- createConfigurationStatusMessage(llmToolStatus, env),
30256
- ];
30697
+ return [createInstallationStatusMessage(llmToolStatus), createConfigurationStatusMessage(llmToolStatus, env)];
30257
30698
  }
30258
30699
  /**
30259
30700
  * Creates the installation-status sentence for one provider.
@@ -37769,7 +38210,10 @@
37769
38210
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
37770
38211
 
37771
38212
  Available models:
37772
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
38213
+ ${block(this.options
38214
+ .getHardcodedModels()
38215
+ .map(({ modelName }) => `- "${modelName}"`)
38216
+ .join('\n'))}
37773
38217
 
37774
38218
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
37775
38219
 
@@ -38123,7 +38567,8 @@
38123
38567
  };
38124
38568
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
38125
38569
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
38126
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
38570
+ rawPromptContent +=
38571
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
38127
38572
  }
38128
38573
  const rawRequest = {
38129
38574
  ...modelSettings,
@@ -38228,7 +38673,9 @@
38228
38673
  * Schedules one request through the shared limiter and retry policy.
38229
38674
  */
38230
38675
  async executeRateLimitedRequest(requestFn) {
38231
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
38676
+ return this.limiter
38677
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
38678
+ .catch((error) => {
38232
38679
  assertsError(error);
38233
38680
  if (this.options.isVerbose) {
38234
38681
  console.info(colors__default["default"].bgRed('error'), error);
@@ -39435,7 +39882,9 @@
39435
39882
  pollingState.lastProgressAtMs = nowMs;
39436
39883
  pollingState.lastProgressKey = progressKey;
39437
39884
  }
39438
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
39885
+ if (this.options.isVerbose &&
39886
+ (statusCountsKey !== pollingState.lastCountsKey ||
39887
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
39439
39888
  console.info('[🤰]', 'Vector store file batch status', {
39440
39889
  vectorStoreId,
39441
39890
  batchId,