@promptbook/wizard 0.112.0-73 → 0.112.0-80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +9 -9
  2. package/esm/index.es.js +823 -374
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  5. package/esm/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  6. package/esm/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  7. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  8. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  9. package/esm/src/book-components/Chat/save/index.d.ts +2 -2
  10. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  11. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  13. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  14. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  15. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  16. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  18. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  19. package/esm/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  20. package/esm/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  21. package/esm/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  22. package/esm/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  23. package/esm/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  24. package/esm/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  25. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  26. package/esm/src/cli/cli-commands/agents-server.d.ts +8 -0
  27. package/esm/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  28. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  29. package/esm/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  30. package/esm/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  31. package/esm/src/utils/color/Color.d.ts +4 -44
  32. package/esm/src/utils/color/ColorValue.d.ts +55 -0
  33. package/esm/src/utils/color/isHexColorString.d.ts +10 -0
  34. package/esm/src/utils/color/parseColorString.d.ts +11 -0
  35. package/esm/src/version.d.ts +1 -1
  36. package/package.json +2 -2
  37. package/umd/index.umd.js +823 -374
  38. package/umd/index.umd.js.map +1 -1
  39. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  40. package/umd/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  41. package/umd/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  42. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  43. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  44. package/umd/src/book-components/Chat/save/index.d.ts +2 -2
  45. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  46. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  47. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  48. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  49. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  50. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  51. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  52. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  53. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  54. package/umd/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  55. package/umd/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  56. package/umd/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  57. package/umd/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  58. package/umd/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  59. package/umd/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  60. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  61. package/umd/src/cli/cli-commands/agents-server.d.ts +8 -0
  62. package/umd/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  63. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  64. package/umd/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  65. package/umd/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  66. package/umd/src/utils/color/Color.d.ts +4 -44
  67. package/umd/src/utils/color/ColorValue.d.ts +55 -0
  68. package/umd/src/utils/color/isHexColorString.d.ts +10 -0
  69. package/umd/src/utils/color/parseColorString.d.ts +11 -0
  70. package/umd/src/version.d.ts +1 -1
  71. package/esm/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  72. package/esm/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
  73. package/umd/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  74. package/umd/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
package/umd/index.umd.js CHANGED
@@ -49,7 +49,7 @@
49
49
  * @generated
50
50
  * @see https://github.com/webgptorg/promptbook
51
51
  */
52
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
52
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-80';
53
53
  /**
54
54
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
55
55
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -313,6 +313,111 @@
313
313
  }
314
314
  }
315
315
 
316
+ /**
317
+ * Shared immutable channel storage and serialization helpers for `Color`.
318
+ *
319
+ * @private base class of Color
320
+ */
321
+ class ColorValue {
322
+ constructor(red, green, blue, alpha = 255) {
323
+ this.red = red;
324
+ this.green = green;
325
+ this.blue = blue;
326
+ this.alpha = alpha;
327
+ checkChannelValue('Red', red);
328
+ checkChannelValue('Green', green);
329
+ checkChannelValue('Blue', blue);
330
+ checkChannelValue('Alpha', alpha);
331
+ }
332
+ /**
333
+ * Shortcut for `red` property
334
+ * Number from 0 to 255
335
+ * @alias red
336
+ */
337
+ get r() {
338
+ return this.red;
339
+ }
340
+ /**
341
+ * Shortcut for `green` property
342
+ * Number from 0 to 255
343
+ * @alias green
344
+ */
345
+ get g() {
346
+ return this.green;
347
+ }
348
+ /**
349
+ * Shortcut for `blue` property
350
+ * Number from 0 to 255
351
+ * @alias blue
352
+ */
353
+ get b() {
354
+ return this.blue;
355
+ }
356
+ /**
357
+ * Shortcut for `alpha` property
358
+ * Number from 0 (transparent) to 255 (opaque)
359
+ * @alias alpha
360
+ */
361
+ get a() {
362
+ return this.alpha;
363
+ }
364
+ /**
365
+ * Shortcut for `alpha` property
366
+ * Number from 0 (transparent) to 255 (opaque)
367
+ * @alias alpha
368
+ */
369
+ get opacity() {
370
+ return this.alpha;
371
+ }
372
+ /**
373
+ * Shortcut for 1-`alpha` property
374
+ */
375
+ get transparency() {
376
+ return 255 - this.alpha;
377
+ }
378
+ clone() {
379
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
380
+ }
381
+ toString() {
382
+ return this.toHex();
383
+ }
384
+ toHex() {
385
+ if (this.alpha === 255) {
386
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
387
+ .toString(16)
388
+ .padStart(2, '0')}`;
389
+ }
390
+ else {
391
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
392
+ .toString(16)
393
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
394
+ }
395
+ }
396
+ toRgb() {
397
+ if (this.alpha === 255) {
398
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
399
+ }
400
+ else {
401
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
402
+ }
403
+ }
404
+ toHsl() {
405
+ throw new Error(`Getting HSL is not implemented`);
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Checks if the given value is a valid hex color string
411
+ *
412
+ * @param value - value to check
413
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
414
+ *
415
+ * @private function of Color
416
+ */
417
+ function isHexColorString(value) {
418
+ 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));
419
+ }
420
+
316
421
  /**
317
422
  * Constant for short hex lengths.
318
423
  */
@@ -524,16 +629,53 @@
524
629
 
525
630
  /**
526
631
  * Pattern matching hsl regex.
632
+ *
633
+ * @private function of Color
527
634
  */
528
635
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
529
636
  /**
530
637
  * Pattern matching RGB regex.
638
+ *
639
+ * @private function of Color
531
640
  */
532
641
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
533
642
  /**
534
643
  * Pattern matching rgba regex.
644
+ *
645
+ * @private function of Color
535
646
  */
536
647
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
648
+ /**
649
+ * Parses a supported color string into RGBA channels.
650
+ *
651
+ * @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`,...
652
+ * @returns RGBA channel values.
653
+ *
654
+ * @private function of Color
655
+ */
656
+ function parseColorString(color) {
657
+ const trimmed = color.trim();
658
+ const cssColor = CSS_COLORS[trimmed];
659
+ if (cssColor) {
660
+ return parseColorString(cssColor);
661
+ }
662
+ else if (isHexColorString(trimmed)) {
663
+ return parseHexColor(trimmed);
664
+ }
665
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
666
+ return parseHslColor(trimmed);
667
+ }
668
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
669
+ return parseRgbColor(trimmed);
670
+ }
671
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
672
+ return parseRgbaColor(trimmed);
673
+ }
674
+ else {
675
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
676
+ }
677
+ }
678
+
537
679
  /**
538
680
  * Color object represents an RGB color with alpha channel
539
681
  *
@@ -541,7 +683,7 @@
541
683
  *
542
684
  * @public exported from `@promptbook/color`
543
685
  */
544
- class Color {
686
+ class Color extends ColorValue {
545
687
  /**
546
688
  * Creates a new Color instance from miscellaneous formats
547
689
  * - It can receive Color instance and just return the same instance
@@ -614,25 +756,7 @@
614
756
  * @returns Color object
615
757
  */
616
758
  static fromString(color) {
617
- const trimmed = color.trim();
618
- if (CSS_COLORS[trimmed]) {
619
- return Color.fromString(CSS_COLORS[trimmed]);
620
- }
621
- else if (Color.isHexColorString(trimmed)) {
622
- return Color.fromHex(trimmed);
623
- }
624
- if (HSL_REGEX_PATTERN.test(trimmed)) {
625
- return Color.fromHsl(trimmed);
626
- }
627
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
628
- return Color.fromRgbString(trimmed);
629
- }
630
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
631
- return Color.fromRgbaString(trimmed);
632
- }
633
- else {
634
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
635
- }
759
+ return Color.fromColorChannels(parseColorString(color));
636
760
  }
637
761
  /**
638
762
  * Gets common color
@@ -662,8 +786,7 @@
662
786
  * @returns Color object
663
787
  */
664
788
  static fromHex(hex) {
665
- const { red, green, blue, alpha } = parseHexColor(hex);
666
- return take(new Color(red, green, blue, alpha));
789
+ return Color.fromColorChannels(parseHexColor(hex));
667
790
  }
668
791
  /**
669
792
  * Creates a new Color instance from color in hsl format
@@ -672,8 +795,7 @@
672
795
  * @returns Color object
673
796
  */
674
797
  static fromHsl(hsl) {
675
- const { red, green, blue, alpha } = parseHslColor(hsl);
676
- return take(new Color(red, green, blue, alpha));
798
+ return Color.fromColorChannels(parseHslColor(hsl));
677
799
  }
678
800
  /**
679
801
  * Creates a new Color instance from color in rgb format
@@ -682,8 +804,7 @@
682
804
  * @returns Color object
683
805
  */
684
806
  static fromRgbString(rgb) {
685
- const { red, green, blue, alpha } = parseRgbColor(rgb);
686
- return take(new Color(red, green, blue, alpha));
807
+ return Color.fromColorChannels(parseRgbColor(rgb));
687
808
  }
688
809
  /**
689
810
  * Creates a new Color instance from color in rbga format
@@ -692,8 +813,7 @@
692
813
  * @returns Color object
693
814
  */
694
815
  static fromRgbaString(rgba) {
695
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
696
- return take(new Color(red, green, blue, alpha));
816
+ return Color.fromColorChannels(parseRgbaColor(rgba));
697
817
  }
698
818
  /**
699
819
  * Creates a new Color for color channels values
@@ -705,7 +825,7 @@
705
825
  * @returns Color object
706
826
  */
707
827
  static fromValues(red, green, blue, alpha = 255) {
708
- return take(new Color(red, green, blue, alpha));
828
+ return Color.fromColorChannels({ red, green, blue, alpha });
709
829
  }
710
830
  /**
711
831
  * Checks if the given value is a valid Color object.
@@ -738,8 +858,7 @@
738
858
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
739
859
  */
740
860
  static isHexColorString(value) {
741
- return (typeof value === 'string' &&
742
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
861
+ return isHexColorString(value);
743
862
  }
744
863
  /**
745
864
  * Creates new Color object
@@ -752,89 +871,13 @@
752
871
  * @param alpha number from 0 (transparent) to 255 (opaque)
753
872
  */
754
873
  constructor(red, green, blue, alpha = 255) {
755
- this.red = red;
756
- this.green = green;
757
- this.blue = blue;
758
- this.alpha = alpha;
759
- checkChannelValue('Red', red);
760
- checkChannelValue('Green', green);
761
- checkChannelValue('Blue', blue);
762
- checkChannelValue('Alpha', alpha);
763
- }
764
- /**
765
- * Shortcut for `red` property
766
- * Number from 0 to 255
767
- * @alias red
768
- */
769
- get r() {
770
- return this.red;
771
- }
772
- /**
773
- * Shortcut for `green` property
774
- * Number from 0 to 255
775
- * @alias green
776
- */
777
- get g() {
778
- return this.green;
779
- }
780
- /**
781
- * Shortcut for `blue` property
782
- * Number from 0 to 255
783
- * @alias blue
784
- */
785
- get b() {
786
- return this.blue;
787
- }
788
- /**
789
- * Shortcut for `alpha` property
790
- * Number from 0 (transparent) to 255 (opaque)
791
- * @alias alpha
792
- */
793
- get a() {
794
- return this.alpha;
795
- }
796
- /**
797
- * Shortcut for `alpha` property
798
- * Number from 0 (transparent) to 255 (opaque)
799
- * @alias alpha
800
- */
801
- get opacity() {
802
- return this.alpha;
803
- }
804
- /**
805
- * Shortcut for 1-`alpha` property
806
- */
807
- get transparency() {
808
- return 255 - this.alpha;
809
- }
810
- clone() {
811
- return take(new Color(this.red, this.green, this.blue, this.alpha));
812
- }
813
- toString() {
814
- return this.toHex();
815
- }
816
- toHex() {
817
- if (this.alpha === 255) {
818
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
819
- .toString(16)
820
- .padStart(2, '0')}`;
821
- }
822
- else {
823
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
824
- .toString(16)
825
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
826
- }
874
+ super(red, green, blue, alpha);
827
875
  }
828
- toRgb() {
829
- if (this.alpha === 255) {
830
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
831
- }
832
- else {
833
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
834
- }
876
+ createColor(red, green, blue, alpha) {
877
+ return new Color(red, green, blue, alpha);
835
878
  }
836
- toHsl() {
837
- throw new Error(`Getting HSL is not implemented`);
879
+ static fromColorChannels({ red, green, blue, alpha }) {
880
+ return take(new Color(red, green, blue, alpha));
838
881
  }
839
882
  }
840
883
 
@@ -1510,120 +1553,183 @@
1510
1553
  * @public exported from `@promptbook/utils`
1511
1554
  */
1512
1555
  function checkSerializableAsJson(options) {
1513
- const { value, name, message } = options;
1556
+ checkSerializableValue(options);
1557
+ }
1558
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1559
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1560
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1561
+ /**
1562
+ * Checks one value and dispatches to the appropriate specialized validator.
1563
+ *
1564
+ * @private function of `checkSerializableAsJson`
1565
+ */
1566
+ function checkSerializableValue(options) {
1567
+ const { value } = options;
1568
+ if (isSerializablePrimitive(value)) {
1569
+ return;
1570
+ }
1514
1571
  if (value === undefined) {
1515
- throw new UnexpectedError(`${name} is undefined`);
1572
+ throw new UnexpectedError(`${options.name} is undefined`);
1516
1573
  }
1517
- else if (value === null) {
1518
- return;
1574
+ if (typeof value === 'symbol') {
1575
+ throw new UnexpectedError(`${options.name} is symbol`);
1519
1576
  }
1520
- else if (typeof value === 'boolean') {
1521
- return;
1577
+ if (typeof value === 'function') {
1578
+ throw new UnexpectedError(`${options.name} is function`);
1522
1579
  }
1523
- else if (typeof value === 'number' && !isNaN(value)) {
1580
+ if (Array.isArray(value)) {
1581
+ checkSerializableArray(options, value);
1524
1582
  return;
1525
1583
  }
1526
- else if (typeof value === 'string') {
1584
+ if (value !== null && typeof value === 'object') {
1585
+ checkSerializableObject(options, value);
1527
1586
  return;
1528
1587
  }
1529
- else if (typeof value === 'symbol') {
1530
- throw new UnexpectedError(`${name} is symbol`);
1531
- }
1532
- else if (typeof value === 'function') {
1533
- throw new UnexpectedError(`${name} is function`);
1534
- }
1535
- else if (typeof value === 'object' && Array.isArray(value)) {
1536
- for (let i = 0; i < value.length; i++) {
1537
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1538
- }
1588
+ throwUnknownTypeError(options);
1589
+ }
1590
+ /**
1591
+ * Checks the primitive values that are directly JSON serializable.
1592
+ *
1593
+ * @private function of `checkSerializableAsJson`
1594
+ */
1595
+ function isSerializablePrimitive(value) {
1596
+ return (value === null ||
1597
+ typeof value === 'boolean' ||
1598
+ (typeof value === 'number' && !isNaN(value)) ||
1599
+ typeof value === 'string');
1600
+ }
1601
+ /**
1602
+ * Recursively checks JSON array items.
1603
+ *
1604
+ * @private function of `checkSerializableAsJson`
1605
+ */
1606
+ function checkSerializableArray(context, arrayValue) {
1607
+ for (let index = 0; index < arrayValue.length; index++) {
1608
+ checkSerializableAsJson({
1609
+ ...context,
1610
+ name: `${context.name}[${index}]`,
1611
+ value: arrayValue[index],
1612
+ });
1539
1613
  }
1540
- else if (typeof value === 'object') {
1541
- if (value instanceof Date) {
1542
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1543
- \`${name}\` is Date
1614
+ }
1615
+ /**
1616
+ * Checks object-like values and dispatches special unsupported built-ins.
1617
+ *
1618
+ * @private function of `checkSerializableAsJson`
1619
+ */
1620
+ function checkSerializableObject(context, objectValue) {
1621
+ checkUnsupportedObjectType(context, objectValue);
1622
+ checkSerializableObjectEntries(context, objectValue);
1623
+ assertJsonStringificationSucceeds(context, objectValue);
1624
+ }
1625
+ /**
1626
+ * Rejects built-in objects that must be converted before JSON serialization.
1627
+ *
1628
+ * @private function of `checkSerializableAsJson`
1629
+ */
1630
+ function checkUnsupportedObjectType(context, objectValue) {
1631
+ if (objectValue instanceof Date) {
1632
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1633
+ \`${context.name}\` is Date
1544
1634
 
1545
- Use \`string_date_iso8601\` instead
1635
+ Use \`string_date_iso8601\` instead
1546
1636
 
1547
- Additional message for \`${name}\`:
1548
- ${block(message || '(nothing)')}
1549
- `));
1550
- }
1551
- else if (value instanceof Map) {
1552
- throw new UnexpectedError(`${name} is Map`);
1553
- }
1554
- else if (value instanceof Set) {
1555
- throw new UnexpectedError(`${name} is Set`);
1556
- }
1557
- else if (value instanceof RegExp) {
1558
- throw new UnexpectedError(`${name} is RegExp`);
1559
- }
1560
- else if (value instanceof Error) {
1561
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1562
- \`${name}\` is unserialized Error
1637
+ Additional message for \`${context.name}\`:
1638
+ ${block(context.message || '(nothing)')}
1639
+ `));
1640
+ }
1641
+ if (objectValue instanceof Map) {
1642
+ throw new UnexpectedError(`${context.name} is Map`);
1643
+ }
1644
+ if (objectValue instanceof Set) {
1645
+ throw new UnexpectedError(`${context.name} is Set`);
1646
+ }
1647
+ if (objectValue instanceof RegExp) {
1648
+ throw new UnexpectedError(`${context.name} is RegExp`);
1649
+ }
1650
+ if (objectValue instanceof Error) {
1651
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1652
+ \`${context.name}\` is unserialized Error
1563
1653
 
1564
- Use function \`serializeError\`
1654
+ Use function \`serializeError\`
1565
1655
 
1566
- Additional message for \`${name}\`:
1567
- ${block(message || '(nothing)')}
1656
+ Additional message for \`${context.name}\`:
1657
+ ${block(context.message || '(nothing)')}
1568
1658
 
1569
- `));
1659
+ `));
1660
+ }
1661
+ }
1662
+ /**
1663
+ * Recursively checks object properties while preserving omitted `undefined` keys.
1664
+ *
1665
+ * @private function of `checkSerializableAsJson`
1666
+ */
1667
+ function checkSerializableObjectEntries(context, objectValue) {
1668
+ for (const [subName, subValue] of Object.entries(objectValue)) {
1669
+ if (subValue === undefined) {
1670
+ // Note: undefined in object is serializable - it is just omitted
1671
+ continue;
1570
1672
  }
1571
- else {
1572
- for (const [subName, subValue] of Object.entries(value)) {
1573
- if (subValue === undefined) {
1574
- // Note: undefined in object is serializable - it is just omitted
1575
- continue;
1576
- }
1577
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1578
- }
1579
- try {
1580
- JSON.stringify(value); // <- TODO: [0]
1581
- }
1582
- catch (error) {
1583
- assertsError(error);
1584
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1585
- \`${name}\` is not serializable
1673
+ checkSerializableAsJson({
1674
+ ...context,
1675
+ name: `${context.name}.${subName}`,
1676
+ value: subValue,
1677
+ });
1678
+ }
1679
+ }
1680
+ /**
1681
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
1682
+ *
1683
+ * @private function of `checkSerializableAsJson`
1684
+ */
1685
+ function assertJsonStringificationSucceeds(context, objectValue) {
1686
+ try {
1687
+ JSON.stringify(objectValue); // <- TODO: [0]
1688
+ }
1689
+ catch (error) {
1690
+ assertsError(error);
1691
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1692
+ \`${context.name}\` is not serializable
1586
1693
 
1587
- ${block(error.stack || error.message)}
1694
+ ${block(error.stack || error.message)}
1588
1695
 
1589
- Additional message for \`${name}\`:
1590
- ${block(message || '(nothing)')}
1591
- `));
1696
+ Additional message for \`${context.name}\`:
1697
+ ${block(context.message || '(nothing)')}
1698
+ `));
1699
+ }
1700
+ /*
1701
+ TODO: [0] Is there some more elegant way to check circular references?
1702
+ const seen = new Set();
1703
+ const stack = [{ value }];
1704
+ while (stack.length > 0) {
1705
+ const { value } = stack.pop()!;
1706
+ if (typeof value === 'object' && value !== null) {
1707
+ if (seen.has(value)) {
1708
+ throw new UnexpectedError(`${name} has circular reference`);
1592
1709
  }
1593
- /*
1594
- TODO: [0] Is there some more elegant way to check circular references?
1595
- const seen = new Set();
1596
- const stack = [{ value }];
1597
- while (stack.length > 0) {
1598
- const { value } = stack.pop()!;
1599
- if (typeof value === 'object' && value !== null) {
1600
- if (seen.has(value)) {
1601
- throw new UnexpectedError(`${name} has circular reference`);
1602
- }
1603
- seen.add(value);
1604
- if (Array.isArray(value)) {
1605
- stack.push(...value.map((value) => ({ value })));
1606
- } else {
1607
- stack.push(...Object.values(value).map((value) => ({ value })));
1608
- }
1609
- }
1710
+ seen.add(value);
1711
+ if (Array.isArray(value)) {
1712
+ stack.push(...value.map((value) => ({ value })));
1713
+ } else {
1714
+ stack.push(...Object.values(value).map((value) => ({ value })));
1610
1715
  }
1611
- */
1612
- return;
1613
1716
  }
1614
1717
  }
1615
- else {
1616
- throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1617
- \`${name}\` is unknown type
1718
+ */
1719
+ }
1720
+ /**
1721
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
1722
+ *
1723
+ * @private function of `checkSerializableAsJson`
1724
+ */
1725
+ function throwUnknownTypeError(context) {
1726
+ throw new UnexpectedError(_spaceTrim.spaceTrim((block) => `
1727
+ \`${context.name}\` is unknown type
1618
1728
 
1619
- Additional message for \`${name}\`:
1620
- ${block(message || '(nothing)')}
1621
- `));
1622
- }
1729
+ Additional message for \`${context.name}\`:
1730
+ ${block(context.message || '(nothing)')}
1731
+ `));
1623
1732
  }
1624
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
1625
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
1626
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
1627
1733
 
1628
1734
  /**
1629
1735
  * Creates a deep clone of the given object
@@ -5984,7 +6090,10 @@
5984
6090
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
5985
6091
 
5986
6092
  Available models:
5987
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
6093
+ ${block(this.options
6094
+ .getHardcodedModels()
6095
+ .map(({ modelName }) => `- "${modelName}"`)
6096
+ .join('\n'))}
5988
6097
 
5989
6098
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
5990
6099
 
@@ -6338,7 +6447,8 @@
6338
6447
  };
6339
6448
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
6340
6449
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
6341
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
6450
+ rawPromptContent +=
6451
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
6342
6452
  }
6343
6453
  const rawRequest = {
6344
6454
  ...modelSettings,
@@ -6443,7 +6553,9 @@
6443
6553
  * Schedules one request through the shared limiter and retry policy.
6444
6554
  */
6445
6555
  async executeRateLimitedRequest(requestFn) {
6446
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
6556
+ return this.limiter
6557
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
6558
+ .catch((error) => {
6447
6559
  assertsError(error);
6448
6560
  if (this.options.isVerbose) {
6449
6561
  console.info(colors__default["default"].bgRed('error'), error);
@@ -9183,7 +9295,9 @@
9183
9295
  pollingState.lastProgressAtMs = nowMs;
9184
9296
  pollingState.lastProgressKey = progressKey;
9185
9297
  }
9186
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
9298
+ if (this.options.isVerbose &&
9299
+ (statusCountsKey !== pollingState.lastCountsKey ||
9300
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
9187
9301
  console.info('[🤰]', 'Vector store file batch status', {
9188
9302
  vectorStoreId,
9189
9303
  batchId,
@@ -11256,7 +11370,7 @@
11256
11370
  */
11257
11371
  function createPostprocessingCommands(task) {
11258
11372
  var _a;
11259
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
11373
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
11260
11374
  }
11261
11375
  /**
11262
11376
  * Collects expectation commands.
@@ -11786,8 +11900,7 @@
11786
11900
  * @private internal utility of `validatePipeline`
11787
11901
  */
11788
11902
  function validateTaskSupportsJokers(task, pipelineIdentification) {
11789
- if (task.format ||
11790
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
11903
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
11791
11904
  return;
11792
11905
  }
11793
11906
  throw new PipelineLogicError(_spaceTrim.spaceTrim((block) => `
@@ -14982,9 +15095,7 @@
14982
15095
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
14983
15096
 
14984
15097
  Result:
14985
- ${block(failure.result === null
14986
- ? 'null'
14987
- : quoteMultilineText(_spaceTrim.spaceTrim(failure.result)))}
15098
+ ${block(failure.result === null ? 'null' : quoteMultilineText(_spaceTrim.spaceTrim(failure.result)))}
14988
15099
  `;
14989
15100
  }))
14990
15101
  .join('\n\n---\n\n');
@@ -22470,7 +22581,7 @@
22470
22581
  *
22471
22582
  * @private helper of `minecraft2AvatarVisual`
22472
22583
  */
22473
- const LIGHT_DIRECTION$1 = normalizeVector3({
22584
+ const LIGHT_DIRECTION$2 = normalizeVector3({
22474
22585
  x: 0.4,
22475
22586
  y: -0.65,
22476
22587
  z: 0.92,
@@ -22682,7 +22793,7 @@
22682
22793
  corners: projectedCorners,
22683
22794
  texture: faceDefinition.texture,
22684
22795
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
22685
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
22796
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
22686
22797
  outlineColor: cuboid.outlineColor,
22687
22798
  };
22688
22799
  });
@@ -23742,13 +23853,138 @@
23742
23853
  context.restore();
23743
23854
  }
23744
23855
 
23856
+ /* eslint-disable no-magic-numbers */
23857
+ /**
23858
+ * Draws one projected eye on a rotated octopus surface.
23859
+ *
23860
+ * @private helper of the 3D octopus avatar visuals
23861
+ */
23862
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
23863
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
23864
+ if (centerScenePoint.z <= center.z) {
23865
+ return;
23866
+ }
23867
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
23868
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
23869
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
23870
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
23871
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
23872
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
23873
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
23874
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
23875
+ return;
23876
+ }
23877
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
23878
+ radiusX: projectedRadiusX,
23879
+ radiusY: projectedRadiusY,
23880
+ timeMs,
23881
+ phase,
23882
+ interaction,
23883
+ });
23884
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
23885
+ context.save();
23886
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
23887
+ context.rotate(rotation);
23888
+ context.beginPath();
23889
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
23890
+ context.fillStyle = '#f8fbff';
23891
+ context.fill();
23892
+ context.clip();
23893
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
23894
+ irisGradient.addColorStop(0, palette.highlight);
23895
+ irisGradient.addColorStop(0.56, palette.secondary);
23896
+ irisGradient.addColorStop(1, palette.shadow);
23897
+ context.beginPath();
23898
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
23899
+ context.fillStyle = irisGradient;
23900
+ context.fill();
23901
+ context.beginPath();
23902
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
23903
+ context.fillStyle = palette.ink;
23904
+ context.fill();
23905
+ context.beginPath();
23906
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
23907
+ context.fillStyle = '#ffffff';
23908
+ context.fill();
23909
+ context.restore();
23910
+ context.save();
23911
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
23912
+ context.rotate(rotation);
23913
+ context.beginPath();
23914
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
23915
+ context.strokeStyle = `${palette.shadow}cc`;
23916
+ context.lineWidth = projectedRadiusX * 0.16;
23917
+ context.stroke();
23918
+ context.beginPath();
23919
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
23920
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
23921
+ context.strokeStyle = `${palette.shadow}73`;
23922
+ context.lineWidth = projectedRadiusX * 0.14;
23923
+ context.lineCap = 'round';
23924
+ context.stroke();
23925
+ if (eyeStyle.lowerLidOpacity > 0) {
23926
+ context.beginPath();
23927
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23928
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
23929
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
23930
+ context.lineWidth = projectedRadiusX * 0.08;
23931
+ context.lineCap = 'round';
23932
+ context.stroke();
23933
+ }
23934
+ context.restore();
23935
+ }
23936
+ /**
23937
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
23938
+ *
23939
+ * @private helper of the 3D octopus avatar visuals
23940
+ */
23941
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
23942
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
23943
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
23944
+ return;
23945
+ }
23946
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
23947
+ context.beginPath();
23948
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
23949
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
23950
+ context.strokeStyle = `${palette.ink}b8`;
23951
+ context.lineWidth = Math.max(1.1, size * 0.009);
23952
+ context.lineCap = 'round';
23953
+ context.stroke();
23954
+ }
23955
+ /**
23956
+ * Draws one filled projected quad.
23957
+ *
23958
+ * @private helper of the 3D octopus avatar visuals
23959
+ */
23960
+ function drawProjectedQuad(context, corners, fillStyle) {
23961
+ context.beginPath();
23962
+ context.moveTo(corners[0].x, corners[0].y);
23963
+ context.lineTo(corners[1].x, corners[1].y);
23964
+ context.lineTo(corners[2].x, corners[2].y);
23965
+ context.lineTo(corners[3].x, corners[3].y);
23966
+ context.closePath();
23967
+ context.fillStyle = fillStyle;
23968
+ context.fill();
23969
+ }
23970
+ /**
23971
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
23972
+ *
23973
+ * @private helper of the 3D octopus avatar visuals
23974
+ */
23975
+ function formatAlphaHex(opacity) {
23976
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
23977
+ .toString(16)
23978
+ .padStart(2, '0');
23979
+ }
23980
+
23745
23981
  /* eslint-disable no-magic-numbers */
23746
23982
  /**
23747
23983
  * Light direction used by the organic 3D octopus shading.
23748
23984
  *
23749
23985
  * @private helper of `octopus3dAvatarVisual`
23750
23986
  */
23751
- const LIGHT_DIRECTION = normalizeVector3({
23987
+ const LIGHT_DIRECTION$1 = normalizeVector3({
23752
23988
  x: 0.48,
23753
23989
  y: -0.62,
23754
23990
  z: 0.94,
@@ -23861,17 +24097,17 @@
23861
24097
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
23862
24098
  drawTentacleStroke(context, tentacleStroke, palette);
23863
24099
  }
23864
- drawProjectedEye(context, {
24100
+ drawProjectedOrganicEye(context, {
23865
24101
  x: -faceEyeSpacing,
23866
24102
  y: faceEyeYOffset,
23867
24103
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
23868
24104
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
23869
- drawProjectedEye(context, {
24105
+ drawProjectedOrganicEye(context, {
23870
24106
  x: faceEyeSpacing,
23871
24107
  y: faceEyeYOffset,
23872
24108
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
23873
24109
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
23874
- drawProjectedMouth(context, [
24110
+ drawProjectedOrganicMouth(context, [
23875
24111
  {
23876
24112
  x: -mouthHalfWidth,
23877
24113
  y: mouthY,
@@ -23958,7 +24194,7 @@
23958
24194
  corners: projectedCorners,
23959
24195
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
23960
24196
  transformedCorners.length,
23961
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
24197
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
23962
24198
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
23963
24199
  outlineColor,
23964
24200
  });
@@ -24140,128 +24376,260 @@
24140
24376
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
24141
24377
  return Math.sqrt(remainingDepthRatio) * radiusZ;
24142
24378
  }
24379
+
24380
+ /* eslint-disable no-magic-numbers */
24143
24381
  /**
24144
- * Draws one projected eye on the turned octopus mantle.
24382
+ * Light direction used by the single-mesh octopus shading.
24145
24383
  *
24146
- * @private helper of `octopus3dAvatarVisual`
24384
+ * @private helper of `octopus3d2AvatarVisual`
24147
24385
  */
24148
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
24149
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
24150
- if (centerScenePoint.z <= center.z) {
24151
- return;
24152
- }
24153
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
24154
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
24155
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
24156
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
24157
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
24158
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
24159
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
24160
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
24161
- return;
24162
- }
24163
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
24164
- radiusX: projectedRadiusX,
24165
- radiusY: projectedRadiusY,
24166
- timeMs,
24167
- phase,
24168
- interaction,
24169
- });
24170
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
24386
+ const LIGHT_DIRECTION = normalizeVector3({
24387
+ x: 0.38,
24388
+ y: -0.6,
24389
+ z: 0.98,
24390
+ });
24391
+ /**
24392
+ * Octopus 3D 2 avatar visual.
24393
+ *
24394
+ * @private built-in avatar visual
24395
+ */
24396
+ const octopus3d2AvatarVisual = {
24397
+ id: 'octopus3d2',
24398
+ title: 'Octopus 3D 2',
24399
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
24400
+ isAnimated: true,
24401
+ supportsPointerTracking: true,
24402
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
24403
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24404
+ const animationRandom = createRandom('octopus3d2-animation-profile');
24405
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
24406
+ const animationPhase = animationRandom() * Math.PI * 2;
24407
+ const sceneCenterX = size * 0.5;
24408
+ const sceneCenterY = size * 0.575;
24409
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
24410
+ const meshCenter = {
24411
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
24412
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
24413
+ z: interaction.intensity * size * 0.018,
24414
+ };
24415
+ const rotationY = -0.14 +
24416
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
24417
+ interaction.bodyOffsetX * 0.2 +
24418
+ interaction.gazeX * 0.78;
24419
+ const rotationX = -0.06 +
24420
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
24421
+ interaction.bodyOffsetY * 0.08 -
24422
+ interaction.gazeY * 0.34;
24423
+ const surfaceOptions = {
24424
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
24425
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
24426
+ radiusZ: size *
24427
+ morphologyProfile.body.bodyRadiusRatio *
24428
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
24429
+ morphologyProfile,
24430
+ timeMs,
24431
+ animationPhase,
24432
+ };
24433
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
24434
+ ...surfaceOptions,
24435
+ center: meshCenter,
24436
+ rotationX,
24437
+ rotationY,
24438
+ sceneCenterX,
24439
+ sceneCenterY,
24440
+ size,
24441
+ palette,
24442
+ });
24443
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
24444
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
24445
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
24446
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
24447
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
24448
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
24449
+ drawAvatarFrame(context, size, palette);
24450
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
24451
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
24452
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
24453
+ drawBlobbySurfacePatch(context, surfacePatch);
24454
+ }
24455
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
24456
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
24457
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
24458
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
24459
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24460
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24461
+ drawProjectedOrganicMouth(context, [
24462
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24463
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
24464
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
24465
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
24466
+ },
24467
+ };
24468
+ /**
24469
+ * Draws the deep-water glow behind the continuous octopus mesh.
24470
+ *
24471
+ * @private helper of `octopus3d2AvatarVisual`
24472
+ */
24473
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
24474
+ 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);
24475
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
24476
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
24477
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
24478
+ context.fillStyle = glowGradient;
24479
+ context.fillRect(0, 0, size, size);
24480
+ 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);
24481
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
24482
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
24483
+ context.fillStyle = lowerGradient;
24484
+ context.fillRect(0, 0, size, size);
24485
+ }
24486
+ /**
24487
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
24488
+ *
24489
+ * @private helper of `octopus3d2AvatarVisual`
24490
+ */
24491
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24171
24492
  context.save();
24172
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
24173
- context.rotate(rotation);
24174
- context.beginPath();
24175
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
24176
- context.fillStyle = '#f8fbff';
24177
- context.fill();
24178
- context.clip();
24179
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
24180
- irisGradient.addColorStop(0, palette.highlight);
24181
- irisGradient.addColorStop(0.56, palette.secondary);
24182
- irisGradient.addColorStop(1, palette.shadow);
24183
- context.beginPath();
24184
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
24185
- context.fillStyle = irisGradient;
24186
- context.fill();
24493
+ context.fillStyle = `${palette.shadow}66`;
24494
+ context.filter = `blur(${size * 0.024}px)`;
24187
24495
  context.beginPath();
24188
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
24189
- context.fillStyle = palette.ink;
24496
+ 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);
24190
24497
  context.fill();
24191
- context.beginPath();
24192
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
24193
- context.fillStyle = '#ffffff';
24194
- context.fill();
24195
- context.restore();
24196
- context.save();
24197
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
24198
- context.rotate(rotation);
24199
- context.beginPath();
24200
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
24201
- context.strokeStyle = `${palette.shadow}cc`;
24202
- context.lineWidth = projectedRadiusX * 0.16;
24203
- context.stroke();
24204
- context.beginPath();
24205
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
24206
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
24207
- context.strokeStyle = `${palette.shadow}73`;
24208
- context.lineWidth = projectedRadiusX * 0.14;
24209
- context.lineCap = 'round';
24210
- context.stroke();
24211
- if (eyeStyle.lowerLidOpacity > 0) {
24212
- context.beginPath();
24213
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
24214
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
24215
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
24216
- context.lineWidth = projectedRadiusX * 0.08;
24217
- context.lineCap = 'round';
24218
- context.stroke();
24219
- }
24220
24498
  context.restore();
24221
24499
  }
24222
24500
  /**
24223
- * Draws a subtle projected mouth arc across the front of the mantle.
24501
+ * Resolves all visible projected patches for the single blobby octopus mesh.
24224
24502
  *
24225
- * @private helper of `octopus3dAvatarVisual`
24503
+ * @private helper of `octopus3d2AvatarVisual`
24226
24504
  */
24227
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
24228
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
24229
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
24230
- return;
24505
+ function resolveVisibleBlobbyOctopusPatches(options) {
24506
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
24507
+ const latitudePatchCount = 12;
24508
+ const longitudePatchCount = 24;
24509
+ const surfacePatches = [];
24510
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
24511
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
24512
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
24513
+ const centerLatitude = (startLatitude + endLatitude) / 2;
24514
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
24515
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
24516
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
24517
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
24518
+ const centerLongitude = (startLongitude + endLongitude) / 2;
24519
+ const localCorners = [
24520
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
24521
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
24522
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
24523
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
24524
+ ];
24525
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
24526
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
24527
+ if (surfaceNormal.z <= 0.01) {
24528
+ continue;
24529
+ }
24530
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
24531
+ surfacePatches.push({
24532
+ corners: projectedCorners,
24533
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
24534
+ transformedCorners.length,
24535
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
24536
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
24537
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
24538
+ });
24539
+ }
24231
24540
  }
24232
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
24233
- context.beginPath();
24234
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
24235
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
24236
- context.strokeStyle = `${palette.ink}b8`;
24237
- context.lineWidth = Math.max(1.1, size * 0.009);
24238
- context.lineCap = 'round';
24239
- context.stroke();
24541
+ return surfacePatches;
24240
24542
  }
24241
24543
  /**
24242
- * Draws one filled projected quad.
24544
+ * Samples one point on the continuous Octopus 3D 2 surface.
24545
+ *
24546
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
24547
+ * instead of switching to separately rendered tentacles.
24548
+ *
24549
+ * @private helper of `octopus3d2AvatarVisual`
24550
+ */
24551
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
24552
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
24553
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
24554
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
24555
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
24556
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
24557
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
24558
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
24559
+ const horizontalScale = 1.02 +
24560
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
24561
+ upperBlend * 0.08;
24562
+ const depthScale = 1.04 +
24563
+ upperBlend * 0.16 +
24564
+ Math.max(0, Math.cos(longitude)) * 0.1 +
24565
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
24566
+ Math.max(0, -Math.cos(longitude)) * 0.04;
24567
+ const lowerDrop = skirtEnvelope *
24568
+ radiusY *
24569
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
24570
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
24571
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
24572
+ return {
24573
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
24574
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
24575
+ upperBlend * radiusY * 0.1 +
24576
+ lowerDrop +
24577
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
24578
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
24579
+ };
24580
+ }
24581
+ /**
24582
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
24243
24583
  *
24244
- * @private helper of `octopus3dAvatarVisual`
24584
+ * @private helper of `octopus3d2AvatarVisual`
24245
24585
  */
24246
- function drawProjectedQuad(context, corners, fillStyle) {
24247
- context.beginPath();
24248
- context.moveTo(corners[0].x, corners[0].y);
24249
- context.lineTo(corners[1].x, corners[1].y);
24250
- context.lineTo(corners[2].x, corners[2].y);
24251
- context.lineTo(corners[3].x, corners[3].y);
24252
- context.closePath();
24253
- context.fillStyle = fillStyle;
24254
- context.fill();
24586
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
24587
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
24588
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
24255
24589
  }
24256
24590
  /**
24257
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
24591
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
24258
24592
  *
24259
- * @private helper of `octopus3dAvatarVisual`
24593
+ * @private helper of `octopus3d2AvatarVisual`
24260
24594
  */
24261
- function formatAlphaHex(opacity) {
24262
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
24263
- .toString(16)
24264
- .padStart(2, '0');
24595
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
24596
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
24597
+ if (tonalProgress < 0.16) {
24598
+ return palette.highlight;
24599
+ }
24600
+ if (tonalProgress < 0.34) {
24601
+ return palette.secondary;
24602
+ }
24603
+ if (tonalProgress < 0.72) {
24604
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
24605
+ }
24606
+ return `${palette.shadow}f2`;
24607
+ }
24608
+ /**
24609
+ * Draws one projected patch with soft octopus shading.
24610
+ *
24611
+ * @private helper of `octopus3d2AvatarVisual`
24612
+ */
24613
+ function drawBlobbySurfacePatch(context, surfacePatch) {
24614
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
24615
+ if (surfacePatch.lightIntensity > 0) {
24616
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
24617
+ }
24618
+ else if (surfacePatch.lightIntensity < 0) {
24619
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
24620
+ }
24621
+ context.save();
24622
+ context.beginPath();
24623
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
24624
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
24625
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
24626
+ }
24627
+ context.closePath();
24628
+ context.strokeStyle = surfacePatch.outlineColor;
24629
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
24630
+ context.lineJoin = 'round';
24631
+ context.stroke();
24632
+ context.restore();
24265
24633
  }
24266
24634
 
24267
24635
  /* eslint-disable no-magic-numbers */
@@ -25033,6 +25401,7 @@
25033
25401
  octopus2AvatarVisual,
25034
25402
  octopus3AvatarVisual,
25035
25403
  octopus3dAvatarVisual,
25404
+ octopus3d2AvatarVisual,
25036
25405
  asciiOctopusAvatarVisual,
25037
25406
  minecraftAvatarVisual,
25038
25407
  minecraft2AvatarVisual,
@@ -30445,11 +30814,11 @@
30445
30814
  });
30446
30815
  }
30447
30816
  /**
30448
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
30817
+ * Prepares one attachment for best-effort text decoding.
30449
30818
  *
30450
- * @private internal utility for shared text decoding
30819
+ * @private function of decodeAttachmentAsText
30451
30820
  */
30452
- function decodeAttachmentAsText(input, options = {}) {
30821
+ function createDecodeAttachmentPreparation(input, options) {
30453
30822
  var _a;
30454
30823
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
30455
30824
  const forceText = options.forceText === true;
@@ -30463,54 +30832,102 @@
30463
30832
  const inspection = inspectBytes(truncatedBytes);
30464
30833
  const trustedTextMime = isTrustedTextMimeType(mimeType);
30465
30834
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
30835
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
30466
30836
  if (isTruncated) {
30467
30837
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
30468
30838
  }
30469
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
30470
- if (shouldTreatAsBinary && !forceText) {
30471
- warnings.push('File content looks binary, so text decoding was skipped.');
30472
- return {
30473
- text: '',
30474
- encodingUsed: 'binary',
30475
- confidence: 1,
30476
- warnings,
30477
- wasBinary: true,
30478
- isTruncated,
30479
- };
30839
+ return {
30840
+ warnings,
30841
+ charset,
30842
+ bom,
30843
+ inspection,
30844
+ truncatedBytes,
30845
+ isTruncated,
30846
+ forceText,
30847
+ shouldTreatAsBinary,
30848
+ };
30849
+ }
30850
+ /**
30851
+ * Returns an early result when the attachment should stay classified as binary.
30852
+ *
30853
+ * @private function of decodeAttachmentAsText
30854
+ */
30855
+ function createBinaryDecodeResult(preparation) {
30856
+ if (!preparation.shouldTreatAsBinary) {
30857
+ return null;
30480
30858
  }
30481
- if (shouldTreatAsBinary && forceText) {
30482
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
30859
+ if (preparation.forceText) {
30860
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
30861
+ return null;
30483
30862
  }
30484
- if (charset && !isSupportedEncoding(charset)) {
30485
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
30863
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
30864
+ return {
30865
+ text: '',
30866
+ encodingUsed: 'binary',
30867
+ confidence: 1,
30868
+ warnings: preparation.warnings,
30869
+ wasBinary: true,
30870
+ isTruncated: preparation.isTruncated,
30871
+ };
30872
+ }
30873
+ /**
30874
+ * Warns when the declared charset cannot be used by the runtime decoder.
30875
+ *
30876
+ * @private function of decodeAttachmentAsText
30877
+ */
30878
+ function addUnsupportedCharsetWarning(preparation) {
30879
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
30880
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
30486
30881
  }
30487
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
30488
- const candidates = buildCandidateEncodings({
30489
- mimeType,
30490
- charset: charset && isSupportedEncoding(charset) ? charset : null,
30491
- bom,
30492
- inspection,
30493
- });
30494
- const decodedCandidates = candidates
30882
+ }
30883
+ /**
30884
+ * Returns the byte slice that should actually be decoded as text.
30885
+ *
30886
+ * @private function of decodeAttachmentAsText
30887
+ */
30888
+ function getBytesToDecode(preparation) {
30889
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
30890
+ }
30891
+ /**
30892
+ * Decodes all candidate encodings and sorts the successful results by score.
30893
+ *
30894
+ * @private function of decodeAttachmentAsText
30895
+ */
30896
+ function decodeAttachmentCandidates(preparation) {
30897
+ return buildCandidateEncodings({
30898
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
30899
+ bom: preparation.bom,
30900
+ inspection: preparation.inspection,
30901
+ })
30495
30902
  .map(({ encoding, source }) => {
30496
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
30903
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
30497
30904
  return decoded ? { ...decoded, source } : null;
30498
30905
  })
30499
30906
  .filter((candidate) => candidate !== null)
30500
30907
  .sort((left, right) => left.score - right.score);
30501
- const bestCandidate = decodedCandidates[0];
30502
- if (!bestCandidate) {
30503
- warnings.push('No supported text decoder was available.');
30504
- return {
30505
- text: '',
30506
- encodingUsed: 'binary',
30507
- confidence: 0,
30508
- warnings,
30509
- wasBinary: true,
30510
- isTruncated,
30511
- };
30512
- }
30513
- const secondBestCandidate = decodedCandidates[1];
30908
+ }
30909
+ /**
30910
+ * Returns the fallback result used when no text decoder could be applied.
30911
+ *
30912
+ * @private function of decodeAttachmentAsText
30913
+ */
30914
+ function createNoDecoderAvailableResult(preparation) {
30915
+ preparation.warnings.push('No supported text decoder was available.');
30916
+ return {
30917
+ text: '',
30918
+ encodingUsed: 'binary',
30919
+ confidence: 0,
30920
+ warnings: preparation.warnings,
30921
+ wasBinary: true,
30922
+ isTruncated: preparation.isTruncated,
30923
+ };
30924
+ }
30925
+ /**
30926
+ * Estimates confidence for the winning decoded text candidate.
30927
+ *
30928
+ * @private function of decodeAttachmentAsText
30929
+ */
30930
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
30514
30931
  const baseConfidence = bestCandidate.source === 'bom'
30515
30932
  ? 1
30516
30933
  : bestCandidate.source === 'charset'
@@ -30523,27 +30940,62 @@
30523
30940
  ? 0.82
30524
30941
  : 0.62;
30525
30942
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
30526
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
30943
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
30944
+ }
30945
+ /**
30946
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
30947
+ *
30948
+ * @private function of decodeAttachmentAsText
30949
+ */
30950
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
30527
30951
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
30528
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
30952
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
30529
30953
  }
30530
30954
  if (bestCandidate.source === 'heuristic' &&
30531
30955
  bestCandidate.encoding === 'utf-8' &&
30532
30956
  bestCandidate.replacementCount > 0) {
30533
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
30957
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
30534
30958
  }
30535
30959
  if (confidence < 0.6) {
30536
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
30960
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
30537
30961
  }
30962
+ }
30963
+ /**
30964
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
30965
+ *
30966
+ * @private function of decodeAttachmentAsText
30967
+ */
30968
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
30538
30969
  return {
30539
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
30970
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
30540
30971
  encodingUsed: bestCandidate.encoding,
30541
30972
  confidence,
30542
- warnings,
30973
+ warnings: preparation.warnings,
30543
30974
  wasBinary: false,
30544
- isTruncated,
30975
+ isTruncated: preparation.isTruncated,
30545
30976
  };
30546
30977
  }
30978
+ /**
30979
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
30980
+ *
30981
+ * @private internal utility for shared text decoding
30982
+ */
30983
+ function decodeAttachmentAsText(input, options = {}) {
30984
+ const preparation = createDecodeAttachmentPreparation(input, options);
30985
+ const binaryResult = createBinaryDecodeResult(preparation);
30986
+ if (binaryResult) {
30987
+ return binaryResult;
30988
+ }
30989
+ addUnsupportedCharsetWarning(preparation);
30990
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
30991
+ const bestCandidate = decodedCandidates[0];
30992
+ if (!bestCandidate) {
30993
+ return createNoDecoderAvailableResult(preparation);
30994
+ }
30995
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
30996
+ addDecodeWarnings(bestCandidate, confidence, preparation);
30997
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
30998
+ }
30547
30999
 
30548
31000
  /**
30549
31001
  * Base GitHub API URL.
@@ -39251,10 +39703,7 @@
39251
39703
  * @private internal function of `$registeredLlmToolsMessage`
39252
39704
  */
39253
39705
  function createProviderStatusMessages(llmToolStatus, env) {
39254
- return [
39255
- createInstallationStatusMessage(llmToolStatus),
39256
- createConfigurationStatusMessage(llmToolStatus, env),
39257
- ];
39706
+ return [createInstallationStatusMessage(llmToolStatus), createConfigurationStatusMessage(llmToolStatus, env)];
39258
39707
  }
39259
39708
  /**
39260
39709
  * Creates the installation-status sentence for one provider.