@promptbook/wizard 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/esm/index.es.js CHANGED
@@ -38,7 +38,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
38
38
  * @generated
39
39
  * @see https://github.com/webgptorg/promptbook
40
40
  */
41
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
41
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
42
42
  /**
43
43
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
44
44
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -302,6 +302,111 @@ function checkChannelValue(channelName, value) {
302
302
  }
303
303
  }
304
304
 
305
+ /**
306
+ * Shared immutable channel storage and serialization helpers for `Color`.
307
+ *
308
+ * @private base class of Color
309
+ */
310
+ class ColorValue {
311
+ constructor(red, green, blue, alpha = 255) {
312
+ this.red = red;
313
+ this.green = green;
314
+ this.blue = blue;
315
+ this.alpha = alpha;
316
+ checkChannelValue('Red', red);
317
+ checkChannelValue('Green', green);
318
+ checkChannelValue('Blue', blue);
319
+ checkChannelValue('Alpha', alpha);
320
+ }
321
+ /**
322
+ * Shortcut for `red` property
323
+ * Number from 0 to 255
324
+ * @alias red
325
+ */
326
+ get r() {
327
+ return this.red;
328
+ }
329
+ /**
330
+ * Shortcut for `green` property
331
+ * Number from 0 to 255
332
+ * @alias green
333
+ */
334
+ get g() {
335
+ return this.green;
336
+ }
337
+ /**
338
+ * Shortcut for `blue` property
339
+ * Number from 0 to 255
340
+ * @alias blue
341
+ */
342
+ get b() {
343
+ return this.blue;
344
+ }
345
+ /**
346
+ * Shortcut for `alpha` property
347
+ * Number from 0 (transparent) to 255 (opaque)
348
+ * @alias alpha
349
+ */
350
+ get a() {
351
+ return this.alpha;
352
+ }
353
+ /**
354
+ * Shortcut for `alpha` property
355
+ * Number from 0 (transparent) to 255 (opaque)
356
+ * @alias alpha
357
+ */
358
+ get opacity() {
359
+ return this.alpha;
360
+ }
361
+ /**
362
+ * Shortcut for 1-`alpha` property
363
+ */
364
+ get transparency() {
365
+ return 255 - this.alpha;
366
+ }
367
+ clone() {
368
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
369
+ }
370
+ toString() {
371
+ return this.toHex();
372
+ }
373
+ toHex() {
374
+ if (this.alpha === 255) {
375
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
376
+ .toString(16)
377
+ .padStart(2, '0')}`;
378
+ }
379
+ else {
380
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
381
+ .toString(16)
382
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
383
+ }
384
+ }
385
+ toRgb() {
386
+ if (this.alpha === 255) {
387
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
388
+ }
389
+ else {
390
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
391
+ }
392
+ }
393
+ toHsl() {
394
+ throw new Error(`Getting HSL is not implemented`);
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Checks if the given value is a valid hex color string
400
+ *
401
+ * @param value - value to check
402
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
403
+ *
404
+ * @private function of Color
405
+ */
406
+ function isHexColorString(value) {
407
+ 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));
408
+ }
409
+
305
410
  /**
306
411
  * Constant for short hex lengths.
307
412
  */
@@ -513,16 +618,53 @@ function parseAlphaValue(value) {
513
618
 
514
619
  /**
515
620
  * Pattern matching hsl regex.
621
+ *
622
+ * @private function of Color
516
623
  */
517
624
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
518
625
  /**
519
626
  * Pattern matching RGB regex.
627
+ *
628
+ * @private function of Color
520
629
  */
521
630
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
522
631
  /**
523
632
  * Pattern matching rgba regex.
633
+ *
634
+ * @private function of Color
524
635
  */
525
636
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
637
+ /**
638
+ * Parses a supported color string into RGBA channels.
639
+ *
640
+ * @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`,...
641
+ * @returns RGBA channel values.
642
+ *
643
+ * @private function of Color
644
+ */
645
+ function parseColorString(color) {
646
+ const trimmed = color.trim();
647
+ const cssColor = CSS_COLORS[trimmed];
648
+ if (cssColor) {
649
+ return parseColorString(cssColor);
650
+ }
651
+ else if (isHexColorString(trimmed)) {
652
+ return parseHexColor(trimmed);
653
+ }
654
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
655
+ return parseHslColor(trimmed);
656
+ }
657
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
658
+ return parseRgbColor(trimmed);
659
+ }
660
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
661
+ return parseRgbaColor(trimmed);
662
+ }
663
+ else {
664
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
665
+ }
666
+ }
667
+
526
668
  /**
527
669
  * Color object represents an RGB color with alpha channel
528
670
  *
@@ -530,7 +672,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
530
672
  *
531
673
  * @public exported from `@promptbook/color`
532
674
  */
533
- class Color {
675
+ class Color extends ColorValue {
534
676
  /**
535
677
  * Creates a new Color instance from miscellaneous formats
536
678
  * - It can receive Color instance and just return the same instance
@@ -603,25 +745,7 @@ class Color {
603
745
  * @returns Color object
604
746
  */
605
747
  static fromString(color) {
606
- const trimmed = color.trim();
607
- if (CSS_COLORS[trimmed]) {
608
- return Color.fromString(CSS_COLORS[trimmed]);
609
- }
610
- else if (Color.isHexColorString(trimmed)) {
611
- return Color.fromHex(trimmed);
612
- }
613
- if (HSL_REGEX_PATTERN.test(trimmed)) {
614
- return Color.fromHsl(trimmed);
615
- }
616
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
617
- return Color.fromRgbString(trimmed);
618
- }
619
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
620
- return Color.fromRgbaString(trimmed);
621
- }
622
- else {
623
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
624
- }
748
+ return Color.fromColorChannels(parseColorString(color));
625
749
  }
626
750
  /**
627
751
  * Gets common color
@@ -651,8 +775,7 @@ class Color {
651
775
  * @returns Color object
652
776
  */
653
777
  static fromHex(hex) {
654
- const { red, green, blue, alpha } = parseHexColor(hex);
655
- return take(new Color(red, green, blue, alpha));
778
+ return Color.fromColorChannels(parseHexColor(hex));
656
779
  }
657
780
  /**
658
781
  * Creates a new Color instance from color in hsl format
@@ -661,8 +784,7 @@ class Color {
661
784
  * @returns Color object
662
785
  */
663
786
  static fromHsl(hsl) {
664
- const { red, green, blue, alpha } = parseHslColor(hsl);
665
- return take(new Color(red, green, blue, alpha));
787
+ return Color.fromColorChannels(parseHslColor(hsl));
666
788
  }
667
789
  /**
668
790
  * Creates a new Color instance from color in rgb format
@@ -671,8 +793,7 @@ class Color {
671
793
  * @returns Color object
672
794
  */
673
795
  static fromRgbString(rgb) {
674
- const { red, green, blue, alpha } = parseRgbColor(rgb);
675
- return take(new Color(red, green, blue, alpha));
796
+ return Color.fromColorChannels(parseRgbColor(rgb));
676
797
  }
677
798
  /**
678
799
  * Creates a new Color instance from color in rbga format
@@ -681,8 +802,7 @@ class Color {
681
802
  * @returns Color object
682
803
  */
683
804
  static fromRgbaString(rgba) {
684
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
685
- return take(new Color(red, green, blue, alpha));
805
+ return Color.fromColorChannels(parseRgbaColor(rgba));
686
806
  }
687
807
  /**
688
808
  * Creates a new Color for color channels values
@@ -694,7 +814,7 @@ class Color {
694
814
  * @returns Color object
695
815
  */
696
816
  static fromValues(red, green, blue, alpha = 255) {
697
- return take(new Color(red, green, blue, alpha));
817
+ return Color.fromColorChannels({ red, green, blue, alpha });
698
818
  }
699
819
  /**
700
820
  * Checks if the given value is a valid Color object.
@@ -727,8 +847,7 @@ class Color {
727
847
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
728
848
  */
729
849
  static isHexColorString(value) {
730
- return (typeof value === 'string' &&
731
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
850
+ return isHexColorString(value);
732
851
  }
733
852
  /**
734
853
  * Creates new Color object
@@ -741,89 +860,13 @@ class Color {
741
860
  * @param alpha number from 0 (transparent) to 255 (opaque)
742
861
  */
743
862
  constructor(red, green, blue, alpha = 255) {
744
- this.red = red;
745
- this.green = green;
746
- this.blue = blue;
747
- this.alpha = alpha;
748
- checkChannelValue('Red', red);
749
- checkChannelValue('Green', green);
750
- checkChannelValue('Blue', blue);
751
- checkChannelValue('Alpha', alpha);
752
- }
753
- /**
754
- * Shortcut for `red` property
755
- * Number from 0 to 255
756
- * @alias red
757
- */
758
- get r() {
759
- return this.red;
760
- }
761
- /**
762
- * Shortcut for `green` property
763
- * Number from 0 to 255
764
- * @alias green
765
- */
766
- get g() {
767
- return this.green;
768
- }
769
- /**
770
- * Shortcut for `blue` property
771
- * Number from 0 to 255
772
- * @alias blue
773
- */
774
- get b() {
775
- return this.blue;
776
- }
777
- /**
778
- * Shortcut for `alpha` property
779
- * Number from 0 (transparent) to 255 (opaque)
780
- * @alias alpha
781
- */
782
- get a() {
783
- return this.alpha;
784
- }
785
- /**
786
- * Shortcut for `alpha` property
787
- * Number from 0 (transparent) to 255 (opaque)
788
- * @alias alpha
789
- */
790
- get opacity() {
791
- return this.alpha;
792
- }
793
- /**
794
- * Shortcut for 1-`alpha` property
795
- */
796
- get transparency() {
797
- return 255 - this.alpha;
798
- }
799
- clone() {
800
- return take(new Color(this.red, this.green, this.blue, this.alpha));
801
- }
802
- toString() {
803
- return this.toHex();
804
- }
805
- toHex() {
806
- if (this.alpha === 255) {
807
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
808
- .toString(16)
809
- .padStart(2, '0')}`;
810
- }
811
- else {
812
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
813
- .toString(16)
814
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
815
- }
863
+ super(red, green, blue, alpha);
816
864
  }
817
- toRgb() {
818
- if (this.alpha === 255) {
819
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
820
- }
821
- else {
822
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
823
- }
865
+ createColor(red, green, blue, alpha) {
866
+ return new Color(red, green, blue, alpha);
824
867
  }
825
- toHsl() {
826
- throw new Error(`Getting HSL is not implemented`);
868
+ static fromColorChannels({ red, green, blue, alpha }) {
869
+ return take(new Color(red, green, blue, alpha));
827
870
  }
828
871
  }
829
872
 
@@ -1499,120 +1542,183 @@ function assertsError(whatWasThrown) {
1499
1542
  * @public exported from `@promptbook/utils`
1500
1543
  */
1501
1544
  function checkSerializableAsJson(options) {
1502
- const { value, name, message } = options;
1545
+ checkSerializableValue(options);
1546
+ }
1547
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1548
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1549
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1550
+ /**
1551
+ * Checks one value and dispatches to the appropriate specialized validator.
1552
+ *
1553
+ * @private function of `checkSerializableAsJson`
1554
+ */
1555
+ function checkSerializableValue(options) {
1556
+ const { value } = options;
1557
+ if (isSerializablePrimitive(value)) {
1558
+ return;
1559
+ }
1503
1560
  if (value === undefined) {
1504
- throw new UnexpectedError(`${name} is undefined`);
1561
+ throw new UnexpectedError(`${options.name} is undefined`);
1505
1562
  }
1506
- else if (value === null) {
1507
- return;
1563
+ if (typeof value === 'symbol') {
1564
+ throw new UnexpectedError(`${options.name} is symbol`);
1508
1565
  }
1509
- else if (typeof value === 'boolean') {
1510
- return;
1566
+ if (typeof value === 'function') {
1567
+ throw new UnexpectedError(`${options.name} is function`);
1511
1568
  }
1512
- else if (typeof value === 'number' && !isNaN(value)) {
1569
+ if (Array.isArray(value)) {
1570
+ checkSerializableArray(options, value);
1513
1571
  return;
1514
1572
  }
1515
- else if (typeof value === 'string') {
1573
+ if (value !== null && typeof value === 'object') {
1574
+ checkSerializableObject(options, value);
1516
1575
  return;
1517
1576
  }
1518
- else if (typeof value === 'symbol') {
1519
- throw new UnexpectedError(`${name} is symbol`);
1520
- }
1521
- else if (typeof value === 'function') {
1522
- throw new UnexpectedError(`${name} is function`);
1523
- }
1524
- else if (typeof value === 'object' && Array.isArray(value)) {
1525
- for (let i = 0; i < value.length; i++) {
1526
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1527
- }
1577
+ throwUnknownTypeError(options);
1578
+ }
1579
+ /**
1580
+ * Checks the primitive values that are directly JSON serializable.
1581
+ *
1582
+ * @private function of `checkSerializableAsJson`
1583
+ */
1584
+ function isSerializablePrimitive(value) {
1585
+ return (value === null ||
1586
+ typeof value === 'boolean' ||
1587
+ (typeof value === 'number' && !isNaN(value)) ||
1588
+ typeof value === 'string');
1589
+ }
1590
+ /**
1591
+ * Recursively checks JSON array items.
1592
+ *
1593
+ * @private function of `checkSerializableAsJson`
1594
+ */
1595
+ function checkSerializableArray(context, arrayValue) {
1596
+ for (let index = 0; index < arrayValue.length; index++) {
1597
+ checkSerializableAsJson({
1598
+ ...context,
1599
+ name: `${context.name}[${index}]`,
1600
+ value: arrayValue[index],
1601
+ });
1528
1602
  }
1529
- else if (typeof value === 'object') {
1530
- if (value instanceof Date) {
1531
- throw new UnexpectedError(spaceTrim$1((block) => `
1532
- \`${name}\` is Date
1603
+ }
1604
+ /**
1605
+ * Checks object-like values and dispatches special unsupported built-ins.
1606
+ *
1607
+ * @private function of `checkSerializableAsJson`
1608
+ */
1609
+ function checkSerializableObject(context, objectValue) {
1610
+ checkUnsupportedObjectType(context, objectValue);
1611
+ checkSerializableObjectEntries(context, objectValue);
1612
+ assertJsonStringificationSucceeds(context, objectValue);
1613
+ }
1614
+ /**
1615
+ * Rejects built-in objects that must be converted before JSON serialization.
1616
+ *
1617
+ * @private function of `checkSerializableAsJson`
1618
+ */
1619
+ function checkUnsupportedObjectType(context, objectValue) {
1620
+ if (objectValue instanceof Date) {
1621
+ throw new UnexpectedError(spaceTrim$1((block) => `
1622
+ \`${context.name}\` is Date
1533
1623
 
1534
- Use \`string_date_iso8601\` instead
1624
+ Use \`string_date_iso8601\` instead
1535
1625
 
1536
- Additional message for \`${name}\`:
1537
- ${block(message || '(nothing)')}
1538
- `));
1539
- }
1540
- else if (value instanceof Map) {
1541
- throw new UnexpectedError(`${name} is Map`);
1542
- }
1543
- else if (value instanceof Set) {
1544
- throw new UnexpectedError(`${name} is Set`);
1545
- }
1546
- else if (value instanceof RegExp) {
1547
- throw new UnexpectedError(`${name} is RegExp`);
1548
- }
1549
- else if (value instanceof Error) {
1550
- throw new UnexpectedError(spaceTrim$1((block) => `
1551
- \`${name}\` is unserialized Error
1626
+ Additional message for \`${context.name}\`:
1627
+ ${block(context.message || '(nothing)')}
1628
+ `));
1629
+ }
1630
+ if (objectValue instanceof Map) {
1631
+ throw new UnexpectedError(`${context.name} is Map`);
1632
+ }
1633
+ if (objectValue instanceof Set) {
1634
+ throw new UnexpectedError(`${context.name} is Set`);
1635
+ }
1636
+ if (objectValue instanceof RegExp) {
1637
+ throw new UnexpectedError(`${context.name} is RegExp`);
1638
+ }
1639
+ if (objectValue instanceof Error) {
1640
+ throw new UnexpectedError(spaceTrim$1((block) => `
1641
+ \`${context.name}\` is unserialized Error
1552
1642
 
1553
- Use function \`serializeError\`
1643
+ Use function \`serializeError\`
1554
1644
 
1555
- Additional message for \`${name}\`:
1556
- ${block(message || '(nothing)')}
1645
+ Additional message for \`${context.name}\`:
1646
+ ${block(context.message || '(nothing)')}
1557
1647
 
1558
- `));
1648
+ `));
1649
+ }
1650
+ }
1651
+ /**
1652
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1653
+ *
1654
+ * @private function of `checkSerializableAsJson`
1655
+ */
1656
+ function checkSerializableObjectEntries(context, objectValue) {
1657
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1658
+ if (subValue === undefined) {
1659
+ // Note: undefined in object is serializable - it is just omitted
1660
+ continue;
1559
1661
  }
1560
- else {
1561
- for (const [subName, subValue] of Object.entries(value)) {
1562
- if (subValue === undefined) {
1563
- // Note: undefined in object is serializable - it is just omitted
1564
- continue;
1565
- }
1566
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1567
- }
1568
- try {
1569
- JSON.stringify(value); // <- TODO: [0]
1570
- }
1571
- catch (error) {
1572
- assertsError(error);
1573
- throw new UnexpectedError(spaceTrim$1((block) => `
1574
- \`${name}\` is not serializable
1662
+ checkSerializableAsJson({
1663
+ ...context,
1664
+ name: `${context.name}.${subName}`,
1665
+ value: subValue,
1666
+ });
1667
+ }
1668
+ }
1669
+ /**
1670
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1671
+ *
1672
+ * @private function of `checkSerializableAsJson`
1673
+ */
1674
+ function assertJsonStringificationSucceeds(context, objectValue) {
1675
+ try {
1676
+ JSON.stringify(objectValue); // <- TODO: [0]
1677
+ }
1678
+ catch (error) {
1679
+ assertsError(error);
1680
+ throw new UnexpectedError(spaceTrim$1((block) => `
1681
+ \`${context.name}\` is not serializable
1575
1682
 
1576
- ${block(error.stack || error.message)}
1683
+ ${block(error.stack || error.message)}
1577
1684
 
1578
- Additional message for \`${name}\`:
1579
- ${block(message || '(nothing)')}
1580
- `));
1685
+ Additional message for \`${context.name}\`:
1686
+ ${block(context.message || '(nothing)')}
1687
+ `));
1688
+ }
1689
+ /*
1690
+ TODO: [0] Is there some more elegant way to check circular references?
1691
+ const seen = new Set();
1692
+ const stack = [{ value }];
1693
+ while (stack.length > 0) {
1694
+ const { value } = stack.pop()!;
1695
+ if (typeof value === 'object' && value !== null) {
1696
+ if (seen.has(value)) {
1697
+ throw new UnexpectedError(`${name} has circular reference`);
1581
1698
  }
1582
- /*
1583
- TODO: [0] Is there some more elegant way to check circular references?
1584
- const seen = new Set();
1585
- const stack = [{ value }];
1586
- while (stack.length > 0) {
1587
- const { value } = stack.pop()!;
1588
- if (typeof value === 'object' && value !== null) {
1589
- if (seen.has(value)) {
1590
- throw new UnexpectedError(`${name} has circular reference`);
1591
- }
1592
- seen.add(value);
1593
- if (Array.isArray(value)) {
1594
- stack.push(...value.map((value) => ({ value })));
1595
- } else {
1596
- stack.push(...Object.values(value).map((value) => ({ value })));
1597
- }
1598
- }
1699
+ seen.add(value);
1700
+ if (Array.isArray(value)) {
1701
+ stack.push(...value.map((value) => ({ value })));
1702
+ } else {
1703
+ stack.push(...Object.values(value).map((value) => ({ value })));
1599
1704
  }
1600
- */
1601
- return;
1602
1705
  }
1603
1706
  }
1604
- else {
1605
- throw new UnexpectedError(spaceTrim$1((block) => `
1606
- \`${name}\` is unknown type
1707
+ */
1708
+ }
1709
+ /**
1710
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
1711
+ *
1712
+ * @private function of `checkSerializableAsJson`
1713
+ */
1714
+ function throwUnknownTypeError(context) {
1715
+ throw new UnexpectedError(spaceTrim$1((block) => `
1716
+ \`${context.name}\` is unknown type
1607
1717
 
1608
- Additional message for \`${name}\`:
1609
- ${block(message || '(nothing)')}
1610
- `));
1611
- }
1718
+ Additional message for \`${context.name}\`:
1719
+ ${block(context.message || '(nothing)')}
1720
+ `));
1612
1721
  }
1613
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1614
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1615
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1616
1722
 
1617
1723
  /**
1618
1724
  * Creates a deep clone of the given object
@@ -5973,7 +6079,10 @@ class OpenAiCompatibleModelCatalog {
5973
6079
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
5974
6080
 
5975
6081
  Available models:
5976
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
6082
+ ${block(this.options
6083
+ .getHardcodedModels()
6084
+ .map(({ modelName }) => `- "${modelName}"`)
6085
+ .join('\n'))}
5977
6086
 
5978
6087
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
5979
6088
 
@@ -6327,7 +6436,8 @@ class OpenAiCompatibleNonChatPromptCaller {
6327
6436
  };
6328
6437
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
6329
6438
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
6330
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
6439
+ rawPromptContent +=
6440
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
6331
6441
  }
6332
6442
  const rawRequest = {
6333
6443
  ...modelSettings,
@@ -6432,7 +6542,9 @@ class OpenAiCompatibleRequestManager {
6432
6542
  * Schedules one request through the shared limiter and retry policy.
6433
6543
  */
6434
6544
  async executeRateLimitedRequest(requestFn) {
6435
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
6545
+ return this.limiter
6546
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
6547
+ .catch((error) => {
6436
6548
  assertsError(error);
6437
6549
  if (this.options.isVerbose) {
6438
6550
  console.info(colors.bgRed('error'), error);
@@ -9172,7 +9284,9 @@ class OpenAiVectorStoreFileBatchPoller {
9172
9284
  pollingState.lastProgressAtMs = nowMs;
9173
9285
  pollingState.lastProgressKey = progressKey;
9174
9286
  }
9175
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
9287
+ if (this.options.isVerbose &&
9288
+ (statusCountsKey !== pollingState.lastCountsKey ||
9289
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
9176
9290
  console.info('[🤰]', 'Vector store file batch status', {
9177
9291
  vectorStoreId,
9178
9292
  batchId,
@@ -11245,7 +11359,7 @@ function createJokerCommands(task) {
11245
11359
  */
11246
11360
  function createPostprocessingCommands(task) {
11247
11361
  var _a;
11248
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
11362
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
11249
11363
  }
11250
11364
  /**
11251
11365
  * Collects expectation commands.
@@ -11775,8 +11889,7 @@ function hasTaskJokers(task) {
11775
11889
  * @private internal utility of `validatePipeline`
11776
11890
  */
11777
11891
  function validateTaskSupportsJokers(task, pipelineIdentification) {
11778
- if (task.format ||
11779
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
11892
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
11780
11893
  return;
11781
11894
  }
11782
11895
  throw new PipelineLogicError(spaceTrim$1((block) => `
@@ -14971,9 +15084,7 @@ function createFailuresSummary($failedResults) {
14971
15084
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
14972
15085
 
14973
15086
  Result:
14974
- ${block(failure.result === null
14975
- ? 'null'
14976
- : quoteMultilineText(spaceTrim$1(failure.result)))}
15087
+ ${block(failure.result === null ? 'null' : quoteMultilineText(spaceTrim$1(failure.result)))}
14977
15088
  `;
14978
15089
  }))
14979
15090
  .join('\n\n---\n\n');
@@ -22459,7 +22570,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
22459
22570
  *
22460
22571
  * @private helper of `minecraft2AvatarVisual`
22461
22572
  */
22462
- const LIGHT_DIRECTION$1 = normalizeVector3({
22573
+ const LIGHT_DIRECTION$2 = normalizeVector3({
22463
22574
  x: 0.4,
22464
22575
  y: -0.65,
22465
22576
  z: 0.92,
@@ -22671,7 +22782,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
22671
22782
  corners: projectedCorners,
22672
22783
  texture: faceDefinition.texture,
22673
22784
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
22674
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
22785
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
22675
22786
  outlineColor: cuboid.outlineColor,
22676
22787
  };
22677
22788
  });
@@ -23731,13 +23842,138 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
23731
23842
  context.restore();
23732
23843
  }
23733
23844
 
23845
+ /* eslint-disable no-magic-numbers */
23846
+ /**
23847
+ * Draws one projected eye on a rotated octopus surface.
23848
+ *
23849
+ * @private helper of the 3D octopus avatar visuals
23850
+ */
23851
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
23852
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
23853
+ if (centerScenePoint.z <= center.z) {
23854
+ return;
23855
+ }
23856
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
23857
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
23858
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
23859
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
23860
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
23861
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
23862
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
23863
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
23864
+ return;
23865
+ }
23866
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
23867
+ radiusX: projectedRadiusX,
23868
+ radiusY: projectedRadiusY,
23869
+ timeMs,
23870
+ phase,
23871
+ interaction,
23872
+ });
23873
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
23874
+ context.save();
23875
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
23876
+ context.rotate(rotation);
23877
+ context.beginPath();
23878
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
23879
+ context.fillStyle = '#f8fbff';
23880
+ context.fill();
23881
+ context.clip();
23882
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
23883
+ irisGradient.addColorStop(0, palette.highlight);
23884
+ irisGradient.addColorStop(0.56, palette.secondary);
23885
+ irisGradient.addColorStop(1, palette.shadow);
23886
+ context.beginPath();
23887
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
23888
+ context.fillStyle = irisGradient;
23889
+ context.fill();
23890
+ context.beginPath();
23891
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
23892
+ context.fillStyle = palette.ink;
23893
+ context.fill();
23894
+ context.beginPath();
23895
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
23896
+ context.fillStyle = '#ffffff';
23897
+ context.fill();
23898
+ context.restore();
23899
+ context.save();
23900
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
23901
+ context.rotate(rotation);
23902
+ context.beginPath();
23903
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
23904
+ context.strokeStyle = `${palette.shadow}cc`;
23905
+ context.lineWidth = projectedRadiusX * 0.16;
23906
+ context.stroke();
23907
+ context.beginPath();
23908
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
23909
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
23910
+ context.strokeStyle = `${palette.shadow}73`;
23911
+ context.lineWidth = projectedRadiusX * 0.14;
23912
+ context.lineCap = 'round';
23913
+ context.stroke();
23914
+ if (eyeStyle.lowerLidOpacity > 0) {
23915
+ context.beginPath();
23916
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23917
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23918
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
23919
+ context.lineWidth = projectedRadiusX * 0.08;
23920
+ context.lineCap = 'round';
23921
+ context.stroke();
23922
+ }
23923
+ context.restore();
23924
+ }
23925
+ /**
23926
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
23927
+ *
23928
+ * @private helper of the 3D octopus avatar visuals
23929
+ */
23930
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
23931
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
23932
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
23933
+ return;
23934
+ }
23935
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
23936
+ context.beginPath();
23937
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
23938
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
23939
+ context.strokeStyle = `${palette.ink}b8`;
23940
+ context.lineWidth = Math.max(1.1, size * 0.009);
23941
+ context.lineCap = 'round';
23942
+ context.stroke();
23943
+ }
23944
+ /**
23945
+ * Draws one filled projected quad.
23946
+ *
23947
+ * @private helper of the 3D octopus avatar visuals
23948
+ */
23949
+ function drawProjectedQuad(context, corners, fillStyle) {
23950
+ context.beginPath();
23951
+ context.moveTo(corners[0].x, corners[0].y);
23952
+ context.lineTo(corners[1].x, corners[1].y);
23953
+ context.lineTo(corners[2].x, corners[2].y);
23954
+ context.lineTo(corners[3].x, corners[3].y);
23955
+ context.closePath();
23956
+ context.fillStyle = fillStyle;
23957
+ context.fill();
23958
+ }
23959
+ /**
23960
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
23961
+ *
23962
+ * @private helper of the 3D octopus avatar visuals
23963
+ */
23964
+ function formatAlphaHex(opacity) {
23965
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
23966
+ .toString(16)
23967
+ .padStart(2, '0');
23968
+ }
23969
+
23734
23970
  /* eslint-disable no-magic-numbers */
23735
23971
  /**
23736
23972
  * Light direction used by the organic 3D octopus shading.
23737
23973
  *
23738
23974
  * @private helper of `octopus3dAvatarVisual`
23739
23975
  */
23740
- const LIGHT_DIRECTION = normalizeVector3({
23976
+ const LIGHT_DIRECTION$1 = normalizeVector3({
23741
23977
  x: 0.48,
23742
23978
  y: -0.62,
23743
23979
  z: 0.94,
@@ -23850,17 +24086,17 @@ const octopus3dAvatarVisual = {
23850
24086
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
23851
24087
  drawTentacleStroke(context, tentacleStroke, palette);
23852
24088
  }
23853
- drawProjectedEye(context, {
24089
+ drawProjectedOrganicEye(context, {
23854
24090
  x: -faceEyeSpacing,
23855
24091
  y: faceEyeYOffset,
23856
24092
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
23857
24093
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
23858
- drawProjectedEye(context, {
24094
+ drawProjectedOrganicEye(context, {
23859
24095
  x: faceEyeSpacing,
23860
24096
  y: faceEyeYOffset,
23861
24097
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
23862
24098
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
23863
- drawProjectedMouth(context, [
24099
+ drawProjectedOrganicMouth(context, [
23864
24100
  {
23865
24101
  x: -mouthHalfWidth,
23866
24102
  y: mouthY,
@@ -23947,7 +24183,7 @@ function resolveVisibleEllipsoidPatches(options) {
23947
24183
  corners: projectedCorners,
23948
24184
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
23949
24185
  transformedCorners.length,
23950
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
24186
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
23951
24187
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
23952
24188
  outlineColor,
23953
24189
  });
@@ -24129,128 +24365,260 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
24129
24365
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
24130
24366
  return Math.sqrt(remainingDepthRatio) * radiusZ;
24131
24367
  }
24368
+
24369
+ /* eslint-disable no-magic-numbers */
24132
24370
  /**
24133
- * Draws one projected eye on the turned octopus mantle.
24371
+ * Light direction used by the single-mesh octopus shading.
24134
24372
  *
24135
- * @private helper of `octopus3dAvatarVisual`
24373
+ * @private helper of `octopus3d2AvatarVisual`
24136
24374
  */
24137
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
24138
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
24139
- if (centerScenePoint.z <= center.z) {
24140
- return;
24141
- }
24142
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
24143
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
24144
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
24145
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
24146
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
24147
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
24148
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
24149
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
24150
- return;
24151
- }
24152
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
24153
- radiusX: projectedRadiusX,
24154
- radiusY: projectedRadiusY,
24155
- timeMs,
24156
- phase,
24157
- interaction,
24158
- });
24159
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
24375
+ const LIGHT_DIRECTION = normalizeVector3({
24376
+ x: 0.38,
24377
+ y: -0.6,
24378
+ z: 0.98,
24379
+ });
24380
+ /**
24381
+ * Octopus 3D 2 avatar visual.
24382
+ *
24383
+ * @private built-in avatar visual
24384
+ */
24385
+ const octopus3d2AvatarVisual = {
24386
+ id: 'octopus3d2',
24387
+ title: 'Octopus 3D 2',
24388
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
24389
+ isAnimated: true,
24390
+ supportsPointerTracking: true,
24391
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
24392
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24393
+ const animationRandom = createRandom('octopus3d2-animation-profile');
24394
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
24395
+ const animationPhase = animationRandom() * Math.PI * 2;
24396
+ const sceneCenterX = size * 0.5;
24397
+ const sceneCenterY = size * 0.575;
24398
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
24399
+ const meshCenter = {
24400
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
24401
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
24402
+ z: interaction.intensity * size * 0.018,
24403
+ };
24404
+ const rotationY = -0.14 +
24405
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
24406
+ interaction.bodyOffsetX * 0.2 +
24407
+ interaction.gazeX * 0.78;
24408
+ const rotationX = -0.06 +
24409
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
24410
+ interaction.bodyOffsetY * 0.08 -
24411
+ interaction.gazeY * 0.34;
24412
+ const surfaceOptions = {
24413
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
24414
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
24415
+ radiusZ: size *
24416
+ morphologyProfile.body.bodyRadiusRatio *
24417
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
24418
+ morphologyProfile,
24419
+ timeMs,
24420
+ animationPhase,
24421
+ };
24422
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
24423
+ ...surfaceOptions,
24424
+ center: meshCenter,
24425
+ rotationX,
24426
+ rotationY,
24427
+ sceneCenterX,
24428
+ sceneCenterY,
24429
+ size,
24430
+ palette,
24431
+ });
24432
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
24433
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
24434
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
24435
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
24436
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
24437
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
24438
+ drawAvatarFrame(context, size, palette);
24439
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
24440
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
24441
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
24442
+ drawBlobbySurfacePatch(context, surfacePatch);
24443
+ }
24444
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
24445
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
24446
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
24447
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
24448
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24449
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24450
+ drawProjectedOrganicMouth(context, [
24451
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24452
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
24453
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
24454
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
24455
+ },
24456
+ };
24457
+ /**
24458
+ * Draws the deep-water glow behind the continuous octopus mesh.
24459
+ *
24460
+ * @private helper of `octopus3d2AvatarVisual`
24461
+ */
24462
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
24463
+ 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);
24464
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
24465
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
24466
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
24467
+ context.fillStyle = glowGradient;
24468
+ context.fillRect(0, 0, size, size);
24469
+ 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);
24470
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
24471
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
24472
+ context.fillStyle = lowerGradient;
24473
+ context.fillRect(0, 0, size, size);
24474
+ }
24475
+ /**
24476
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
24477
+ *
24478
+ * @private helper of `octopus3d2AvatarVisual`
24479
+ */
24480
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24160
24481
  context.save();
24161
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
24162
- context.rotate(rotation);
24163
- context.beginPath();
24164
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
24165
- context.fillStyle = '#f8fbff';
24166
- context.fill();
24167
- context.clip();
24168
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
24169
- irisGradient.addColorStop(0, palette.highlight);
24170
- irisGradient.addColorStop(0.56, palette.secondary);
24171
- irisGradient.addColorStop(1, palette.shadow);
24172
- context.beginPath();
24173
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
24174
- context.fillStyle = irisGradient;
24175
- context.fill();
24482
+ context.fillStyle = `${palette.shadow}66`;
24483
+ context.filter = `blur(${size * 0.024}px)`;
24176
24484
  context.beginPath();
24177
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
24178
- context.fillStyle = palette.ink;
24485
+ 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);
24179
24486
  context.fill();
24180
- context.beginPath();
24181
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
24182
- context.fillStyle = '#ffffff';
24183
- context.fill();
24184
- context.restore();
24185
- context.save();
24186
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
24187
- context.rotate(rotation);
24188
- context.beginPath();
24189
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
24190
- context.strokeStyle = `${palette.shadow}cc`;
24191
- context.lineWidth = projectedRadiusX * 0.16;
24192
- context.stroke();
24193
- context.beginPath();
24194
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
24195
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
24196
- context.strokeStyle = `${palette.shadow}73`;
24197
- context.lineWidth = projectedRadiusX * 0.14;
24198
- context.lineCap = 'round';
24199
- context.stroke();
24200
- if (eyeStyle.lowerLidOpacity > 0) {
24201
- context.beginPath();
24202
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
24203
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
24204
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
24205
- context.lineWidth = projectedRadiusX * 0.08;
24206
- context.lineCap = 'round';
24207
- context.stroke();
24208
- }
24209
24487
  context.restore();
24210
24488
  }
24211
24489
  /**
24212
- * Draws a subtle projected mouth arc across the front of the mantle.
24490
+ * Resolves all visible projected patches for the single blobby octopus mesh.
24213
24491
  *
24214
- * @private helper of `octopus3dAvatarVisual`
24492
+ * @private helper of `octopus3d2AvatarVisual`
24215
24493
  */
24216
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
24217
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
24218
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
24219
- return;
24494
+ function resolveVisibleBlobbyOctopusPatches(options) {
24495
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
24496
+ const latitudePatchCount = 12;
24497
+ const longitudePatchCount = 24;
24498
+ const surfacePatches = [];
24499
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
24500
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
24501
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
24502
+ const centerLatitude = (startLatitude + endLatitude) / 2;
24503
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
24504
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
24505
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
24506
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
24507
+ const centerLongitude = (startLongitude + endLongitude) / 2;
24508
+ const localCorners = [
24509
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
24510
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
24511
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
24512
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
24513
+ ];
24514
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
24515
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
24516
+ if (surfaceNormal.z <= 0.01) {
24517
+ continue;
24518
+ }
24519
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
24520
+ surfacePatches.push({
24521
+ corners: projectedCorners,
24522
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
24523
+ transformedCorners.length,
24524
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
24525
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
24526
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
24527
+ });
24528
+ }
24220
24529
  }
24221
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
24222
- context.beginPath();
24223
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
24224
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
24225
- context.strokeStyle = `${palette.ink}b8`;
24226
- context.lineWidth = Math.max(1.1, size * 0.009);
24227
- context.lineCap = 'round';
24228
- context.stroke();
24530
+ return surfacePatches;
24229
24531
  }
24230
24532
  /**
24231
- * Draws one filled projected quad.
24533
+ * Samples one point on the continuous Octopus 3D 2 surface.
24534
+ *
24535
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
24536
+ * instead of switching to separately rendered tentacles.
24537
+ *
24538
+ * @private helper of `octopus3d2AvatarVisual`
24539
+ */
24540
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
24541
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
24542
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
24543
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
24544
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
24545
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
24546
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
24547
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
24548
+ const horizontalScale = 1.02 +
24549
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
24550
+ upperBlend * 0.08;
24551
+ const depthScale = 1.04 +
24552
+ upperBlend * 0.16 +
24553
+ Math.max(0, Math.cos(longitude)) * 0.1 +
24554
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
24555
+ Math.max(0, -Math.cos(longitude)) * 0.04;
24556
+ const lowerDrop = skirtEnvelope *
24557
+ radiusY *
24558
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
24559
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
24560
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
24561
+ return {
24562
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
24563
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
24564
+ upperBlend * radiusY * 0.1 +
24565
+ lowerDrop +
24566
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
24567
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
24568
+ };
24569
+ }
24570
+ /**
24571
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
24232
24572
  *
24233
- * @private helper of `octopus3dAvatarVisual`
24573
+ * @private helper of `octopus3d2AvatarVisual`
24234
24574
  */
24235
- function drawProjectedQuad(context, corners, fillStyle) {
24236
- context.beginPath();
24237
- context.moveTo(corners[0].x, corners[0].y);
24238
- context.lineTo(corners[1].x, corners[1].y);
24239
- context.lineTo(corners[2].x, corners[2].y);
24240
- context.lineTo(corners[3].x, corners[3].y);
24241
- context.closePath();
24242
- context.fillStyle = fillStyle;
24243
- context.fill();
24575
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
24576
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
24577
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
24244
24578
  }
24245
24579
  /**
24246
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
24580
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
24247
24581
  *
24248
- * @private helper of `octopus3dAvatarVisual`
24582
+ * @private helper of `octopus3d2AvatarVisual`
24249
24583
  */
24250
- function formatAlphaHex(opacity) {
24251
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
24252
- .toString(16)
24253
- .padStart(2, '0');
24584
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
24585
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
24586
+ if (tonalProgress < 0.16) {
24587
+ return palette.highlight;
24588
+ }
24589
+ if (tonalProgress < 0.34) {
24590
+ return palette.secondary;
24591
+ }
24592
+ if (tonalProgress < 0.72) {
24593
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
24594
+ }
24595
+ return `${palette.shadow}f2`;
24596
+ }
24597
+ /**
24598
+ * Draws one projected patch with soft octopus shading.
24599
+ *
24600
+ * @private helper of `octopus3d2AvatarVisual`
24601
+ */
24602
+ function drawBlobbySurfacePatch(context, surfacePatch) {
24603
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
24604
+ if (surfacePatch.lightIntensity > 0) {
24605
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
24606
+ }
24607
+ else if (surfacePatch.lightIntensity < 0) {
24608
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
24609
+ }
24610
+ context.save();
24611
+ context.beginPath();
24612
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
24613
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
24614
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
24615
+ }
24616
+ context.closePath();
24617
+ context.strokeStyle = surfacePatch.outlineColor;
24618
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
24619
+ context.lineJoin = 'round';
24620
+ context.stroke();
24621
+ context.restore();
24254
24622
  }
24255
24623
 
24256
24624
  /* eslint-disable no-magic-numbers */
@@ -25022,6 +25390,7 @@ const AVATAR_VISUALS = [
25022
25390
  octopus2AvatarVisual,
25023
25391
  octopus3AvatarVisual,
25024
25392
  octopus3dAvatarVisual,
25393
+ octopus3d2AvatarVisual,
25025
25394
  asciiOctopusAvatarVisual,
25026
25395
  minecraftAvatarVisual,
25027
25396
  minecraft2AvatarVisual,
@@ -30434,11 +30803,11 @@ function buildCandidateEncodings(options) {
30434
30803
  });
30435
30804
  }
30436
30805
  /**
30437
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
30806
+ * Prepares one attachment for best-effort text decoding.
30438
30807
  *
30439
- * @private internal utility for shared text decoding
30808
+ * @private function of decodeAttachmentAsText
30440
30809
  */
30441
- function decodeAttachmentAsText(input, options = {}) {
30810
+ function createDecodeAttachmentPreparation(input, options) {
30442
30811
  var _a;
30443
30812
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
30444
30813
  const forceText = options.forceText === true;
@@ -30452,54 +30821,102 @@ function decodeAttachmentAsText(input, options = {}) {
30452
30821
  const inspection = inspectBytes(truncatedBytes);
30453
30822
  const trustedTextMime = isTrustedTextMimeType(mimeType);
30454
30823
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
30824
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
30455
30825
  if (isTruncated) {
30456
30826
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
30457
30827
  }
30458
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
30459
- if (shouldTreatAsBinary && !forceText) {
30460
- warnings.push('File content looks binary, so text decoding was skipped.');
30461
- return {
30462
- text: '',
30463
- encodingUsed: 'binary',
30464
- confidence: 1,
30465
- warnings,
30466
- wasBinary: true,
30467
- isTruncated,
30468
- };
30828
+ return {
30829
+ warnings,
30830
+ charset,
30831
+ bom,
30832
+ inspection,
30833
+ truncatedBytes,
30834
+ isTruncated,
30835
+ forceText,
30836
+ shouldTreatAsBinary,
30837
+ };
30838
+ }
30839
+ /**
30840
+ * Returns an early result when the attachment should stay classified as binary.
30841
+ *
30842
+ * @private function of decodeAttachmentAsText
30843
+ */
30844
+ function createBinaryDecodeResult(preparation) {
30845
+ if (!preparation.shouldTreatAsBinary) {
30846
+ return null;
30469
30847
  }
30470
- if (shouldTreatAsBinary && forceText) {
30471
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
30848
+ if (preparation.forceText) {
30849
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
30850
+ return null;
30472
30851
  }
30473
- if (charset && !isSupportedEncoding(charset)) {
30474
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
30852
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
30853
+ return {
30854
+ text: '',
30855
+ encodingUsed: 'binary',
30856
+ confidence: 1,
30857
+ warnings: preparation.warnings,
30858
+ wasBinary: true,
30859
+ isTruncated: preparation.isTruncated,
30860
+ };
30861
+ }
30862
+ /**
30863
+ * Warns when the declared charset cannot be used by the runtime decoder.
30864
+ *
30865
+ * @private function of decodeAttachmentAsText
30866
+ */
30867
+ function addUnsupportedCharsetWarning(preparation) {
30868
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
30869
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
30475
30870
  }
30476
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
30477
- const candidates = buildCandidateEncodings({
30478
- mimeType,
30479
- charset: charset && isSupportedEncoding(charset) ? charset : null,
30480
- bom,
30481
- inspection,
30482
- });
30483
- const decodedCandidates = candidates
30871
+ }
30872
+ /**
30873
+ * Returns the byte slice that should actually be decoded as text.
30874
+ *
30875
+ * @private function of decodeAttachmentAsText
30876
+ */
30877
+ function getBytesToDecode(preparation) {
30878
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
30879
+ }
30880
+ /**
30881
+ * Decodes all candidate encodings and sorts the successful results by score.
30882
+ *
30883
+ * @private function of decodeAttachmentAsText
30884
+ */
30885
+ function decodeAttachmentCandidates(preparation) {
30886
+ return buildCandidateEncodings({
30887
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
30888
+ bom: preparation.bom,
30889
+ inspection: preparation.inspection,
30890
+ })
30484
30891
  .map(({ encoding, source }) => {
30485
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
30892
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
30486
30893
  return decoded ? { ...decoded, source } : null;
30487
30894
  })
30488
30895
  .filter((candidate) => candidate !== null)
30489
30896
  .sort((left, right) => left.score - right.score);
30490
- const bestCandidate = decodedCandidates[0];
30491
- if (!bestCandidate) {
30492
- warnings.push('No supported text decoder was available.');
30493
- return {
30494
- text: '',
30495
- encodingUsed: 'binary',
30496
- confidence: 0,
30497
- warnings,
30498
- wasBinary: true,
30499
- isTruncated,
30500
- };
30501
- }
30502
- const secondBestCandidate = decodedCandidates[1];
30897
+ }
30898
+ /**
30899
+ * Returns the fallback result used when no text decoder could be applied.
30900
+ *
30901
+ * @private function of decodeAttachmentAsText
30902
+ */
30903
+ function createNoDecoderAvailableResult(preparation) {
30904
+ preparation.warnings.push('No supported text decoder was available.');
30905
+ return {
30906
+ text: '',
30907
+ encodingUsed: 'binary',
30908
+ confidence: 0,
30909
+ warnings: preparation.warnings,
30910
+ wasBinary: true,
30911
+ isTruncated: preparation.isTruncated,
30912
+ };
30913
+ }
30914
+ /**
30915
+ * Estimates confidence for the winning decoded text candidate.
30916
+ *
30917
+ * @private function of decodeAttachmentAsText
30918
+ */
30919
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
30503
30920
  const baseConfidence = bestCandidate.source === 'bom'
30504
30921
  ? 1
30505
30922
  : bestCandidate.source === 'charset'
@@ -30512,27 +30929,62 @@ function decodeAttachmentAsText(input, options = {}) {
30512
30929
  ? 0.82
30513
30930
  : 0.62;
30514
30931
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
30515
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
30932
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
30933
+ }
30934
+ /**
30935
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
30936
+ *
30937
+ * @private function of decodeAttachmentAsText
30938
+ */
30939
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
30516
30940
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
30517
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
30941
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
30518
30942
  }
30519
30943
  if (bestCandidate.source === 'heuristic' &&
30520
30944
  bestCandidate.encoding === 'utf-8' &&
30521
30945
  bestCandidate.replacementCount > 0) {
30522
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
30946
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
30523
30947
  }
30524
30948
  if (confidence < 0.6) {
30525
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
30949
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
30526
30950
  }
30951
+ }
30952
+ /**
30953
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
30954
+ *
30955
+ * @private function of decodeAttachmentAsText
30956
+ */
30957
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
30527
30958
  return {
30528
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
30959
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
30529
30960
  encodingUsed: bestCandidate.encoding,
30530
30961
  confidence,
30531
- warnings,
30962
+ warnings: preparation.warnings,
30532
30963
  wasBinary: false,
30533
- isTruncated,
30964
+ isTruncated: preparation.isTruncated,
30534
30965
  };
30535
30966
  }
30967
+ /**
30968
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
30969
+ *
30970
+ * @private internal utility for shared text decoding
30971
+ */
30972
+ function decodeAttachmentAsText(input, options = {}) {
30973
+ const preparation = createDecodeAttachmentPreparation(input, options);
30974
+ const binaryResult = createBinaryDecodeResult(preparation);
30975
+ if (binaryResult) {
30976
+ return binaryResult;
30977
+ }
30978
+ addUnsupportedCharsetWarning(preparation);
30979
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
30980
+ const bestCandidate = decodedCandidates[0];
30981
+ if (!bestCandidate) {
30982
+ return createNoDecoderAvailableResult(preparation);
30983
+ }
30984
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
30985
+ addDecodeWarnings(bestCandidate, confidence, preparation);
30986
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
30987
+ }
30536
30988
 
30537
30989
  /**
30538
30990
  * Base GitHub API URL.
@@ -39240,10 +39692,7 @@ function createAvailableProviderMessage(llmToolStatus, index, env) {
39240
39692
  * @private internal function of `$registeredLlmToolsMessage`
39241
39693
  */
39242
39694
  function createProviderStatusMessages(llmToolStatus, env) {
39243
- return [
39244
- createInstallationStatusMessage(llmToolStatus),
39245
- createConfigurationStatusMessage(llmToolStatus, env),
39246
- ];
39695
+ return [createInstallationStatusMessage(llmToolStatus), createConfigurationStatusMessage(llmToolStatus, env)];
39247
39696
  }
39248
39697
  /**
39249
39698
  * Creates the installation-status sentence for one provider.