@promptbook/browser 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 +822 -370
  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 +822 -370
  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
@@ -29,7 +29,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
29
29
  * @generated
30
30
  * @see https://github.com/webgptorg/promptbook
31
31
  */
32
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
32
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
33
33
  /**
34
34
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
35
35
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -279,6 +279,111 @@ function checkChannelValue(channelName, value) {
279
279
  }
280
280
  }
281
281
 
282
+ /**
283
+ * Shared immutable channel storage and serialization helpers for `Color`.
284
+ *
285
+ * @private base class of Color
286
+ */
287
+ class ColorValue {
288
+ constructor(red, green, blue, alpha = 255) {
289
+ this.red = red;
290
+ this.green = green;
291
+ this.blue = blue;
292
+ this.alpha = alpha;
293
+ checkChannelValue('Red', red);
294
+ checkChannelValue('Green', green);
295
+ checkChannelValue('Blue', blue);
296
+ checkChannelValue('Alpha', alpha);
297
+ }
298
+ /**
299
+ * Shortcut for `red` property
300
+ * Number from 0 to 255
301
+ * @alias red
302
+ */
303
+ get r() {
304
+ return this.red;
305
+ }
306
+ /**
307
+ * Shortcut for `green` property
308
+ * Number from 0 to 255
309
+ * @alias green
310
+ */
311
+ get g() {
312
+ return this.green;
313
+ }
314
+ /**
315
+ * Shortcut for `blue` property
316
+ * Number from 0 to 255
317
+ * @alias blue
318
+ */
319
+ get b() {
320
+ return this.blue;
321
+ }
322
+ /**
323
+ * Shortcut for `alpha` property
324
+ * Number from 0 (transparent) to 255 (opaque)
325
+ * @alias alpha
326
+ */
327
+ get a() {
328
+ return this.alpha;
329
+ }
330
+ /**
331
+ * Shortcut for `alpha` property
332
+ * Number from 0 (transparent) to 255 (opaque)
333
+ * @alias alpha
334
+ */
335
+ get opacity() {
336
+ return this.alpha;
337
+ }
338
+ /**
339
+ * Shortcut for 1-`alpha` property
340
+ */
341
+ get transparency() {
342
+ return 255 - this.alpha;
343
+ }
344
+ clone() {
345
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
346
+ }
347
+ toString() {
348
+ return this.toHex();
349
+ }
350
+ toHex() {
351
+ if (this.alpha === 255) {
352
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
353
+ .toString(16)
354
+ .padStart(2, '0')}`;
355
+ }
356
+ else {
357
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
358
+ .toString(16)
359
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
360
+ }
361
+ }
362
+ toRgb() {
363
+ if (this.alpha === 255) {
364
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
365
+ }
366
+ else {
367
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
368
+ }
369
+ }
370
+ toHsl() {
371
+ throw new Error(`Getting HSL is not implemented`);
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Checks if the given value is a valid hex color string
377
+ *
378
+ * @param value - value to check
379
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
380
+ *
381
+ * @private function of Color
382
+ */
383
+ function isHexColorString(value) {
384
+ 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));
385
+ }
386
+
282
387
  /**
283
388
  * Constant for short hex lengths.
284
389
  */
@@ -490,16 +595,53 @@ function parseAlphaValue(value) {
490
595
 
491
596
  /**
492
597
  * Pattern matching hsl regex.
598
+ *
599
+ * @private function of Color
493
600
  */
494
601
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
495
602
  /**
496
603
  * Pattern matching RGB regex.
604
+ *
605
+ * @private function of Color
497
606
  */
498
607
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
499
608
  /**
500
609
  * Pattern matching rgba regex.
610
+ *
611
+ * @private function of Color
501
612
  */
502
613
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
614
+ /**
615
+ * Parses a supported color string into RGBA channels.
616
+ *
617
+ * @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`,...
618
+ * @returns RGBA channel values.
619
+ *
620
+ * @private function of Color
621
+ */
622
+ function parseColorString(color) {
623
+ const trimmed = color.trim();
624
+ const cssColor = CSS_COLORS[trimmed];
625
+ if (cssColor) {
626
+ return parseColorString(cssColor);
627
+ }
628
+ else if (isHexColorString(trimmed)) {
629
+ return parseHexColor(trimmed);
630
+ }
631
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
632
+ return parseHslColor(trimmed);
633
+ }
634
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
635
+ return parseRgbColor(trimmed);
636
+ }
637
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
638
+ return parseRgbaColor(trimmed);
639
+ }
640
+ else {
641
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
642
+ }
643
+ }
644
+
503
645
  /**
504
646
  * Color object represents an RGB color with alpha channel
505
647
  *
@@ -507,7 +649,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
507
649
  *
508
650
  * @public exported from `@promptbook/color`
509
651
  */
510
- class Color {
652
+ class Color extends ColorValue {
511
653
  /**
512
654
  * Creates a new Color instance from miscellaneous formats
513
655
  * - It can receive Color instance and just return the same instance
@@ -580,25 +722,7 @@ class Color {
580
722
  * @returns Color object
581
723
  */
582
724
  static fromString(color) {
583
- const trimmed = color.trim();
584
- if (CSS_COLORS[trimmed]) {
585
- return Color.fromString(CSS_COLORS[trimmed]);
586
- }
587
- else if (Color.isHexColorString(trimmed)) {
588
- return Color.fromHex(trimmed);
589
- }
590
- if (HSL_REGEX_PATTERN.test(trimmed)) {
591
- return Color.fromHsl(trimmed);
592
- }
593
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
594
- return Color.fromRgbString(trimmed);
595
- }
596
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
597
- return Color.fromRgbaString(trimmed);
598
- }
599
- else {
600
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
601
- }
725
+ return Color.fromColorChannels(parseColorString(color));
602
726
  }
603
727
  /**
604
728
  * Gets common color
@@ -628,8 +752,7 @@ class Color {
628
752
  * @returns Color object
629
753
  */
630
754
  static fromHex(hex) {
631
- const { red, green, blue, alpha } = parseHexColor(hex);
632
- return take(new Color(red, green, blue, alpha));
755
+ return Color.fromColorChannels(parseHexColor(hex));
633
756
  }
634
757
  /**
635
758
  * Creates a new Color instance from color in hsl format
@@ -638,8 +761,7 @@ class Color {
638
761
  * @returns Color object
639
762
  */
640
763
  static fromHsl(hsl) {
641
- const { red, green, blue, alpha } = parseHslColor(hsl);
642
- return take(new Color(red, green, blue, alpha));
764
+ return Color.fromColorChannels(parseHslColor(hsl));
643
765
  }
644
766
  /**
645
767
  * Creates a new Color instance from color in rgb format
@@ -648,8 +770,7 @@ class Color {
648
770
  * @returns Color object
649
771
  */
650
772
  static fromRgbString(rgb) {
651
- const { red, green, blue, alpha } = parseRgbColor(rgb);
652
- return take(new Color(red, green, blue, alpha));
773
+ return Color.fromColorChannels(parseRgbColor(rgb));
653
774
  }
654
775
  /**
655
776
  * Creates a new Color instance from color in rbga format
@@ -658,8 +779,7 @@ class Color {
658
779
  * @returns Color object
659
780
  */
660
781
  static fromRgbaString(rgba) {
661
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
662
- return take(new Color(red, green, blue, alpha));
782
+ return Color.fromColorChannels(parseRgbaColor(rgba));
663
783
  }
664
784
  /**
665
785
  * Creates a new Color for color channels values
@@ -671,7 +791,7 @@ class Color {
671
791
  * @returns Color object
672
792
  */
673
793
  static fromValues(red, green, blue, alpha = 255) {
674
- return take(new Color(red, green, blue, alpha));
794
+ return Color.fromColorChannels({ red, green, blue, alpha });
675
795
  }
676
796
  /**
677
797
  * Checks if the given value is a valid Color object.
@@ -704,8 +824,7 @@ class Color {
704
824
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
705
825
  */
706
826
  static isHexColorString(value) {
707
- return (typeof value === 'string' &&
708
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
827
+ return isHexColorString(value);
709
828
  }
710
829
  /**
711
830
  * Creates new Color object
@@ -718,89 +837,13 @@ class Color {
718
837
  * @param alpha number from 0 (transparent) to 255 (opaque)
719
838
  */
720
839
  constructor(red, green, blue, alpha = 255) {
721
- this.red = red;
722
- this.green = green;
723
- this.blue = blue;
724
- this.alpha = alpha;
725
- checkChannelValue('Red', red);
726
- checkChannelValue('Green', green);
727
- checkChannelValue('Blue', blue);
728
- checkChannelValue('Alpha', alpha);
840
+ super(red, green, blue, alpha);
729
841
  }
730
- /**
731
- * Shortcut for `red` property
732
- * Number from 0 to 255
733
- * @alias red
734
- */
735
- get r() {
736
- return this.red;
737
- }
738
- /**
739
- * Shortcut for `green` property
740
- * Number from 0 to 255
741
- * @alias green
742
- */
743
- get g() {
744
- return this.green;
745
- }
746
- /**
747
- * Shortcut for `blue` property
748
- * Number from 0 to 255
749
- * @alias blue
750
- */
751
- get b() {
752
- return this.blue;
753
- }
754
- /**
755
- * Shortcut for `alpha` property
756
- * Number from 0 (transparent) to 255 (opaque)
757
- * @alias alpha
758
- */
759
- get a() {
760
- return this.alpha;
842
+ createColor(red, green, blue, alpha) {
843
+ return new Color(red, green, blue, alpha);
761
844
  }
762
- /**
763
- * Shortcut for `alpha` property
764
- * Number from 0 (transparent) to 255 (opaque)
765
- * @alias alpha
766
- */
767
- get opacity() {
768
- return this.alpha;
769
- }
770
- /**
771
- * Shortcut for 1-`alpha` property
772
- */
773
- get transparency() {
774
- return 255 - this.alpha;
775
- }
776
- clone() {
777
- return take(new Color(this.red, this.green, this.blue, this.alpha));
778
- }
779
- toString() {
780
- return this.toHex();
781
- }
782
- toHex() {
783
- if (this.alpha === 255) {
784
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
785
- .toString(16)
786
- .padStart(2, '0')}`;
787
- }
788
- else {
789
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
790
- .toString(16)
791
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
792
- }
793
- }
794
- toRgb() {
795
- if (this.alpha === 255) {
796
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
797
- }
798
- else {
799
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
800
- }
801
- }
802
- toHsl() {
803
- throw new Error(`Getting HSL is not implemented`);
845
+ static fromColorChannels({ red, green, blue, alpha }) {
846
+ return take(new Color(red, green, blue, alpha));
804
847
  }
805
848
  }
806
849
 
@@ -6594,7 +6637,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
6594
6637
  *
6595
6638
  * @private helper of `minecraft2AvatarVisual`
6596
6639
  */
6597
- const LIGHT_DIRECTION$1 = normalizeVector3({
6640
+ const LIGHT_DIRECTION$2 = normalizeVector3({
6598
6641
  x: 0.4,
6599
6642
  y: -0.65,
6600
6643
  z: 0.92,
@@ -6806,7 +6849,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
6806
6849
  corners: projectedCorners,
6807
6850
  texture: faceDefinition.texture,
6808
6851
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
6809
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
6852
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
6810
6853
  outlineColor: cuboid.outlineColor,
6811
6854
  };
6812
6855
  });
@@ -7866,13 +7909,138 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
7866
7909
  context.restore();
7867
7910
  }
7868
7911
 
7912
+ /* eslint-disable no-magic-numbers */
7913
+ /**
7914
+ * Draws one projected eye on a rotated octopus surface.
7915
+ *
7916
+ * @private helper of the 3D octopus avatar visuals
7917
+ */
7918
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
7919
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
7920
+ if (centerScenePoint.z <= center.z) {
7921
+ return;
7922
+ }
7923
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
7924
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
7925
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
7926
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
7927
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
7928
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
7929
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
7930
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
7931
+ return;
7932
+ }
7933
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
7934
+ radiusX: projectedRadiusX,
7935
+ radiusY: projectedRadiusY,
7936
+ timeMs,
7937
+ phase,
7938
+ interaction,
7939
+ });
7940
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
7941
+ context.save();
7942
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
7943
+ context.rotate(rotation);
7944
+ context.beginPath();
7945
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
7946
+ context.fillStyle = '#f8fbff';
7947
+ context.fill();
7948
+ context.clip();
7949
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
7950
+ irisGradient.addColorStop(0, palette.highlight);
7951
+ irisGradient.addColorStop(0.56, palette.secondary);
7952
+ irisGradient.addColorStop(1, palette.shadow);
7953
+ context.beginPath();
7954
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
7955
+ context.fillStyle = irisGradient;
7956
+ context.fill();
7957
+ context.beginPath();
7958
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
7959
+ context.fillStyle = palette.ink;
7960
+ context.fill();
7961
+ context.beginPath();
7962
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
7963
+ context.fillStyle = '#ffffff';
7964
+ context.fill();
7965
+ context.restore();
7966
+ context.save();
7967
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
7968
+ context.rotate(rotation);
7969
+ context.beginPath();
7970
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
7971
+ context.strokeStyle = `${palette.shadow}cc`;
7972
+ context.lineWidth = projectedRadiusX * 0.16;
7973
+ context.stroke();
7974
+ context.beginPath();
7975
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
7976
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
7977
+ context.strokeStyle = `${palette.shadow}73`;
7978
+ context.lineWidth = projectedRadiusX * 0.14;
7979
+ context.lineCap = 'round';
7980
+ context.stroke();
7981
+ if (eyeStyle.lowerLidOpacity > 0) {
7982
+ context.beginPath();
7983
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
7984
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
7985
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
7986
+ context.lineWidth = projectedRadiusX * 0.08;
7987
+ context.lineCap = 'round';
7988
+ context.stroke();
7989
+ }
7990
+ context.restore();
7991
+ }
7992
+ /**
7993
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
7994
+ *
7995
+ * @private helper of the 3D octopus avatar visuals
7996
+ */
7997
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
7998
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
7999
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
8000
+ return;
8001
+ }
8002
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
8003
+ context.beginPath();
8004
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
8005
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
8006
+ context.strokeStyle = `${palette.ink}b8`;
8007
+ context.lineWidth = Math.max(1.1, size * 0.009);
8008
+ context.lineCap = 'round';
8009
+ context.stroke();
8010
+ }
8011
+ /**
8012
+ * Draws one filled projected quad.
8013
+ *
8014
+ * @private helper of the 3D octopus avatar visuals
8015
+ */
8016
+ function drawProjectedQuad(context, corners, fillStyle) {
8017
+ context.beginPath();
8018
+ context.moveTo(corners[0].x, corners[0].y);
8019
+ context.lineTo(corners[1].x, corners[1].y);
8020
+ context.lineTo(corners[2].x, corners[2].y);
8021
+ context.lineTo(corners[3].x, corners[3].y);
8022
+ context.closePath();
8023
+ context.fillStyle = fillStyle;
8024
+ context.fill();
8025
+ }
8026
+ /**
8027
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
8028
+ *
8029
+ * @private helper of the 3D octopus avatar visuals
8030
+ */
8031
+ function formatAlphaHex(opacity) {
8032
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
8033
+ .toString(16)
8034
+ .padStart(2, '0');
8035
+ }
8036
+
7869
8037
  /* eslint-disable no-magic-numbers */
7870
8038
  /**
7871
8039
  * Light direction used by the organic 3D octopus shading.
7872
8040
  *
7873
8041
  * @private helper of `octopus3dAvatarVisual`
7874
8042
  */
7875
- const LIGHT_DIRECTION = normalizeVector3({
8043
+ const LIGHT_DIRECTION$1 = normalizeVector3({
7876
8044
  x: 0.48,
7877
8045
  y: -0.62,
7878
8046
  z: 0.94,
@@ -7985,17 +8153,17 @@ const octopus3dAvatarVisual = {
7985
8153
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
7986
8154
  drawTentacleStroke(context, tentacleStroke, palette);
7987
8155
  }
7988
- drawProjectedEye(context, {
8156
+ drawProjectedOrganicEye(context, {
7989
8157
  x: -faceEyeSpacing,
7990
8158
  y: faceEyeYOffset,
7991
8159
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
7992
8160
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
7993
- drawProjectedEye(context, {
8161
+ drawProjectedOrganicEye(context, {
7994
8162
  x: faceEyeSpacing,
7995
8163
  y: faceEyeYOffset,
7996
8164
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
7997
8165
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
7998
- drawProjectedMouth(context, [
8166
+ drawProjectedOrganicMouth(context, [
7999
8167
  {
8000
8168
  x: -mouthHalfWidth,
8001
8169
  y: mouthY,
@@ -8082,7 +8250,7 @@ function resolveVisibleEllipsoidPatches(options) {
8082
8250
  corners: projectedCorners,
8083
8251
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
8084
8252
  transformedCorners.length,
8085
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
8253
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
8086
8254
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
8087
8255
  outlineColor,
8088
8256
  });
@@ -8264,128 +8432,260 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
8264
8432
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
8265
8433
  return Math.sqrt(remainingDepthRatio) * radiusZ;
8266
8434
  }
8435
+
8436
+ /* eslint-disable no-magic-numbers */
8267
8437
  /**
8268
- * Draws one projected eye on the turned octopus mantle.
8438
+ * Light direction used by the single-mesh octopus shading.
8269
8439
  *
8270
- * @private helper of `octopus3dAvatarVisual`
8440
+ * @private helper of `octopus3d2AvatarVisual`
8271
8441
  */
8272
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
8273
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
8274
- if (centerScenePoint.z <= center.z) {
8275
- return;
8276
- }
8277
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
8278
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
8279
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
8280
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
8281
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
8282
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
8283
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
8284
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
8285
- return;
8286
- }
8287
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
8288
- radiusX: projectedRadiusX,
8289
- radiusY: projectedRadiusY,
8290
- timeMs,
8291
- phase,
8292
- interaction,
8293
- });
8294
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
8442
+ const LIGHT_DIRECTION = normalizeVector3({
8443
+ x: 0.38,
8444
+ y: -0.6,
8445
+ z: 0.98,
8446
+ });
8447
+ /**
8448
+ * Octopus 3D 2 avatar visual.
8449
+ *
8450
+ * @private built-in avatar visual
8451
+ */
8452
+ const octopus3d2AvatarVisual = {
8453
+ id: 'octopus3d2',
8454
+ title: 'Octopus 3D 2',
8455
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
8456
+ isAnimated: true,
8457
+ supportsPointerTracking: true,
8458
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
8459
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
8460
+ const animationRandom = createRandom('octopus3d2-animation-profile');
8461
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
8462
+ const animationPhase = animationRandom() * Math.PI * 2;
8463
+ const sceneCenterX = size * 0.5;
8464
+ const sceneCenterY = size * 0.575;
8465
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
8466
+ const meshCenter = {
8467
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
8468
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
8469
+ z: interaction.intensity * size * 0.018,
8470
+ };
8471
+ const rotationY = -0.14 +
8472
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
8473
+ interaction.bodyOffsetX * 0.2 +
8474
+ interaction.gazeX * 0.78;
8475
+ const rotationX = -0.06 +
8476
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
8477
+ interaction.bodyOffsetY * 0.08 -
8478
+ interaction.gazeY * 0.34;
8479
+ const surfaceOptions = {
8480
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
8481
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
8482
+ radiusZ: size *
8483
+ morphologyProfile.body.bodyRadiusRatio *
8484
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
8485
+ morphologyProfile,
8486
+ timeMs,
8487
+ animationPhase,
8488
+ };
8489
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
8490
+ ...surfaceOptions,
8491
+ center: meshCenter,
8492
+ rotationX,
8493
+ rotationY,
8494
+ sceneCenterX,
8495
+ sceneCenterY,
8496
+ size,
8497
+ palette,
8498
+ });
8499
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
8500
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
8501
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
8502
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
8503
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
8504
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
8505
+ drawAvatarFrame(context, size, palette);
8506
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
8507
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
8508
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
8509
+ drawBlobbySurfacePatch(context, surfacePatch);
8510
+ }
8511
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
8512
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
8513
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
8514
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
8515
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
8516
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
8517
+ drawProjectedOrganicMouth(context, [
8518
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
8519
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
8520
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
8521
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
8522
+ },
8523
+ };
8524
+ /**
8525
+ * Draws the deep-water glow behind the continuous octopus mesh.
8526
+ *
8527
+ * @private helper of `octopus3d2AvatarVisual`
8528
+ */
8529
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
8530
+ 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);
8531
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
8532
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
8533
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
8534
+ context.fillStyle = glowGradient;
8535
+ context.fillRect(0, 0, size, size);
8536
+ 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);
8537
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
8538
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
8539
+ context.fillStyle = lowerGradient;
8540
+ context.fillRect(0, 0, size, size);
8541
+ }
8542
+ /**
8543
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
8544
+ *
8545
+ * @private helper of `octopus3d2AvatarVisual`
8546
+ */
8547
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
8295
8548
  context.save();
8296
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
8297
- context.rotate(rotation);
8298
- context.beginPath();
8299
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
8300
- context.fillStyle = '#f8fbff';
8301
- context.fill();
8302
- context.clip();
8303
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
8304
- irisGradient.addColorStop(0, palette.highlight);
8305
- irisGradient.addColorStop(0.56, palette.secondary);
8306
- irisGradient.addColorStop(1, palette.shadow);
8307
- context.beginPath();
8308
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
8309
- context.fillStyle = irisGradient;
8310
- context.fill();
8311
- context.beginPath();
8312
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
8313
- context.fillStyle = palette.ink;
8314
- context.fill();
8549
+ context.fillStyle = `${palette.shadow}66`;
8550
+ context.filter = `blur(${size * 0.024}px)`;
8315
8551
  context.beginPath();
8316
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
8317
- context.fillStyle = '#ffffff';
8552
+ 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);
8318
8553
  context.fill();
8319
8554
  context.restore();
8320
- context.save();
8321
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
8322
- context.rotate(rotation);
8323
- context.beginPath();
8324
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
8325
- context.strokeStyle = `${palette.shadow}cc`;
8326
- context.lineWidth = projectedRadiusX * 0.16;
8327
- context.stroke();
8328
- context.beginPath();
8329
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
8330
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
8331
- context.strokeStyle = `${palette.shadow}73`;
8332
- context.lineWidth = projectedRadiusX * 0.14;
8333
- context.lineCap = 'round';
8334
- context.stroke();
8335
- if (eyeStyle.lowerLidOpacity > 0) {
8336
- context.beginPath();
8337
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
8338
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
8339
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
8340
- context.lineWidth = projectedRadiusX * 0.08;
8341
- context.lineCap = 'round';
8342
- context.stroke();
8555
+ }
8556
+ /**
8557
+ * Resolves all visible projected patches for the single blobby octopus mesh.
8558
+ *
8559
+ * @private helper of `octopus3d2AvatarVisual`
8560
+ */
8561
+ function resolveVisibleBlobbyOctopusPatches(options) {
8562
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
8563
+ const latitudePatchCount = 12;
8564
+ const longitudePatchCount = 24;
8565
+ const surfacePatches = [];
8566
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
8567
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
8568
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
8569
+ const centerLatitude = (startLatitude + endLatitude) / 2;
8570
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
8571
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
8572
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
8573
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
8574
+ const centerLongitude = (startLongitude + endLongitude) / 2;
8575
+ const localCorners = [
8576
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
8577
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
8578
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
8579
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
8580
+ ];
8581
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
8582
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
8583
+ if (surfaceNormal.z <= 0.01) {
8584
+ continue;
8585
+ }
8586
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
8587
+ surfacePatches.push({
8588
+ corners: projectedCorners,
8589
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
8590
+ transformedCorners.length,
8591
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
8592
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
8593
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
8594
+ });
8595
+ }
8343
8596
  }
8344
- context.restore();
8597
+ return surfacePatches;
8598
+ }
8599
+ /**
8600
+ * Samples one point on the continuous Octopus 3D 2 surface.
8601
+ *
8602
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
8603
+ * instead of switching to separately rendered tentacles.
8604
+ *
8605
+ * @private helper of `octopus3d2AvatarVisual`
8606
+ */
8607
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
8608
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
8609
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
8610
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
8611
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
8612
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
8613
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
8614
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
8615
+ const horizontalScale = 1.02 +
8616
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
8617
+ upperBlend * 0.08;
8618
+ const depthScale = 1.04 +
8619
+ upperBlend * 0.16 +
8620
+ Math.max(0, Math.cos(longitude)) * 0.1 +
8621
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
8622
+ Math.max(0, -Math.cos(longitude)) * 0.04;
8623
+ const lowerDrop = skirtEnvelope *
8624
+ radiusY *
8625
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
8626
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
8627
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
8628
+ return {
8629
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
8630
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
8631
+ upperBlend * radiusY * 0.1 +
8632
+ lowerDrop +
8633
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
8634
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
8635
+ };
8345
8636
  }
8346
8637
  /**
8347
- * Draws a subtle projected mouth arc across the front of the mantle.
8638
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
8348
8639
  *
8349
- * @private helper of `octopus3dAvatarVisual`
8640
+ * @private helper of `octopus3d2AvatarVisual`
8350
8641
  */
8351
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
8352
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
8353
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
8354
- return;
8355
- }
8356
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
8357
- context.beginPath();
8358
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
8359
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
8360
- context.strokeStyle = `${palette.ink}b8`;
8361
- context.lineWidth = Math.max(1.1, size * 0.009);
8362
- context.lineCap = 'round';
8363
- context.stroke();
8642
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
8643
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
8644
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
8364
8645
  }
8365
8646
  /**
8366
- * Draws one filled projected quad.
8647
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
8367
8648
  *
8368
- * @private helper of `octopus3dAvatarVisual`
8649
+ * @private helper of `octopus3d2AvatarVisual`
8369
8650
  */
8370
- function drawProjectedQuad(context, corners, fillStyle) {
8371
- context.beginPath();
8372
- context.moveTo(corners[0].x, corners[0].y);
8373
- context.lineTo(corners[1].x, corners[1].y);
8374
- context.lineTo(corners[2].x, corners[2].y);
8375
- context.lineTo(corners[3].x, corners[3].y);
8376
- context.closePath();
8377
- context.fillStyle = fillStyle;
8378
- context.fill();
8651
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
8652
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
8653
+ if (tonalProgress < 0.16) {
8654
+ return palette.highlight;
8655
+ }
8656
+ if (tonalProgress < 0.34) {
8657
+ return palette.secondary;
8658
+ }
8659
+ if (tonalProgress < 0.72) {
8660
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
8661
+ }
8662
+ return `${palette.shadow}f2`;
8379
8663
  }
8380
8664
  /**
8381
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
8665
+ * Draws one projected patch with soft octopus shading.
8382
8666
  *
8383
- * @private helper of `octopus3dAvatarVisual`
8667
+ * @private helper of `octopus3d2AvatarVisual`
8384
8668
  */
8385
- function formatAlphaHex(opacity) {
8386
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
8387
- .toString(16)
8388
- .padStart(2, '0');
8669
+ function drawBlobbySurfacePatch(context, surfacePatch) {
8670
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
8671
+ if (surfacePatch.lightIntensity > 0) {
8672
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
8673
+ }
8674
+ else if (surfacePatch.lightIntensity < 0) {
8675
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
8676
+ }
8677
+ context.save();
8678
+ context.beginPath();
8679
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
8680
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
8681
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
8682
+ }
8683
+ context.closePath();
8684
+ context.strokeStyle = surfacePatch.outlineColor;
8685
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
8686
+ context.lineJoin = 'round';
8687
+ context.stroke();
8688
+ context.restore();
8389
8689
  }
8390
8690
 
8391
8691
  /* eslint-disable no-magic-numbers */
@@ -9157,6 +9457,7 @@ const AVATAR_VISUALS = [
9157
9457
  octopus2AvatarVisual,
9158
9458
  octopus3AvatarVisual,
9159
9459
  octopus3dAvatarVisual,
9460
+ octopus3d2AvatarVisual,
9160
9461
  asciiOctopusAvatarVisual,
9161
9462
  minecraftAvatarVisual,
9162
9463
  minecraft2AvatarVisual,
@@ -14821,11 +15122,11 @@ function buildCandidateEncodings(options) {
14821
15122
  });
14822
15123
  }
14823
15124
  /**
14824
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
15125
+ * Prepares one attachment for best-effort text decoding.
14825
15126
  *
14826
- * @private internal utility for shared text decoding
15127
+ * @private function of decodeAttachmentAsText
14827
15128
  */
14828
- function decodeAttachmentAsText(input, options = {}) {
15129
+ function createDecodeAttachmentPreparation(input, options) {
14829
15130
  var _a;
14830
15131
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
14831
15132
  const forceText = options.forceText === true;
@@ -14839,54 +15140,102 @@ function decodeAttachmentAsText(input, options = {}) {
14839
15140
  const inspection = inspectBytes(truncatedBytes);
14840
15141
  const trustedTextMime = isTrustedTextMimeType(mimeType);
14841
15142
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
15143
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
14842
15144
  if (isTruncated) {
14843
15145
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
14844
15146
  }
14845
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
14846
- if (shouldTreatAsBinary && !forceText) {
14847
- warnings.push('File content looks binary, so text decoding was skipped.');
14848
- return {
14849
- text: '',
14850
- encodingUsed: 'binary',
14851
- confidence: 1,
14852
- warnings,
14853
- wasBinary: true,
14854
- isTruncated,
14855
- };
15147
+ return {
15148
+ warnings,
15149
+ charset,
15150
+ bom,
15151
+ inspection,
15152
+ truncatedBytes,
15153
+ isTruncated,
15154
+ forceText,
15155
+ shouldTreatAsBinary,
15156
+ };
15157
+ }
15158
+ /**
15159
+ * Returns an early result when the attachment should stay classified as binary.
15160
+ *
15161
+ * @private function of decodeAttachmentAsText
15162
+ */
15163
+ function createBinaryDecodeResult(preparation) {
15164
+ if (!preparation.shouldTreatAsBinary) {
15165
+ return null;
14856
15166
  }
14857
- if (shouldTreatAsBinary && forceText) {
14858
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
15167
+ if (preparation.forceText) {
15168
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
15169
+ return null;
14859
15170
  }
14860
- if (charset && !isSupportedEncoding(charset)) {
14861
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
15171
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
15172
+ return {
15173
+ text: '',
15174
+ encodingUsed: 'binary',
15175
+ confidence: 1,
15176
+ warnings: preparation.warnings,
15177
+ wasBinary: true,
15178
+ isTruncated: preparation.isTruncated,
15179
+ };
15180
+ }
15181
+ /**
15182
+ * Warns when the declared charset cannot be used by the runtime decoder.
15183
+ *
15184
+ * @private function of decodeAttachmentAsText
15185
+ */
15186
+ function addUnsupportedCharsetWarning(preparation) {
15187
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
15188
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
14862
15189
  }
14863
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
14864
- const candidates = buildCandidateEncodings({
14865
- mimeType,
14866
- charset: charset && isSupportedEncoding(charset) ? charset : null,
14867
- bom,
14868
- inspection,
14869
- });
14870
- const decodedCandidates = candidates
15190
+ }
15191
+ /**
15192
+ * Returns the byte slice that should actually be decoded as text.
15193
+ *
15194
+ * @private function of decodeAttachmentAsText
15195
+ */
15196
+ function getBytesToDecode(preparation) {
15197
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
15198
+ }
15199
+ /**
15200
+ * Decodes all candidate encodings and sorts the successful results by score.
15201
+ *
15202
+ * @private function of decodeAttachmentAsText
15203
+ */
15204
+ function decodeAttachmentCandidates(preparation) {
15205
+ return buildCandidateEncodings({
15206
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
15207
+ bom: preparation.bom,
15208
+ inspection: preparation.inspection,
15209
+ })
14871
15210
  .map(({ encoding, source }) => {
14872
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
15211
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
14873
15212
  return decoded ? { ...decoded, source } : null;
14874
15213
  })
14875
15214
  .filter((candidate) => candidate !== null)
14876
15215
  .sort((left, right) => left.score - right.score);
14877
- const bestCandidate = decodedCandidates[0];
14878
- if (!bestCandidate) {
14879
- warnings.push('No supported text decoder was available.');
14880
- return {
14881
- text: '',
14882
- encodingUsed: 'binary',
14883
- confidence: 0,
14884
- warnings,
14885
- wasBinary: true,
14886
- isTruncated,
14887
- };
14888
- }
14889
- const secondBestCandidate = decodedCandidates[1];
15216
+ }
15217
+ /**
15218
+ * Returns the fallback result used when no text decoder could be applied.
15219
+ *
15220
+ * @private function of decodeAttachmentAsText
15221
+ */
15222
+ function createNoDecoderAvailableResult(preparation) {
15223
+ preparation.warnings.push('No supported text decoder was available.');
15224
+ return {
15225
+ text: '',
15226
+ encodingUsed: 'binary',
15227
+ confidence: 0,
15228
+ warnings: preparation.warnings,
15229
+ wasBinary: true,
15230
+ isTruncated: preparation.isTruncated,
15231
+ };
15232
+ }
15233
+ /**
15234
+ * Estimates confidence for the winning decoded text candidate.
15235
+ *
15236
+ * @private function of decodeAttachmentAsText
15237
+ */
15238
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
14890
15239
  const baseConfidence = bestCandidate.source === 'bom'
14891
15240
  ? 1
14892
15241
  : bestCandidate.source === 'charset'
@@ -14899,27 +15248,62 @@ function decodeAttachmentAsText(input, options = {}) {
14899
15248
  ? 0.82
14900
15249
  : 0.62;
14901
15250
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
14902
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
15251
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
15252
+ }
15253
+ /**
15254
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
15255
+ *
15256
+ * @private function of decodeAttachmentAsText
15257
+ */
15258
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
14903
15259
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
14904
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
15260
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
14905
15261
  }
14906
15262
  if (bestCandidate.source === 'heuristic' &&
14907
15263
  bestCandidate.encoding === 'utf-8' &&
14908
15264
  bestCandidate.replacementCount > 0) {
14909
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
15265
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
14910
15266
  }
14911
15267
  if (confidence < 0.6) {
14912
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
15268
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
14913
15269
  }
15270
+ }
15271
+ /**
15272
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
15273
+ *
15274
+ * @private function of decodeAttachmentAsText
15275
+ */
15276
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
14914
15277
  return {
14915
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
15278
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
14916
15279
  encodingUsed: bestCandidate.encoding,
14917
15280
  confidence,
14918
- warnings,
15281
+ warnings: preparation.warnings,
14919
15282
  wasBinary: false,
14920
- isTruncated,
15283
+ isTruncated: preparation.isTruncated,
14921
15284
  };
14922
15285
  }
15286
+ /**
15287
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
15288
+ *
15289
+ * @private internal utility for shared text decoding
15290
+ */
15291
+ function decodeAttachmentAsText(input, options = {}) {
15292
+ const preparation = createDecodeAttachmentPreparation(input, options);
15293
+ const binaryResult = createBinaryDecodeResult(preparation);
15294
+ if (binaryResult) {
15295
+ return binaryResult;
15296
+ }
15297
+ addUnsupportedCharsetWarning(preparation);
15298
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
15299
+ const bestCandidate = decodedCandidates[0];
15300
+ if (!bestCandidate) {
15301
+ return createNoDecoderAvailableResult(preparation);
15302
+ }
15303
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
15304
+ addDecodeWarnings(bestCandidate, confidence, preparation);
15305
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
15306
+ }
14923
15307
 
14924
15308
  /**
14925
15309
  * Convert file extension to mime type
@@ -19287,120 +19671,183 @@ function orderJson(options) {
19287
19671
  * @public exported from `@promptbook/utils`
19288
19672
  */
19289
19673
  function checkSerializableAsJson(options) {
19290
- const { value, name, message } = options;
19674
+ checkSerializableValue(options);
19675
+ }
19676
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
19677
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
19678
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
19679
+ /**
19680
+ * Checks one value and dispatches to the appropriate specialized validator.
19681
+ *
19682
+ * @private function of `checkSerializableAsJson`
19683
+ */
19684
+ function checkSerializableValue(options) {
19685
+ const { value } = options;
19686
+ if (isSerializablePrimitive(value)) {
19687
+ return;
19688
+ }
19291
19689
  if (value === undefined) {
19292
- throw new UnexpectedError(`${name} is undefined`);
19690
+ throw new UnexpectedError(`${options.name} is undefined`);
19293
19691
  }
19294
- else if (value === null) {
19295
- return;
19692
+ if (typeof value === 'symbol') {
19693
+ throw new UnexpectedError(`${options.name} is symbol`);
19296
19694
  }
19297
- else if (typeof value === 'boolean') {
19298
- return;
19695
+ if (typeof value === 'function') {
19696
+ throw new UnexpectedError(`${options.name} is function`);
19299
19697
  }
19300
- else if (typeof value === 'number' && !isNaN(value)) {
19698
+ if (Array.isArray(value)) {
19699
+ checkSerializableArray(options, value);
19301
19700
  return;
19302
19701
  }
19303
- else if (typeof value === 'string') {
19702
+ if (value !== null && typeof value === 'object') {
19703
+ checkSerializableObject(options, value);
19304
19704
  return;
19305
19705
  }
19306
- else if (typeof value === 'symbol') {
19307
- throw new UnexpectedError(`${name} is symbol`);
19308
- }
19309
- else if (typeof value === 'function') {
19310
- throw new UnexpectedError(`${name} is function`);
19311
- }
19312
- else if (typeof value === 'object' && Array.isArray(value)) {
19313
- for (let i = 0; i < value.length; i++) {
19314
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
19315
- }
19706
+ throwUnknownTypeError(options);
19707
+ }
19708
+ /**
19709
+ * Checks the primitive values that are directly JSON serializable.
19710
+ *
19711
+ * @private function of `checkSerializableAsJson`
19712
+ */
19713
+ function isSerializablePrimitive(value) {
19714
+ return (value === null ||
19715
+ typeof value === 'boolean' ||
19716
+ (typeof value === 'number' && !isNaN(value)) ||
19717
+ typeof value === 'string');
19718
+ }
19719
+ /**
19720
+ * Recursively checks JSON array items.
19721
+ *
19722
+ * @private function of `checkSerializableAsJson`
19723
+ */
19724
+ function checkSerializableArray(context, arrayValue) {
19725
+ for (let index = 0; index < arrayValue.length; index++) {
19726
+ checkSerializableAsJson({
19727
+ ...context,
19728
+ name: `${context.name}[${index}]`,
19729
+ value: arrayValue[index],
19730
+ });
19316
19731
  }
19317
- else if (typeof value === 'object') {
19318
- if (value instanceof Date) {
19319
- throw new UnexpectedError(spaceTrim$1((block) => `
19320
- \`${name}\` is Date
19732
+ }
19733
+ /**
19734
+ * Checks object-like values and dispatches special unsupported built-ins.
19735
+ *
19736
+ * @private function of `checkSerializableAsJson`
19737
+ */
19738
+ function checkSerializableObject(context, objectValue) {
19739
+ checkUnsupportedObjectType(context, objectValue);
19740
+ checkSerializableObjectEntries(context, objectValue);
19741
+ assertJsonStringificationSucceeds(context, objectValue);
19742
+ }
19743
+ /**
19744
+ * Rejects built-in objects that must be converted before JSON serialization.
19745
+ *
19746
+ * @private function of `checkSerializableAsJson`
19747
+ */
19748
+ function checkUnsupportedObjectType(context, objectValue) {
19749
+ if (objectValue instanceof Date) {
19750
+ throw new UnexpectedError(spaceTrim$1((block) => `
19751
+ \`${context.name}\` is Date
19321
19752
 
19322
- Use \`string_date_iso8601\` instead
19753
+ Use \`string_date_iso8601\` instead
19323
19754
 
19324
- Additional message for \`${name}\`:
19325
- ${block(message || '(nothing)')}
19326
- `));
19327
- }
19328
- else if (value instanceof Map) {
19329
- throw new UnexpectedError(`${name} is Map`);
19330
- }
19331
- else if (value instanceof Set) {
19332
- throw new UnexpectedError(`${name} is Set`);
19333
- }
19334
- else if (value instanceof RegExp) {
19335
- throw new UnexpectedError(`${name} is RegExp`);
19336
- }
19337
- else if (value instanceof Error) {
19338
- throw new UnexpectedError(spaceTrim$1((block) => `
19339
- \`${name}\` is unserialized Error
19755
+ Additional message for \`${context.name}\`:
19756
+ ${block(context.message || '(nothing)')}
19757
+ `));
19758
+ }
19759
+ if (objectValue instanceof Map) {
19760
+ throw new UnexpectedError(`${context.name} is Map`);
19761
+ }
19762
+ if (objectValue instanceof Set) {
19763
+ throw new UnexpectedError(`${context.name} is Set`);
19764
+ }
19765
+ if (objectValue instanceof RegExp) {
19766
+ throw new UnexpectedError(`${context.name} is RegExp`);
19767
+ }
19768
+ if (objectValue instanceof Error) {
19769
+ throw new UnexpectedError(spaceTrim$1((block) => `
19770
+ \`${context.name}\` is unserialized Error
19340
19771
 
19341
- Use function \`serializeError\`
19772
+ Use function \`serializeError\`
19342
19773
 
19343
- Additional message for \`${name}\`:
19344
- ${block(message || '(nothing)')}
19774
+ Additional message for \`${context.name}\`:
19775
+ ${block(context.message || '(nothing)')}
19345
19776
 
19346
- `));
19777
+ `));
19778
+ }
19779
+ }
19780
+ /**
19781
+ * Recursively checks object properties while preserving omitted `undefined` keys.
19782
+ *
19783
+ * @private function of `checkSerializableAsJson`
19784
+ */
19785
+ function checkSerializableObjectEntries(context, objectValue) {
19786
+ for (const [subName, subValue] of Object.entries(objectValue)) {
19787
+ if (subValue === undefined) {
19788
+ // Note: undefined in object is serializable - it is just omitted
19789
+ continue;
19347
19790
  }
19348
- else {
19349
- for (const [subName, subValue] of Object.entries(value)) {
19350
- if (subValue === undefined) {
19351
- // Note: undefined in object is serializable - it is just omitted
19352
- continue;
19353
- }
19354
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
19355
- }
19356
- try {
19357
- JSON.stringify(value); // <- TODO: [0]
19358
- }
19359
- catch (error) {
19360
- assertsError(error);
19361
- throw new UnexpectedError(spaceTrim$1((block) => `
19362
- \`${name}\` is not serializable
19791
+ checkSerializableAsJson({
19792
+ ...context,
19793
+ name: `${context.name}.${subName}`,
19794
+ value: subValue,
19795
+ });
19796
+ }
19797
+ }
19798
+ /**
19799
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
19800
+ *
19801
+ * @private function of `checkSerializableAsJson`
19802
+ */
19803
+ function assertJsonStringificationSucceeds(context, objectValue) {
19804
+ try {
19805
+ JSON.stringify(objectValue); // <- TODO: [0]
19806
+ }
19807
+ catch (error) {
19808
+ assertsError(error);
19809
+ throw new UnexpectedError(spaceTrim$1((block) => `
19810
+ \`${context.name}\` is not serializable
19363
19811
 
19364
- ${block(error.stack || error.message)}
19812
+ ${block(error.stack || error.message)}
19365
19813
 
19366
- Additional message for \`${name}\`:
19367
- ${block(message || '(nothing)')}
19368
- `));
19814
+ Additional message for \`${context.name}\`:
19815
+ ${block(context.message || '(nothing)')}
19816
+ `));
19817
+ }
19818
+ /*
19819
+ TODO: [0] Is there some more elegant way to check circular references?
19820
+ const seen = new Set();
19821
+ const stack = [{ value }];
19822
+ while (stack.length > 0) {
19823
+ const { value } = stack.pop()!;
19824
+ if (typeof value === 'object' && value !== null) {
19825
+ if (seen.has(value)) {
19826
+ throw new UnexpectedError(`${name} has circular reference`);
19369
19827
  }
19370
- /*
19371
- TODO: [0] Is there some more elegant way to check circular references?
19372
- const seen = new Set();
19373
- const stack = [{ value }];
19374
- while (stack.length > 0) {
19375
- const { value } = stack.pop()!;
19376
- if (typeof value === 'object' && value !== null) {
19377
- if (seen.has(value)) {
19378
- throw new UnexpectedError(`${name} has circular reference`);
19379
- }
19380
- seen.add(value);
19381
- if (Array.isArray(value)) {
19382
- stack.push(...value.map((value) => ({ value })));
19383
- } else {
19384
- stack.push(...Object.values(value).map((value) => ({ value })));
19385
- }
19386
- }
19828
+ seen.add(value);
19829
+ if (Array.isArray(value)) {
19830
+ stack.push(...value.map((value) => ({ value })));
19831
+ } else {
19832
+ stack.push(...Object.values(value).map((value) => ({ value })));
19387
19833
  }
19388
- */
19389
- return;
19390
19834
  }
19391
19835
  }
19392
- else {
19393
- throw new UnexpectedError(spaceTrim$1((block) => `
19394
- \`${name}\` is unknown type
19836
+ */
19837
+ }
19838
+ /**
19839
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
19840
+ *
19841
+ * @private function of `checkSerializableAsJson`
19842
+ */
19843
+ function throwUnknownTypeError(context) {
19844
+ throw new UnexpectedError(spaceTrim$1((block) => `
19845
+ \`${context.name}\` is unknown type
19395
19846
 
19396
- Additional message for \`${name}\`:
19397
- ${block(message || '(nothing)')}
19398
- `));
19399
- }
19847
+ Additional message for \`${context.name}\`:
19848
+ ${block(context.message || '(nothing)')}
19849
+ `));
19400
19850
  }
19401
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
19402
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
19403
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
19404
19851
 
19405
19852
  /**
19406
19853
  * Creates a deep clone of the given object
@@ -22353,7 +22800,7 @@ function createJokerCommands(task) {
22353
22800
  */
22354
22801
  function createPostprocessingCommands(task) {
22355
22802
  var _a;
22356
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
22803
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
22357
22804
  }
22358
22805
  /**
22359
22806
  * Collects expectation commands.
@@ -22896,8 +23343,7 @@ function hasTaskJokers(task) {
22896
23343
  * @private internal utility of `validatePipeline`
22897
23344
  */
22898
23345
  function validateTaskSupportsJokers(task, pipelineIdentification) {
22899
- if (task.format ||
22900
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
23346
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
22901
23347
  return;
22902
23348
  }
22903
23349
  throw new PipelineLogicError(spaceTrim$1((block) => `
@@ -26262,9 +26708,7 @@ function createFailuresSummary($failedResults) {
26262
26708
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
26263
26709
 
26264
26710
  Result:
26265
- ${block(failure.result === null
26266
- ? 'null'
26267
- : quoteMultilineText(spaceTrim$1(failure.result)))}
26711
+ ${block(failure.result === null ? 'null' : quoteMultilineText(spaceTrim$1(failure.result)))}
26268
26712
  `;
26269
26713
  }))
26270
26714
  .join('\n\n---\n\n');
@@ -30104,7 +30548,10 @@ class OpenAiCompatibleModelCatalog {
30104
30548
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
30105
30549
 
30106
30550
  Available models:
30107
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
30551
+ ${block(this.options
30552
+ .getHardcodedModels()
30553
+ .map(({ modelName }) => `- "${modelName}"`)
30554
+ .join('\n'))}
30108
30555
 
30109
30556
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
30110
30557
 
@@ -30458,7 +30905,8 @@ class OpenAiCompatibleNonChatPromptCaller {
30458
30905
  };
30459
30906
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
30460
30907
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
30461
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
30908
+ rawPromptContent +=
30909
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
30462
30910
  }
30463
30911
  const rawRequest = {
30464
30912
  ...modelSettings,
@@ -30563,7 +31011,9 @@ class OpenAiCompatibleRequestManager {
30563
31011
  * Schedules one request through the shared limiter and retry policy.
30564
31012
  */
30565
31013
  async executeRateLimitedRequest(requestFn) {
30566
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
31014
+ return this.limiter
31015
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
31016
+ .catch((error) => {
30567
31017
  assertsError(error);
30568
31018
  if (this.options.isVerbose) {
30569
31019
  console.info(colors.bgRed('error'), error);
@@ -31770,7 +32220,9 @@ class OpenAiVectorStoreFileBatchPoller {
31770
32220
  pollingState.lastProgressAtMs = nowMs;
31771
32221
  pollingState.lastProgressKey = progressKey;
31772
32222
  }
31773
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
32223
+ if (this.options.isVerbose &&
32224
+ (statusCountsKey !== pollingState.lastCountsKey ||
32225
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
31774
32226
  console.info('[🤰]', 'Vector store file batch status', {
31775
32227
  vectorStoreId,
31776
32228
  batchId,