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