@promptbook/core 0.112.0-73 → 0.112.0-80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +9 -9
  2. package/esm/index.es.js +823 -374
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  5. package/esm/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  6. package/esm/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  7. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  8. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  9. package/esm/src/book-components/Chat/save/index.d.ts +2 -2
  10. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  11. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  13. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  14. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  15. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  16. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  18. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  19. package/esm/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  20. package/esm/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  21. package/esm/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  22. package/esm/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  23. package/esm/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  24. package/esm/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  25. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  26. package/esm/src/cli/cli-commands/agents-server.d.ts +8 -0
  27. package/esm/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  28. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  29. package/esm/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  30. package/esm/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  31. package/esm/src/utils/color/Color.d.ts +4 -44
  32. package/esm/src/utils/color/ColorValue.d.ts +55 -0
  33. package/esm/src/utils/color/isHexColorString.d.ts +10 -0
  34. package/esm/src/utils/color/parseColorString.d.ts +11 -0
  35. package/esm/src/version.d.ts +1 -1
  36. package/package.json +1 -1
  37. package/umd/index.umd.js +823 -374
  38. package/umd/index.umd.js.map +1 -1
  39. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  40. package/umd/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  41. package/umd/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  42. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +47 -0
  43. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +12 -0
  44. package/umd/src/book-components/Chat/save/index.d.ts +2 -2
  45. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  46. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -3
  47. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +1 -1
  48. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  49. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -1
  50. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  51. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  52. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  53. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  54. package/umd/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  55. package/umd/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  56. package/umd/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  57. package/umd/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  58. package/umd/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  59. package/umd/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  60. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  61. package/umd/src/cli/cli-commands/agents-server.d.ts +8 -0
  62. package/umd/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  63. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  64. package/umd/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  65. package/umd/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  66. package/umd/src/utils/color/Color.d.ts +4 -44
  67. package/umd/src/utils/color/ColorValue.d.ts +55 -0
  68. package/umd/src/utils/color/isHexColorString.d.ts +10 -0
  69. package/umd/src/utils/color/parseColorString.d.ts +11 -0
  70. package/umd/src/version.d.ts +1 -1
  71. package/esm/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  72. package/esm/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
  73. package/umd/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  74. package/umd/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
package/esm/index.es.js CHANGED
@@ -28,7 +28,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
28
28
  * @generated
29
29
  * @see https://github.com/webgptorg/promptbook
30
30
  */
31
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
31
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-80';
32
32
  /**
33
33
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
34
34
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -278,6 +278,111 @@ function checkChannelValue(channelName, value) {
278
278
  }
279
279
  }
280
280
 
281
+ /**
282
+ * Shared immutable channel storage and serialization helpers for `Color`.
283
+ *
284
+ * @private base class of Color
285
+ */
286
+ class ColorValue {
287
+ constructor(red, green, blue, alpha = 255) {
288
+ this.red = red;
289
+ this.green = green;
290
+ this.blue = blue;
291
+ this.alpha = alpha;
292
+ checkChannelValue('Red', red);
293
+ checkChannelValue('Green', green);
294
+ checkChannelValue('Blue', blue);
295
+ checkChannelValue('Alpha', alpha);
296
+ }
297
+ /**
298
+ * Shortcut for `red` property
299
+ * Number from 0 to 255
300
+ * @alias red
301
+ */
302
+ get r() {
303
+ return this.red;
304
+ }
305
+ /**
306
+ * Shortcut for `green` property
307
+ * Number from 0 to 255
308
+ * @alias green
309
+ */
310
+ get g() {
311
+ return this.green;
312
+ }
313
+ /**
314
+ * Shortcut for `blue` property
315
+ * Number from 0 to 255
316
+ * @alias blue
317
+ */
318
+ get b() {
319
+ return this.blue;
320
+ }
321
+ /**
322
+ * Shortcut for `alpha` property
323
+ * Number from 0 (transparent) to 255 (opaque)
324
+ * @alias alpha
325
+ */
326
+ get a() {
327
+ return this.alpha;
328
+ }
329
+ /**
330
+ * Shortcut for `alpha` property
331
+ * Number from 0 (transparent) to 255 (opaque)
332
+ * @alias alpha
333
+ */
334
+ get opacity() {
335
+ return this.alpha;
336
+ }
337
+ /**
338
+ * Shortcut for 1-`alpha` property
339
+ */
340
+ get transparency() {
341
+ return 255 - this.alpha;
342
+ }
343
+ clone() {
344
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
345
+ }
346
+ toString() {
347
+ return this.toHex();
348
+ }
349
+ toHex() {
350
+ if (this.alpha === 255) {
351
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
352
+ .toString(16)
353
+ .padStart(2, '0')}`;
354
+ }
355
+ else {
356
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
357
+ .toString(16)
358
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
359
+ }
360
+ }
361
+ toRgb() {
362
+ if (this.alpha === 255) {
363
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
364
+ }
365
+ else {
366
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
367
+ }
368
+ }
369
+ toHsl() {
370
+ throw new Error(`Getting HSL is not implemented`);
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Checks if the given value is a valid hex color string
376
+ *
377
+ * @param value - value to check
378
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
379
+ *
380
+ * @private function of Color
381
+ */
382
+ function isHexColorString(value) {
383
+ 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));
384
+ }
385
+
281
386
  /**
282
387
  * Constant for short hex lengths.
283
388
  */
@@ -489,16 +594,53 @@ function parseAlphaValue(value) {
489
594
 
490
595
  /**
491
596
  * Pattern matching hsl regex.
597
+ *
598
+ * @private function of Color
492
599
  */
493
600
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
494
601
  /**
495
602
  * Pattern matching RGB regex.
603
+ *
604
+ * @private function of Color
496
605
  */
497
606
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
498
607
  /**
499
608
  * Pattern matching rgba regex.
609
+ *
610
+ * @private function of Color
500
611
  */
501
612
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
613
+ /**
614
+ * Parses a supported color string into RGBA channels.
615
+ *
616
+ * @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`,...
617
+ * @returns RGBA channel values.
618
+ *
619
+ * @private function of Color
620
+ */
621
+ function parseColorString(color) {
622
+ const trimmed = color.trim();
623
+ const cssColor = CSS_COLORS[trimmed];
624
+ if (cssColor) {
625
+ return parseColorString(cssColor);
626
+ }
627
+ else if (isHexColorString(trimmed)) {
628
+ return parseHexColor(trimmed);
629
+ }
630
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
631
+ return parseHslColor(trimmed);
632
+ }
633
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
634
+ return parseRgbColor(trimmed);
635
+ }
636
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
637
+ return parseRgbaColor(trimmed);
638
+ }
639
+ else {
640
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
641
+ }
642
+ }
643
+
502
644
  /**
503
645
  * Color object represents an RGB color with alpha channel
504
646
  *
@@ -506,7 +648,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
506
648
  *
507
649
  * @public exported from `@promptbook/color`
508
650
  */
509
- class Color {
651
+ class Color extends ColorValue {
510
652
  /**
511
653
  * Creates a new Color instance from miscellaneous formats
512
654
  * - It can receive Color instance and just return the same instance
@@ -579,25 +721,7 @@ class Color {
579
721
  * @returns Color object
580
722
  */
581
723
  static fromString(color) {
582
- const trimmed = color.trim();
583
- if (CSS_COLORS[trimmed]) {
584
- return Color.fromString(CSS_COLORS[trimmed]);
585
- }
586
- else if (Color.isHexColorString(trimmed)) {
587
- return Color.fromHex(trimmed);
588
- }
589
- if (HSL_REGEX_PATTERN.test(trimmed)) {
590
- return Color.fromHsl(trimmed);
591
- }
592
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
593
- return Color.fromRgbString(trimmed);
594
- }
595
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
596
- return Color.fromRgbaString(trimmed);
597
- }
598
- else {
599
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
600
- }
724
+ return Color.fromColorChannels(parseColorString(color));
601
725
  }
602
726
  /**
603
727
  * Gets common color
@@ -627,8 +751,7 @@ class Color {
627
751
  * @returns Color object
628
752
  */
629
753
  static fromHex(hex) {
630
- const { red, green, blue, alpha } = parseHexColor(hex);
631
- return take(new Color(red, green, blue, alpha));
754
+ return Color.fromColorChannels(parseHexColor(hex));
632
755
  }
633
756
  /**
634
757
  * Creates a new Color instance from color in hsl format
@@ -637,8 +760,7 @@ class Color {
637
760
  * @returns Color object
638
761
  */
639
762
  static fromHsl(hsl) {
640
- const { red, green, blue, alpha } = parseHslColor(hsl);
641
- return take(new Color(red, green, blue, alpha));
763
+ return Color.fromColorChannels(parseHslColor(hsl));
642
764
  }
643
765
  /**
644
766
  * Creates a new Color instance from color in rgb format
@@ -647,8 +769,7 @@ class Color {
647
769
  * @returns Color object
648
770
  */
649
771
  static fromRgbString(rgb) {
650
- const { red, green, blue, alpha } = parseRgbColor(rgb);
651
- return take(new Color(red, green, blue, alpha));
772
+ return Color.fromColorChannels(parseRgbColor(rgb));
652
773
  }
653
774
  /**
654
775
  * Creates a new Color instance from color in rbga format
@@ -657,8 +778,7 @@ class Color {
657
778
  * @returns Color object
658
779
  */
659
780
  static fromRgbaString(rgba) {
660
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
661
- return take(new Color(red, green, blue, alpha));
781
+ return Color.fromColorChannels(parseRgbaColor(rgba));
662
782
  }
663
783
  /**
664
784
  * Creates a new Color for color channels values
@@ -670,7 +790,7 @@ class Color {
670
790
  * @returns Color object
671
791
  */
672
792
  static fromValues(red, green, blue, alpha = 255) {
673
- return take(new Color(red, green, blue, alpha));
793
+ return Color.fromColorChannels({ red, green, blue, alpha });
674
794
  }
675
795
  /**
676
796
  * Checks if the given value is a valid Color object.
@@ -703,8 +823,7 @@ class Color {
703
823
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
704
824
  */
705
825
  static isHexColorString(value) {
706
- return (typeof value === 'string' &&
707
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
826
+ return isHexColorString(value);
708
827
  }
709
828
  /**
710
829
  * Creates new Color object
@@ -717,89 +836,13 @@ class Color {
717
836
  * @param alpha number from 0 (transparent) to 255 (opaque)
718
837
  */
719
838
  constructor(red, green, blue, alpha = 255) {
720
- this.red = red;
721
- this.green = green;
722
- this.blue = blue;
723
- this.alpha = alpha;
724
- checkChannelValue('Red', red);
725
- checkChannelValue('Green', green);
726
- checkChannelValue('Blue', blue);
727
- checkChannelValue('Alpha', alpha);
728
- }
729
- /**
730
- * Shortcut for `red` property
731
- * Number from 0 to 255
732
- * @alias red
733
- */
734
- get r() {
735
- return this.red;
736
- }
737
- /**
738
- * Shortcut for `green` property
739
- * Number from 0 to 255
740
- * @alias green
741
- */
742
- get g() {
743
- return this.green;
744
- }
745
- /**
746
- * Shortcut for `blue` property
747
- * Number from 0 to 255
748
- * @alias blue
749
- */
750
- get b() {
751
- return this.blue;
752
- }
753
- /**
754
- * Shortcut for `alpha` property
755
- * Number from 0 (transparent) to 255 (opaque)
756
- * @alias alpha
757
- */
758
- get a() {
759
- return this.alpha;
760
- }
761
- /**
762
- * Shortcut for `alpha` property
763
- * Number from 0 (transparent) to 255 (opaque)
764
- * @alias alpha
765
- */
766
- get opacity() {
767
- return this.alpha;
768
- }
769
- /**
770
- * Shortcut for 1-`alpha` property
771
- */
772
- get transparency() {
773
- return 255 - this.alpha;
774
- }
775
- clone() {
776
- return take(new Color(this.red, this.green, this.blue, this.alpha));
777
- }
778
- toString() {
779
- return this.toHex();
780
- }
781
- toHex() {
782
- if (this.alpha === 255) {
783
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
784
- .toString(16)
785
- .padStart(2, '0')}`;
786
- }
787
- else {
788
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
789
- .toString(16)
790
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
791
- }
839
+ super(red, green, blue, alpha);
792
840
  }
793
- toRgb() {
794
- if (this.alpha === 255) {
795
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
796
- }
797
- else {
798
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
799
- }
841
+ createColor(red, green, blue, alpha) {
842
+ return new Color(red, green, blue, alpha);
800
843
  }
801
- toHsl() {
802
- throw new Error(`Getting HSL is not implemented`);
844
+ static fromColorChannels({ red, green, blue, alpha }) {
845
+ return take(new Color(red, green, blue, alpha));
803
846
  }
804
847
  }
805
848
 
@@ -1982,7 +2025,7 @@ function createJokerCommands(task) {
1982
2025
  */
1983
2026
  function createPostprocessingCommands(task) {
1984
2027
  var _a;
1985
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
2028
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
1986
2029
  }
1987
2030
  /**
1988
2031
  * Collects expectation commands.
@@ -2147,120 +2190,183 @@ function $deepFreeze(objectValue) {
2147
2190
  * @public exported from `@promptbook/utils`
2148
2191
  */
2149
2192
  function checkSerializableAsJson(options) {
2150
- const { value, name, message } = options;
2193
+ checkSerializableValue(options);
2194
+ }
2195
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2196
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2197
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2198
+ /**
2199
+ * Checks one value and dispatches to the appropriate specialized validator.
2200
+ *
2201
+ * @private function of `checkSerializableAsJson`
2202
+ */
2203
+ function checkSerializableValue(options) {
2204
+ const { value } = options;
2205
+ if (isSerializablePrimitive(value)) {
2206
+ return;
2207
+ }
2151
2208
  if (value === undefined) {
2152
- throw new UnexpectedError(`${name} is undefined`);
2209
+ throw new UnexpectedError(`${options.name} is undefined`);
2153
2210
  }
2154
- else if (value === null) {
2155
- return;
2211
+ if (typeof value === 'symbol') {
2212
+ throw new UnexpectedError(`${options.name} is symbol`);
2156
2213
  }
2157
- else if (typeof value === 'boolean') {
2158
- return;
2214
+ if (typeof value === 'function') {
2215
+ throw new UnexpectedError(`${options.name} is function`);
2159
2216
  }
2160
- else if (typeof value === 'number' && !isNaN(value)) {
2217
+ if (Array.isArray(value)) {
2218
+ checkSerializableArray(options, value);
2161
2219
  return;
2162
2220
  }
2163
- else if (typeof value === 'string') {
2221
+ if (value !== null && typeof value === 'object') {
2222
+ checkSerializableObject(options, value);
2164
2223
  return;
2165
2224
  }
2166
- else if (typeof value === 'symbol') {
2167
- throw new UnexpectedError(`${name} is symbol`);
2168
- }
2169
- else if (typeof value === 'function') {
2170
- throw new UnexpectedError(`${name} is function`);
2171
- }
2172
- else if (typeof value === 'object' && Array.isArray(value)) {
2173
- for (let i = 0; i < value.length; i++) {
2174
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
2175
- }
2225
+ throwUnknownTypeError(options);
2226
+ }
2227
+ /**
2228
+ * Checks the primitive values that are directly JSON serializable.
2229
+ *
2230
+ * @private function of `checkSerializableAsJson`
2231
+ */
2232
+ function isSerializablePrimitive(value) {
2233
+ return (value === null ||
2234
+ typeof value === 'boolean' ||
2235
+ (typeof value === 'number' && !isNaN(value)) ||
2236
+ typeof value === 'string');
2237
+ }
2238
+ /**
2239
+ * Recursively checks JSON array items.
2240
+ *
2241
+ * @private function of `checkSerializableAsJson`
2242
+ */
2243
+ function checkSerializableArray(context, arrayValue) {
2244
+ for (let index = 0; index < arrayValue.length; index++) {
2245
+ checkSerializableAsJson({
2246
+ ...context,
2247
+ name: `${context.name}[${index}]`,
2248
+ value: arrayValue[index],
2249
+ });
2176
2250
  }
2177
- else if (typeof value === 'object') {
2178
- if (value instanceof Date) {
2179
- throw new UnexpectedError(spaceTrim$1((block) => `
2180
- \`${name}\` is Date
2251
+ }
2252
+ /**
2253
+ * Checks object-like values and dispatches special unsupported built-ins.
2254
+ *
2255
+ * @private function of `checkSerializableAsJson`
2256
+ */
2257
+ function checkSerializableObject(context, objectValue) {
2258
+ checkUnsupportedObjectType(context, objectValue);
2259
+ checkSerializableObjectEntries(context, objectValue);
2260
+ assertJsonStringificationSucceeds(context, objectValue);
2261
+ }
2262
+ /**
2263
+ * Rejects built-in objects that must be converted before JSON serialization.
2264
+ *
2265
+ * @private function of `checkSerializableAsJson`
2266
+ */
2267
+ function checkUnsupportedObjectType(context, objectValue) {
2268
+ if (objectValue instanceof Date) {
2269
+ throw new UnexpectedError(spaceTrim$1((block) => `
2270
+ \`${context.name}\` is Date
2181
2271
 
2182
- Use \`string_date_iso8601\` instead
2272
+ Use \`string_date_iso8601\` instead
2183
2273
 
2184
- Additional message for \`${name}\`:
2185
- ${block(message || '(nothing)')}
2186
- `));
2187
- }
2188
- else if (value instanceof Map) {
2189
- throw new UnexpectedError(`${name} is Map`);
2190
- }
2191
- else if (value instanceof Set) {
2192
- throw new UnexpectedError(`${name} is Set`);
2193
- }
2194
- else if (value instanceof RegExp) {
2195
- throw new UnexpectedError(`${name} is RegExp`);
2196
- }
2197
- else if (value instanceof Error) {
2198
- throw new UnexpectedError(spaceTrim$1((block) => `
2199
- \`${name}\` is unserialized Error
2274
+ Additional message for \`${context.name}\`:
2275
+ ${block(context.message || '(nothing)')}
2276
+ `));
2277
+ }
2278
+ if (objectValue instanceof Map) {
2279
+ throw new UnexpectedError(`${context.name} is Map`);
2280
+ }
2281
+ if (objectValue instanceof Set) {
2282
+ throw new UnexpectedError(`${context.name} is Set`);
2283
+ }
2284
+ if (objectValue instanceof RegExp) {
2285
+ throw new UnexpectedError(`${context.name} is RegExp`);
2286
+ }
2287
+ if (objectValue instanceof Error) {
2288
+ throw new UnexpectedError(spaceTrim$1((block) => `
2289
+ \`${context.name}\` is unserialized Error
2200
2290
 
2201
- Use function \`serializeError\`
2291
+ Use function \`serializeError\`
2202
2292
 
2203
- Additional message for \`${name}\`:
2204
- ${block(message || '(nothing)')}
2293
+ Additional message for \`${context.name}\`:
2294
+ ${block(context.message || '(nothing)')}
2205
2295
 
2206
- `));
2296
+ `));
2297
+ }
2298
+ }
2299
+ /**
2300
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2301
+ *
2302
+ * @private function of `checkSerializableAsJson`
2303
+ */
2304
+ function checkSerializableObjectEntries(context, objectValue) {
2305
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2306
+ if (subValue === undefined) {
2307
+ // Note: undefined in object is serializable - it is just omitted
2308
+ continue;
2207
2309
  }
2208
- else {
2209
- for (const [subName, subValue] of Object.entries(value)) {
2210
- if (subValue === undefined) {
2211
- // Note: undefined in object is serializable - it is just omitted
2212
- continue;
2213
- }
2214
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
2215
- }
2216
- try {
2217
- JSON.stringify(value); // <- TODO: [0]
2218
- }
2219
- catch (error) {
2220
- assertsError(error);
2221
- throw new UnexpectedError(spaceTrim$1((block) => `
2222
- \`${name}\` is not serializable
2310
+ checkSerializableAsJson({
2311
+ ...context,
2312
+ name: `${context.name}.${subName}`,
2313
+ value: subValue,
2314
+ });
2315
+ }
2316
+ }
2317
+ /**
2318
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2319
+ *
2320
+ * @private function of `checkSerializableAsJson`
2321
+ */
2322
+ function assertJsonStringificationSucceeds(context, objectValue) {
2323
+ try {
2324
+ JSON.stringify(objectValue); // <- TODO: [0]
2325
+ }
2326
+ catch (error) {
2327
+ assertsError(error);
2328
+ throw new UnexpectedError(spaceTrim$1((block) => `
2329
+ \`${context.name}\` is not serializable
2223
2330
 
2224
- ${block(error.stack || error.message)}
2331
+ ${block(error.stack || error.message)}
2225
2332
 
2226
- Additional message for \`${name}\`:
2227
- ${block(message || '(nothing)')}
2228
- `));
2333
+ Additional message for \`${context.name}\`:
2334
+ ${block(context.message || '(nothing)')}
2335
+ `));
2336
+ }
2337
+ /*
2338
+ TODO: [0] Is there some more elegant way to check circular references?
2339
+ const seen = new Set();
2340
+ const stack = [{ value }];
2341
+ while (stack.length > 0) {
2342
+ const { value } = stack.pop()!;
2343
+ if (typeof value === 'object' && value !== null) {
2344
+ if (seen.has(value)) {
2345
+ throw new UnexpectedError(`${name} has circular reference`);
2229
2346
  }
2230
- /*
2231
- TODO: [0] Is there some more elegant way to check circular references?
2232
- const seen = new Set();
2233
- const stack = [{ value }];
2234
- while (stack.length > 0) {
2235
- const { value } = stack.pop()!;
2236
- if (typeof value === 'object' && value !== null) {
2237
- if (seen.has(value)) {
2238
- throw new UnexpectedError(`${name} has circular reference`);
2239
- }
2240
- seen.add(value);
2241
- if (Array.isArray(value)) {
2242
- stack.push(...value.map((value) => ({ value })));
2243
- } else {
2244
- stack.push(...Object.values(value).map((value) => ({ value })));
2245
- }
2246
- }
2347
+ seen.add(value);
2348
+ if (Array.isArray(value)) {
2349
+ stack.push(...value.map((value) => ({ value })));
2350
+ } else {
2351
+ stack.push(...Object.values(value).map((value) => ({ value })));
2247
2352
  }
2248
- */
2249
- return;
2250
2353
  }
2251
2354
  }
2252
- else {
2253
- throw new UnexpectedError(spaceTrim$1((block) => `
2254
- \`${name}\` is unknown type
2355
+ */
2356
+ }
2357
+ /**
2358
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2359
+ *
2360
+ * @private function of `checkSerializableAsJson`
2361
+ */
2362
+ function throwUnknownTypeError(context) {
2363
+ throw new UnexpectedError(spaceTrim$1((block) => `
2364
+ \`${context.name}\` is unknown type
2255
2365
 
2256
- Additional message for \`${name}\`:
2257
- ${block(message || '(nothing)')}
2258
- `));
2259
- }
2366
+ Additional message for \`${context.name}\`:
2367
+ ${block(context.message || '(nothing)')}
2368
+ `));
2260
2369
  }
2261
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2262
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2263
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2264
2370
 
2265
2371
  /**
2266
2372
  * Creates a deep clone of the given object
@@ -3001,8 +3107,7 @@ function hasTaskJokers(task) {
3001
3107
  * @private internal utility of `validatePipeline`
3002
3108
  */
3003
3109
  function validateTaskSupportsJokers(task, pipelineIdentification) {
3004
- if (task.format ||
3005
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
3110
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
3006
3111
  return;
3007
3112
  }
3008
3113
  throw new PipelineLogicError(spaceTrim$1((block) => `
@@ -7391,9 +7496,7 @@ function createFailuresSummary($failedResults) {
7391
7496
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
7392
7497
 
7393
7498
  Result:
7394
- ${block(failure.result === null
7395
- ? 'null'
7396
- : quoteMultilineText(spaceTrim$1(failure.result)))}
7499
+ ${block(failure.result === null ? 'null' : quoteMultilineText(spaceTrim$1(failure.result)))}
7397
7500
  `;
7398
7501
  }))
7399
7502
  .join('\n\n---\n\n');
@@ -13436,7 +13539,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
13436
13539
  *
13437
13540
  * @private helper of `minecraft2AvatarVisual`
13438
13541
  */
13439
- const LIGHT_DIRECTION$1 = normalizeVector3({
13542
+ const LIGHT_DIRECTION$2 = normalizeVector3({
13440
13543
  x: 0.4,
13441
13544
  y: -0.65,
13442
13545
  z: 0.92,
@@ -13648,7 +13751,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
13648
13751
  corners: projectedCorners,
13649
13752
  texture: faceDefinition.texture,
13650
13753
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
13651
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
13754
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
13652
13755
  outlineColor: cuboid.outlineColor,
13653
13756
  };
13654
13757
  });
@@ -14708,13 +14811,138 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
14708
14811
  context.restore();
14709
14812
  }
14710
14813
 
14814
+ /* eslint-disable no-magic-numbers */
14815
+ /**
14816
+ * Draws one projected eye on a rotated octopus surface.
14817
+ *
14818
+ * @private helper of the 3D octopus avatar visuals
14819
+ */
14820
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
14821
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
14822
+ if (centerScenePoint.z <= center.z) {
14823
+ return;
14824
+ }
14825
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
14826
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
14827
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
14828
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
14829
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
14830
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
14831
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
14832
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
14833
+ return;
14834
+ }
14835
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
14836
+ radiusX: projectedRadiusX,
14837
+ radiusY: projectedRadiusY,
14838
+ timeMs,
14839
+ phase,
14840
+ interaction,
14841
+ });
14842
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
14843
+ context.save();
14844
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
14845
+ context.rotate(rotation);
14846
+ context.beginPath();
14847
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
14848
+ context.fillStyle = '#f8fbff';
14849
+ context.fill();
14850
+ context.clip();
14851
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
14852
+ irisGradient.addColorStop(0, palette.highlight);
14853
+ irisGradient.addColorStop(0.56, palette.secondary);
14854
+ irisGradient.addColorStop(1, palette.shadow);
14855
+ context.beginPath();
14856
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
14857
+ context.fillStyle = irisGradient;
14858
+ context.fill();
14859
+ context.beginPath();
14860
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
14861
+ context.fillStyle = palette.ink;
14862
+ context.fill();
14863
+ context.beginPath();
14864
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
14865
+ context.fillStyle = '#ffffff';
14866
+ context.fill();
14867
+ context.restore();
14868
+ context.save();
14869
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
14870
+ context.rotate(rotation);
14871
+ context.beginPath();
14872
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
14873
+ context.strokeStyle = `${palette.shadow}cc`;
14874
+ context.lineWidth = projectedRadiusX * 0.16;
14875
+ context.stroke();
14876
+ context.beginPath();
14877
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
14878
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
14879
+ context.strokeStyle = `${palette.shadow}73`;
14880
+ context.lineWidth = projectedRadiusX * 0.14;
14881
+ context.lineCap = 'round';
14882
+ context.stroke();
14883
+ if (eyeStyle.lowerLidOpacity > 0) {
14884
+ context.beginPath();
14885
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
14886
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
14887
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
14888
+ context.lineWidth = projectedRadiusX * 0.08;
14889
+ context.lineCap = 'round';
14890
+ context.stroke();
14891
+ }
14892
+ context.restore();
14893
+ }
14894
+ /**
14895
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
14896
+ *
14897
+ * @private helper of the 3D octopus avatar visuals
14898
+ */
14899
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
14900
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
14901
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
14902
+ return;
14903
+ }
14904
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
14905
+ context.beginPath();
14906
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
14907
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
14908
+ context.strokeStyle = `${palette.ink}b8`;
14909
+ context.lineWidth = Math.max(1.1, size * 0.009);
14910
+ context.lineCap = 'round';
14911
+ context.stroke();
14912
+ }
14913
+ /**
14914
+ * Draws one filled projected quad.
14915
+ *
14916
+ * @private helper of the 3D octopus avatar visuals
14917
+ */
14918
+ function drawProjectedQuad(context, corners, fillStyle) {
14919
+ context.beginPath();
14920
+ context.moveTo(corners[0].x, corners[0].y);
14921
+ context.lineTo(corners[1].x, corners[1].y);
14922
+ context.lineTo(corners[2].x, corners[2].y);
14923
+ context.lineTo(corners[3].x, corners[3].y);
14924
+ context.closePath();
14925
+ context.fillStyle = fillStyle;
14926
+ context.fill();
14927
+ }
14928
+ /**
14929
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
14930
+ *
14931
+ * @private helper of the 3D octopus avatar visuals
14932
+ */
14933
+ function formatAlphaHex(opacity) {
14934
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
14935
+ .toString(16)
14936
+ .padStart(2, '0');
14937
+ }
14938
+
14711
14939
  /* eslint-disable no-magic-numbers */
14712
14940
  /**
14713
14941
  * Light direction used by the organic 3D octopus shading.
14714
14942
  *
14715
14943
  * @private helper of `octopus3dAvatarVisual`
14716
14944
  */
14717
- const LIGHT_DIRECTION = normalizeVector3({
14945
+ const LIGHT_DIRECTION$1 = normalizeVector3({
14718
14946
  x: 0.48,
14719
14947
  y: -0.62,
14720
14948
  z: 0.94,
@@ -14827,17 +15055,17 @@ const octopus3dAvatarVisual = {
14827
15055
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
14828
15056
  drawTentacleStroke(context, tentacleStroke, palette);
14829
15057
  }
14830
- drawProjectedEye(context, {
15058
+ drawProjectedOrganicEye(context, {
14831
15059
  x: -faceEyeSpacing,
14832
15060
  y: faceEyeYOffset,
14833
15061
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
14834
15062
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
14835
- drawProjectedEye(context, {
15063
+ drawProjectedOrganicEye(context, {
14836
15064
  x: faceEyeSpacing,
14837
15065
  y: faceEyeYOffset,
14838
15066
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
14839
15067
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
14840
- drawProjectedMouth(context, [
15068
+ drawProjectedOrganicMouth(context, [
14841
15069
  {
14842
15070
  x: -mouthHalfWidth,
14843
15071
  y: mouthY,
@@ -14924,7 +15152,7 @@ function resolveVisibleEllipsoidPatches(options) {
14924
15152
  corners: projectedCorners,
14925
15153
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
14926
15154
  transformedCorners.length,
14927
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
15155
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
14928
15156
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
14929
15157
  outlineColor,
14930
15158
  });
@@ -15106,128 +15334,260 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
15106
15334
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
15107
15335
  return Math.sqrt(remainingDepthRatio) * radiusZ;
15108
15336
  }
15337
+
15338
+ /* eslint-disable no-magic-numbers */
15109
15339
  /**
15110
- * Draws one projected eye on the turned octopus mantle.
15340
+ * Light direction used by the single-mesh octopus shading.
15111
15341
  *
15112
- * @private helper of `octopus3dAvatarVisual`
15342
+ * @private helper of `octopus3d2AvatarVisual`
15113
15343
  */
15114
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
15115
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
15116
- if (centerScenePoint.z <= center.z) {
15117
- return;
15118
- }
15119
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
15120
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
15121
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
15122
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
15123
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
15124
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
15125
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
15126
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
15127
- return;
15128
- }
15129
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
15130
- radiusX: projectedRadiusX,
15131
- radiusY: projectedRadiusY,
15132
- timeMs,
15133
- phase,
15134
- interaction,
15135
- });
15136
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
15344
+ const LIGHT_DIRECTION = normalizeVector3({
15345
+ x: 0.38,
15346
+ y: -0.6,
15347
+ z: 0.98,
15348
+ });
15349
+ /**
15350
+ * Octopus 3D 2 avatar visual.
15351
+ *
15352
+ * @private built-in avatar visual
15353
+ */
15354
+ const octopus3d2AvatarVisual = {
15355
+ id: 'octopus3d2',
15356
+ title: 'Octopus 3D 2',
15357
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
15358
+ isAnimated: true,
15359
+ supportsPointerTracking: true,
15360
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
15361
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15362
+ const animationRandom = createRandom('octopus3d2-animation-profile');
15363
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
15364
+ const animationPhase = animationRandom() * Math.PI * 2;
15365
+ const sceneCenterX = size * 0.5;
15366
+ const sceneCenterY = size * 0.575;
15367
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
15368
+ const meshCenter = {
15369
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
15370
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
15371
+ z: interaction.intensity * size * 0.018,
15372
+ };
15373
+ const rotationY = -0.14 +
15374
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
15375
+ interaction.bodyOffsetX * 0.2 +
15376
+ interaction.gazeX * 0.78;
15377
+ const rotationX = -0.06 +
15378
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
15379
+ interaction.bodyOffsetY * 0.08 -
15380
+ interaction.gazeY * 0.34;
15381
+ const surfaceOptions = {
15382
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
15383
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
15384
+ radiusZ: size *
15385
+ morphologyProfile.body.bodyRadiusRatio *
15386
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
15387
+ morphologyProfile,
15388
+ timeMs,
15389
+ animationPhase,
15390
+ };
15391
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
15392
+ ...surfaceOptions,
15393
+ center: meshCenter,
15394
+ rotationX,
15395
+ rotationY,
15396
+ sceneCenterX,
15397
+ sceneCenterY,
15398
+ size,
15399
+ palette,
15400
+ });
15401
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
15402
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
15403
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
15404
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
15405
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
15406
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
15407
+ drawAvatarFrame(context, size, palette);
15408
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
15409
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
15410
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
15411
+ drawBlobbySurfacePatch(context, surfacePatch);
15412
+ }
15413
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
15414
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
15415
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
15416
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
15417
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15418
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15419
+ drawProjectedOrganicMouth(context, [
15420
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
15421
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
15422
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
15423
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
15424
+ },
15425
+ };
15426
+ /**
15427
+ * Draws the deep-water glow behind the continuous octopus mesh.
15428
+ *
15429
+ * @private helper of `octopus3d2AvatarVisual`
15430
+ */
15431
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
15432
+ 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);
15433
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
15434
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
15435
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
15436
+ context.fillStyle = glowGradient;
15437
+ context.fillRect(0, 0, size, size);
15438
+ 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);
15439
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
15440
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
15441
+ context.fillStyle = lowerGradient;
15442
+ context.fillRect(0, 0, size, size);
15443
+ }
15444
+ /**
15445
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
15446
+ *
15447
+ * @private helper of `octopus3d2AvatarVisual`
15448
+ */
15449
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
15137
15450
  context.save();
15138
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
15139
- context.rotate(rotation);
15140
- context.beginPath();
15141
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
15142
- context.fillStyle = '#f8fbff';
15143
- context.fill();
15144
- context.clip();
15145
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
15146
- irisGradient.addColorStop(0, palette.highlight);
15147
- irisGradient.addColorStop(0.56, palette.secondary);
15148
- irisGradient.addColorStop(1, palette.shadow);
15149
- context.beginPath();
15150
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
15151
- context.fillStyle = irisGradient;
15152
- context.fill();
15451
+ context.fillStyle = `${palette.shadow}66`;
15452
+ context.filter = `blur(${size * 0.024}px)`;
15153
15453
  context.beginPath();
15154
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
15155
- context.fillStyle = palette.ink;
15454
+ 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);
15156
15455
  context.fill();
15157
- context.beginPath();
15158
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
15159
- context.fillStyle = '#ffffff';
15160
- context.fill();
15161
- context.restore();
15162
- context.save();
15163
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
15164
- context.rotate(rotation);
15165
- context.beginPath();
15166
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
15167
- context.strokeStyle = `${palette.shadow}cc`;
15168
- context.lineWidth = projectedRadiusX * 0.16;
15169
- context.stroke();
15170
- context.beginPath();
15171
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
15172
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
15173
- context.strokeStyle = `${palette.shadow}73`;
15174
- context.lineWidth = projectedRadiusX * 0.14;
15175
- context.lineCap = 'round';
15176
- context.stroke();
15177
- if (eyeStyle.lowerLidOpacity > 0) {
15178
- context.beginPath();
15179
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
15180
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
15181
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
15182
- context.lineWidth = projectedRadiusX * 0.08;
15183
- context.lineCap = 'round';
15184
- context.stroke();
15185
- }
15186
15456
  context.restore();
15187
15457
  }
15188
15458
  /**
15189
- * Draws a subtle projected mouth arc across the front of the mantle.
15459
+ * Resolves all visible projected patches for the single blobby octopus mesh.
15190
15460
  *
15191
- * @private helper of `octopus3dAvatarVisual`
15461
+ * @private helper of `octopus3d2AvatarVisual`
15192
15462
  */
15193
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
15194
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
15195
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
15196
- return;
15463
+ function resolveVisibleBlobbyOctopusPatches(options) {
15464
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
15465
+ const latitudePatchCount = 12;
15466
+ const longitudePatchCount = 24;
15467
+ const surfacePatches = [];
15468
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
15469
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
15470
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
15471
+ const centerLatitude = (startLatitude + endLatitude) / 2;
15472
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
15473
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
15474
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
15475
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
15476
+ const centerLongitude = (startLongitude + endLongitude) / 2;
15477
+ const localCorners = [
15478
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
15479
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
15480
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
15481
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
15482
+ ];
15483
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
15484
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
15485
+ if (surfaceNormal.z <= 0.01) {
15486
+ continue;
15487
+ }
15488
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
15489
+ surfacePatches.push({
15490
+ corners: projectedCorners,
15491
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
15492
+ transformedCorners.length,
15493
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
15494
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
15495
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
15496
+ });
15497
+ }
15197
15498
  }
15198
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
15199
- context.beginPath();
15200
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
15201
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
15202
- context.strokeStyle = `${palette.ink}b8`;
15203
- context.lineWidth = Math.max(1.1, size * 0.009);
15204
- context.lineCap = 'round';
15205
- context.stroke();
15499
+ return surfacePatches;
15206
15500
  }
15207
15501
  /**
15208
- * Draws one filled projected quad.
15502
+ * Samples one point on the continuous Octopus 3D 2 surface.
15503
+ *
15504
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
15505
+ * instead of switching to separately rendered tentacles.
15506
+ *
15507
+ * @private helper of `octopus3d2AvatarVisual`
15508
+ */
15509
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
15510
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
15511
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
15512
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
15513
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
15514
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
15515
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
15516
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
15517
+ const horizontalScale = 1.02 +
15518
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
15519
+ upperBlend * 0.08;
15520
+ const depthScale = 1.04 +
15521
+ upperBlend * 0.16 +
15522
+ Math.max(0, Math.cos(longitude)) * 0.1 +
15523
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
15524
+ Math.max(0, -Math.cos(longitude)) * 0.04;
15525
+ const lowerDrop = skirtEnvelope *
15526
+ radiusY *
15527
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
15528
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
15529
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
15530
+ return {
15531
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
15532
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
15533
+ upperBlend * radiusY * 0.1 +
15534
+ lowerDrop +
15535
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
15536
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
15537
+ };
15538
+ }
15539
+ /**
15540
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
15209
15541
  *
15210
- * @private helper of `octopus3dAvatarVisual`
15542
+ * @private helper of `octopus3d2AvatarVisual`
15211
15543
  */
15212
- function drawProjectedQuad(context, corners, fillStyle) {
15213
- context.beginPath();
15214
- context.moveTo(corners[0].x, corners[0].y);
15215
- context.lineTo(corners[1].x, corners[1].y);
15216
- context.lineTo(corners[2].x, corners[2].y);
15217
- context.lineTo(corners[3].x, corners[3].y);
15218
- context.closePath();
15219
- context.fillStyle = fillStyle;
15220
- context.fill();
15544
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
15545
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
15546
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
15221
15547
  }
15222
15548
  /**
15223
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
15549
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
15224
15550
  *
15225
- * @private helper of `octopus3dAvatarVisual`
15551
+ * @private helper of `octopus3d2AvatarVisual`
15226
15552
  */
15227
- function formatAlphaHex(opacity) {
15228
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
15229
- .toString(16)
15230
- .padStart(2, '0');
15553
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
15554
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
15555
+ if (tonalProgress < 0.16) {
15556
+ return palette.highlight;
15557
+ }
15558
+ if (tonalProgress < 0.34) {
15559
+ return palette.secondary;
15560
+ }
15561
+ if (tonalProgress < 0.72) {
15562
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
15563
+ }
15564
+ return `${palette.shadow}f2`;
15565
+ }
15566
+ /**
15567
+ * Draws one projected patch with soft octopus shading.
15568
+ *
15569
+ * @private helper of `octopus3d2AvatarVisual`
15570
+ */
15571
+ function drawBlobbySurfacePatch(context, surfacePatch) {
15572
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
15573
+ if (surfacePatch.lightIntensity > 0) {
15574
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
15575
+ }
15576
+ else if (surfacePatch.lightIntensity < 0) {
15577
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
15578
+ }
15579
+ context.save();
15580
+ context.beginPath();
15581
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
15582
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
15583
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
15584
+ }
15585
+ context.closePath();
15586
+ context.strokeStyle = surfacePatch.outlineColor;
15587
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
15588
+ context.lineJoin = 'round';
15589
+ context.stroke();
15590
+ context.restore();
15231
15591
  }
15232
15592
 
15233
15593
  /* eslint-disable no-magic-numbers */
@@ -15999,6 +16359,7 @@ const AVATAR_VISUALS = [
15999
16359
  octopus2AvatarVisual,
16000
16360
  octopus3AvatarVisual,
16001
16361
  octopus3dAvatarVisual,
16362
+ octopus3d2AvatarVisual,
16002
16363
  asciiOctopusAvatarVisual,
16003
16364
  minecraftAvatarVisual,
16004
16365
  minecraft2AvatarVisual,
@@ -21411,11 +21772,11 @@ function buildCandidateEncodings(options) {
21411
21772
  });
21412
21773
  }
21413
21774
  /**
21414
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
21775
+ * Prepares one attachment for best-effort text decoding.
21415
21776
  *
21416
- * @private internal utility for shared text decoding
21777
+ * @private function of decodeAttachmentAsText
21417
21778
  */
21418
- function decodeAttachmentAsText(input, options = {}) {
21779
+ function createDecodeAttachmentPreparation(input, options) {
21419
21780
  var _a;
21420
21781
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
21421
21782
  const forceText = options.forceText === true;
@@ -21429,54 +21790,102 @@ function decodeAttachmentAsText(input, options = {}) {
21429
21790
  const inspection = inspectBytes(truncatedBytes);
21430
21791
  const trustedTextMime = isTrustedTextMimeType(mimeType);
21431
21792
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
21793
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
21432
21794
  if (isTruncated) {
21433
21795
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
21434
21796
  }
21435
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
21436
- if (shouldTreatAsBinary && !forceText) {
21437
- warnings.push('File content looks binary, so text decoding was skipped.');
21438
- return {
21439
- text: '',
21440
- encodingUsed: 'binary',
21441
- confidence: 1,
21442
- warnings,
21443
- wasBinary: true,
21444
- isTruncated,
21445
- };
21797
+ return {
21798
+ warnings,
21799
+ charset,
21800
+ bom,
21801
+ inspection,
21802
+ truncatedBytes,
21803
+ isTruncated,
21804
+ forceText,
21805
+ shouldTreatAsBinary,
21806
+ };
21807
+ }
21808
+ /**
21809
+ * Returns an early result when the attachment should stay classified as binary.
21810
+ *
21811
+ * @private function of decodeAttachmentAsText
21812
+ */
21813
+ function createBinaryDecodeResult(preparation) {
21814
+ if (!preparation.shouldTreatAsBinary) {
21815
+ return null;
21446
21816
  }
21447
- if (shouldTreatAsBinary && forceText) {
21448
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
21817
+ if (preparation.forceText) {
21818
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
21819
+ return null;
21449
21820
  }
21450
- if (charset && !isSupportedEncoding(charset)) {
21451
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
21821
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
21822
+ return {
21823
+ text: '',
21824
+ encodingUsed: 'binary',
21825
+ confidence: 1,
21826
+ warnings: preparation.warnings,
21827
+ wasBinary: true,
21828
+ isTruncated: preparation.isTruncated,
21829
+ };
21830
+ }
21831
+ /**
21832
+ * Warns when the declared charset cannot be used by the runtime decoder.
21833
+ *
21834
+ * @private function of decodeAttachmentAsText
21835
+ */
21836
+ function addUnsupportedCharsetWarning(preparation) {
21837
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
21838
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
21452
21839
  }
21453
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
21454
- const candidates = buildCandidateEncodings({
21455
- mimeType,
21456
- charset: charset && isSupportedEncoding(charset) ? charset : null,
21457
- bom,
21458
- inspection,
21459
- });
21460
- const decodedCandidates = candidates
21840
+ }
21841
+ /**
21842
+ * Returns the byte slice that should actually be decoded as text.
21843
+ *
21844
+ * @private function of decodeAttachmentAsText
21845
+ */
21846
+ function getBytesToDecode(preparation) {
21847
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
21848
+ }
21849
+ /**
21850
+ * Decodes all candidate encodings and sorts the successful results by score.
21851
+ *
21852
+ * @private function of decodeAttachmentAsText
21853
+ */
21854
+ function decodeAttachmentCandidates(preparation) {
21855
+ return buildCandidateEncodings({
21856
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
21857
+ bom: preparation.bom,
21858
+ inspection: preparation.inspection,
21859
+ })
21461
21860
  .map(({ encoding, source }) => {
21462
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
21861
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
21463
21862
  return decoded ? { ...decoded, source } : null;
21464
21863
  })
21465
21864
  .filter((candidate) => candidate !== null)
21466
21865
  .sort((left, right) => left.score - right.score);
21467
- const bestCandidate = decodedCandidates[0];
21468
- if (!bestCandidate) {
21469
- warnings.push('No supported text decoder was available.');
21470
- return {
21471
- text: '',
21472
- encodingUsed: 'binary',
21473
- confidence: 0,
21474
- warnings,
21475
- wasBinary: true,
21476
- isTruncated,
21477
- };
21478
- }
21479
- const secondBestCandidate = decodedCandidates[1];
21866
+ }
21867
+ /**
21868
+ * Returns the fallback result used when no text decoder could be applied.
21869
+ *
21870
+ * @private function of decodeAttachmentAsText
21871
+ */
21872
+ function createNoDecoderAvailableResult(preparation) {
21873
+ preparation.warnings.push('No supported text decoder was available.');
21874
+ return {
21875
+ text: '',
21876
+ encodingUsed: 'binary',
21877
+ confidence: 0,
21878
+ warnings: preparation.warnings,
21879
+ wasBinary: true,
21880
+ isTruncated: preparation.isTruncated,
21881
+ };
21882
+ }
21883
+ /**
21884
+ * Estimates confidence for the winning decoded text candidate.
21885
+ *
21886
+ * @private function of decodeAttachmentAsText
21887
+ */
21888
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
21480
21889
  const baseConfidence = bestCandidate.source === 'bom'
21481
21890
  ? 1
21482
21891
  : bestCandidate.source === 'charset'
@@ -21489,27 +21898,62 @@ function decodeAttachmentAsText(input, options = {}) {
21489
21898
  ? 0.82
21490
21899
  : 0.62;
21491
21900
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
21492
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
21901
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
21902
+ }
21903
+ /**
21904
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
21905
+ *
21906
+ * @private function of decodeAttachmentAsText
21907
+ */
21908
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
21493
21909
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
21494
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
21910
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
21495
21911
  }
21496
21912
  if (bestCandidate.source === 'heuristic' &&
21497
21913
  bestCandidate.encoding === 'utf-8' &&
21498
21914
  bestCandidate.replacementCount > 0) {
21499
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
21915
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
21500
21916
  }
21501
21917
  if (confidence < 0.6) {
21502
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
21918
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
21503
21919
  }
21920
+ }
21921
+ /**
21922
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
21923
+ *
21924
+ * @private function of decodeAttachmentAsText
21925
+ */
21926
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
21504
21927
  return {
21505
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
21928
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
21506
21929
  encodingUsed: bestCandidate.encoding,
21507
21930
  confidence,
21508
- warnings,
21931
+ warnings: preparation.warnings,
21509
21932
  wasBinary: false,
21510
- isTruncated,
21933
+ isTruncated: preparation.isTruncated,
21511
21934
  };
21512
21935
  }
21936
+ /**
21937
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
21938
+ *
21939
+ * @private internal utility for shared text decoding
21940
+ */
21941
+ function decodeAttachmentAsText(input, options = {}) {
21942
+ const preparation = createDecodeAttachmentPreparation(input, options);
21943
+ const binaryResult = createBinaryDecodeResult(preparation);
21944
+ if (binaryResult) {
21945
+ return binaryResult;
21946
+ }
21947
+ addUnsupportedCharsetWarning(preparation);
21948
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
21949
+ const bestCandidate = decodedCandidates[0];
21950
+ if (!bestCandidate) {
21951
+ return createNoDecoderAvailableResult(preparation);
21952
+ }
21953
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
21954
+ addDecodeWarnings(bestCandidate, confidence, preparation);
21955
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
21956
+ }
21513
21957
 
21514
21958
  /**
21515
21959
  * Base GitHub API URL.
@@ -33867,10 +34311,7 @@ function createAvailableProviderMessage(llmToolStatus, index, env) {
33867
34311
  * @private internal function of `$registeredLlmToolsMessage`
33868
34312
  */
33869
34313
  function createProviderStatusMessages(llmToolStatus, env) {
33870
- return [
33871
- createInstallationStatusMessage(llmToolStatus),
33872
- createConfigurationStatusMessage(llmToolStatus, env),
33873
- ];
34314
+ return [createInstallationStatusMessage(llmToolStatus), createConfigurationStatusMessage(llmToolStatus, env)];
33874
34315
  }
33875
34316
  /**
33876
34317
  * Creates the installation-status sentence for one provider.
@@ -36131,7 +36572,10 @@ class OpenAiCompatibleModelCatalog {
36131
36572
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
36132
36573
 
36133
36574
  Available models:
36134
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
36575
+ ${block(this.options
36576
+ .getHardcodedModels()
36577
+ .map(({ modelName }) => `- "${modelName}"`)
36578
+ .join('\n'))}
36135
36579
 
36136
36580
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
36137
36581
 
@@ -36485,7 +36929,8 @@ class OpenAiCompatibleNonChatPromptCaller {
36485
36929
  };
36486
36930
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
36487
36931
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
36488
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
36932
+ rawPromptContent +=
36933
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
36489
36934
  }
36490
36935
  const rawRequest = {
36491
36936
  ...modelSettings,
@@ -36590,7 +37035,9 @@ class OpenAiCompatibleRequestManager {
36590
37035
  * Schedules one request through the shared limiter and retry policy.
36591
37036
  */
36592
37037
  async executeRateLimitedRequest(requestFn) {
36593
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
37038
+ return this.limiter
37039
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
37040
+ .catch((error) => {
36594
37041
  assertsError(error);
36595
37042
  if (this.options.isVerbose) {
36596
37043
  console.info(colors.bgRed('error'), error);
@@ -37797,7 +38244,9 @@ class OpenAiVectorStoreFileBatchPoller {
37797
38244
  pollingState.lastProgressAtMs = nowMs;
37798
38245
  pollingState.lastProgressKey = progressKey;
37799
38246
  }
37800
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
38247
+ if (this.options.isVerbose &&
38248
+ (statusCountsKey !== pollingState.lastCountsKey ||
38249
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
37801
38250
  console.info('[🤰]', 'Vector store file batch status', {
37802
38251
  vectorStoreId,
37803
38252
  batchId,