@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/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-80';
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);
727
- }
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;
759
- }
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
- }
838
+ super(red, green, blue, alpha);
791
839
  }
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
- }
840
+ createColor(red, green, blue, alpha) {
841
+ return new Color(red, green, blue, alpha);
799
842
  }
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
 
@@ -1981,7 +2024,7 @@
1981
2024
  */
1982
2025
  function createPostprocessingCommands(task) {
1983
2026
  var _a;
1984
- return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
2027
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
1985
2028
  }
1986
2029
  /**
1987
2030
  * Collects expectation commands.
@@ -2146,120 +2189,183 @@
2146
2189
  * @public exported from `@promptbook/utils`
2147
2190
  */
2148
2191
  function checkSerializableAsJson(options) {
2149
- const { value, name, message } = options;
2192
+ checkSerializableValue(options);
2193
+ }
2194
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2195
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2196
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2197
+ /**
2198
+ * Checks one value and dispatches to the appropriate specialized validator.
2199
+ *
2200
+ * @private function of `checkSerializableAsJson`
2201
+ */
2202
+ function checkSerializableValue(options) {
2203
+ const { value } = options;
2204
+ if (isSerializablePrimitive(value)) {
2205
+ return;
2206
+ }
2150
2207
  if (value === undefined) {
2151
- throw new UnexpectedError(`${name} is undefined`);
2208
+ throw new UnexpectedError(`${options.name} is undefined`);
2152
2209
  }
2153
- else if (value === null) {
2154
- return;
2210
+ if (typeof value === 'symbol') {
2211
+ throw new UnexpectedError(`${options.name} is symbol`);
2155
2212
  }
2156
- else if (typeof value === 'boolean') {
2157
- return;
2213
+ if (typeof value === 'function') {
2214
+ throw new UnexpectedError(`${options.name} is function`);
2158
2215
  }
2159
- else if (typeof value === 'number' && !isNaN(value)) {
2216
+ if (Array.isArray(value)) {
2217
+ checkSerializableArray(options, value);
2160
2218
  return;
2161
2219
  }
2162
- else if (typeof value === 'string') {
2220
+ if (value !== null && typeof value === 'object') {
2221
+ checkSerializableObject(options, value);
2163
2222
  return;
2164
2223
  }
2165
- else if (typeof value === 'symbol') {
2166
- throw new UnexpectedError(`${name} is symbol`);
2167
- }
2168
- else if (typeof value === 'function') {
2169
- throw new UnexpectedError(`${name} is function`);
2170
- }
2171
- else if (typeof value === 'object' && Array.isArray(value)) {
2172
- for (let i = 0; i < value.length; i++) {
2173
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
2174
- }
2224
+ throwUnknownTypeError(options);
2225
+ }
2226
+ /**
2227
+ * Checks the primitive values that are directly JSON serializable.
2228
+ *
2229
+ * @private function of `checkSerializableAsJson`
2230
+ */
2231
+ function isSerializablePrimitive(value) {
2232
+ return (value === null ||
2233
+ typeof value === 'boolean' ||
2234
+ (typeof value === 'number' && !isNaN(value)) ||
2235
+ typeof value === 'string');
2236
+ }
2237
+ /**
2238
+ * Recursively checks JSON array items.
2239
+ *
2240
+ * @private function of `checkSerializableAsJson`
2241
+ */
2242
+ function checkSerializableArray(context, arrayValue) {
2243
+ for (let index = 0; index < arrayValue.length; index++) {
2244
+ checkSerializableAsJson({
2245
+ ...context,
2246
+ name: `${context.name}[${index}]`,
2247
+ value: arrayValue[index],
2248
+ });
2175
2249
  }
2176
- else if (typeof value === 'object') {
2177
- if (value instanceof Date) {
2178
- throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2179
- \`${name}\` is Date
2250
+ }
2251
+ /**
2252
+ * Checks object-like values and dispatches special unsupported built-ins.
2253
+ *
2254
+ * @private function of `checkSerializableAsJson`
2255
+ */
2256
+ function checkSerializableObject(context, objectValue) {
2257
+ checkUnsupportedObjectType(context, objectValue);
2258
+ checkSerializableObjectEntries(context, objectValue);
2259
+ assertJsonStringificationSucceeds(context, objectValue);
2260
+ }
2261
+ /**
2262
+ * Rejects built-in objects that must be converted before JSON serialization.
2263
+ *
2264
+ * @private function of `checkSerializableAsJson`
2265
+ */
2266
+ function checkUnsupportedObjectType(context, objectValue) {
2267
+ if (objectValue instanceof Date) {
2268
+ throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2269
+ \`${context.name}\` is Date
2180
2270
 
2181
- Use \`string_date_iso8601\` instead
2271
+ Use \`string_date_iso8601\` instead
2182
2272
 
2183
- Additional message for \`${name}\`:
2184
- ${block(message || '(nothing)')}
2185
- `));
2186
- }
2187
- else if (value instanceof Map) {
2188
- throw new UnexpectedError(`${name} is Map`);
2189
- }
2190
- else if (value instanceof Set) {
2191
- throw new UnexpectedError(`${name} is Set`);
2192
- }
2193
- else if (value instanceof RegExp) {
2194
- throw new UnexpectedError(`${name} is RegExp`);
2195
- }
2196
- else if (value instanceof Error) {
2197
- throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2198
- \`${name}\` is unserialized Error
2273
+ Additional message for \`${context.name}\`:
2274
+ ${block(context.message || '(nothing)')}
2275
+ `));
2276
+ }
2277
+ if (objectValue instanceof Map) {
2278
+ throw new UnexpectedError(`${context.name} is Map`);
2279
+ }
2280
+ if (objectValue instanceof Set) {
2281
+ throw new UnexpectedError(`${context.name} is Set`);
2282
+ }
2283
+ if (objectValue instanceof RegExp) {
2284
+ throw new UnexpectedError(`${context.name} is RegExp`);
2285
+ }
2286
+ if (objectValue instanceof Error) {
2287
+ throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2288
+ \`${context.name}\` is unserialized Error
2199
2289
 
2200
- Use function \`serializeError\`
2290
+ Use function \`serializeError\`
2201
2291
 
2202
- Additional message for \`${name}\`:
2203
- ${block(message || '(nothing)')}
2292
+ Additional message for \`${context.name}\`:
2293
+ ${block(context.message || '(nothing)')}
2204
2294
 
2205
- `));
2295
+ `));
2296
+ }
2297
+ }
2298
+ /**
2299
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2300
+ *
2301
+ * @private function of `checkSerializableAsJson`
2302
+ */
2303
+ function checkSerializableObjectEntries(context, objectValue) {
2304
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2305
+ if (subValue === undefined) {
2306
+ // Note: undefined in object is serializable - it is just omitted
2307
+ continue;
2206
2308
  }
2207
- else {
2208
- for (const [subName, subValue] of Object.entries(value)) {
2209
- if (subValue === undefined) {
2210
- // Note: undefined in object is serializable - it is just omitted
2211
- continue;
2212
- }
2213
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
2214
- }
2215
- try {
2216
- JSON.stringify(value); // <- TODO: [0]
2217
- }
2218
- catch (error) {
2219
- assertsError(error);
2220
- throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2221
- \`${name}\` is not serializable
2309
+ checkSerializableAsJson({
2310
+ ...context,
2311
+ name: `${context.name}.${subName}`,
2312
+ value: subValue,
2313
+ });
2314
+ }
2315
+ }
2316
+ /**
2317
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2318
+ *
2319
+ * @private function of `checkSerializableAsJson`
2320
+ */
2321
+ function assertJsonStringificationSucceeds(context, objectValue) {
2322
+ try {
2323
+ JSON.stringify(objectValue); // <- TODO: [0]
2324
+ }
2325
+ catch (error) {
2326
+ assertsError(error);
2327
+ throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2328
+ \`${context.name}\` is not serializable
2222
2329
 
2223
- ${block(error.stack || error.message)}
2330
+ ${block(error.stack || error.message)}
2224
2331
 
2225
- Additional message for \`${name}\`:
2226
- ${block(message || '(nothing)')}
2227
- `));
2332
+ Additional message for \`${context.name}\`:
2333
+ ${block(context.message || '(nothing)')}
2334
+ `));
2335
+ }
2336
+ /*
2337
+ TODO: [0] Is there some more elegant way to check circular references?
2338
+ const seen = new Set();
2339
+ const stack = [{ value }];
2340
+ while (stack.length > 0) {
2341
+ const { value } = stack.pop()!;
2342
+ if (typeof value === 'object' && value !== null) {
2343
+ if (seen.has(value)) {
2344
+ throw new UnexpectedError(`${name} has circular reference`);
2228
2345
  }
2229
- /*
2230
- TODO: [0] Is there some more elegant way to check circular references?
2231
- const seen = new Set();
2232
- const stack = [{ value }];
2233
- while (stack.length > 0) {
2234
- const { value } = stack.pop()!;
2235
- if (typeof value === 'object' && value !== null) {
2236
- if (seen.has(value)) {
2237
- throw new UnexpectedError(`${name} has circular reference`);
2238
- }
2239
- seen.add(value);
2240
- if (Array.isArray(value)) {
2241
- stack.push(...value.map((value) => ({ value })));
2242
- } else {
2243
- stack.push(...Object.values(value).map((value) => ({ value })));
2244
- }
2245
- }
2346
+ seen.add(value);
2347
+ if (Array.isArray(value)) {
2348
+ stack.push(...value.map((value) => ({ value })));
2349
+ } else {
2350
+ stack.push(...Object.values(value).map((value) => ({ value })));
2246
2351
  }
2247
- */
2248
- return;
2249
2352
  }
2250
2353
  }
2251
- else {
2252
- throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2253
- \`${name}\` is unknown type
2354
+ */
2355
+ }
2356
+ /**
2357
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2358
+ *
2359
+ * @private function of `checkSerializableAsJson`
2360
+ */
2361
+ function throwUnknownTypeError(context) {
2362
+ throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2363
+ \`${context.name}\` is unknown type
2254
2364
 
2255
- Additional message for \`${name}\`:
2256
- ${block(message || '(nothing)')}
2257
- `));
2258
- }
2365
+ Additional message for \`${context.name}\`:
2366
+ ${block(context.message || '(nothing)')}
2367
+ `));
2259
2368
  }
2260
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2261
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2262
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2263
2369
 
2264
2370
  /**
2265
2371
  * Creates a deep clone of the given object
@@ -3000,8 +3106,7 @@
3000
3106
  * @private internal utility of `validatePipeline`
3001
3107
  */
3002
3108
  function validateTaskSupportsJokers(task, pipelineIdentification) {
3003
- if (task.format ||
3004
- task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
3109
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
3005
3110
  return;
3006
3111
  }
3007
3112
  throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
@@ -7390,9 +7495,7 @@
7390
7495
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
7391
7496
 
7392
7497
  Result:
7393
- ${block(failure.result === null
7394
- ? 'null'
7395
- : quoteMultilineText(spacetrim.spaceTrim(failure.result)))}
7498
+ ${block(failure.result === null ? 'null' : quoteMultilineText(spacetrim.spaceTrim(failure.result)))}
7396
7499
  `;
7397
7500
  }))
7398
7501
  .join('\n\n---\n\n');
@@ -13435,7 +13538,7 @@
13435
13538
  *
13436
13539
  * @private helper of `minecraft2AvatarVisual`
13437
13540
  */
13438
- const LIGHT_DIRECTION$1 = normalizeVector3({
13541
+ const LIGHT_DIRECTION$2 = normalizeVector3({
13439
13542
  x: 0.4,
13440
13543
  y: -0.65,
13441
13544
  z: 0.92,
@@ -13647,7 +13750,7 @@
13647
13750
  corners: projectedCorners,
13648
13751
  texture: faceDefinition.texture,
13649
13752
  averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
13650
- lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$1), -1, 1),
13753
+ lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$2), -1, 1),
13651
13754
  outlineColor: cuboid.outlineColor,
13652
13755
  };
13653
13756
  });
@@ -14707,13 +14810,138 @@
14707
14810
  context.restore();
14708
14811
  }
14709
14812
 
14813
+ /* eslint-disable no-magic-numbers */
14814
+ /**
14815
+ * Draws one projected eye on a rotated octopus surface.
14816
+ *
14817
+ * @private helper of the 3D octopus avatar visuals
14818
+ */
14819
+ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
14820
+ const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
14821
+ if (centerScenePoint.z <= center.z) {
14822
+ return;
14823
+ }
14824
+ const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
14825
+ const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
14826
+ const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
14827
+ const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
14828
+ const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
14829
+ const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
14830
+ const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
14831
+ if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
14832
+ return;
14833
+ }
14834
+ const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
14835
+ radiusX: projectedRadiusX,
14836
+ radiusY: projectedRadiusY,
14837
+ timeMs,
14838
+ phase,
14839
+ interaction,
14840
+ });
14841
+ const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
14842
+ context.save();
14843
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
14844
+ context.rotate(rotation);
14845
+ context.beginPath();
14846
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
14847
+ context.fillStyle = '#f8fbff';
14848
+ context.fill();
14849
+ context.clip();
14850
+ const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
14851
+ irisGradient.addColorStop(0, palette.highlight);
14852
+ irisGradient.addColorStop(0.56, palette.secondary);
14853
+ irisGradient.addColorStop(1, palette.shadow);
14854
+ context.beginPath();
14855
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
14856
+ context.fillStyle = irisGradient;
14857
+ context.fill();
14858
+ context.beginPath();
14859
+ context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
14860
+ context.fillStyle = palette.ink;
14861
+ context.fill();
14862
+ context.beginPath();
14863
+ context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
14864
+ context.fillStyle = '#ffffff';
14865
+ context.fill();
14866
+ context.restore();
14867
+ context.save();
14868
+ context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
14869
+ context.rotate(rotation);
14870
+ context.beginPath();
14871
+ context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
14872
+ context.strokeStyle = `${palette.shadow}cc`;
14873
+ context.lineWidth = projectedRadiusX * 0.16;
14874
+ context.stroke();
14875
+ context.beginPath();
14876
+ context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
14877
+ context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
14878
+ context.strokeStyle = `${palette.shadow}73`;
14879
+ context.lineWidth = projectedRadiusX * 0.14;
14880
+ context.lineCap = 'round';
14881
+ context.stroke();
14882
+ if (eyeStyle.lowerLidOpacity > 0) {
14883
+ context.beginPath();
14884
+ context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
14885
+ context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
14886
+ context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
14887
+ context.lineWidth = projectedRadiusX * 0.08;
14888
+ context.lineCap = 'round';
14889
+ context.stroke();
14890
+ }
14891
+ context.restore();
14892
+ }
14893
+ /**
14894
+ * Draws a subtle projected mouth arc across the front of a rotated octopus surface.
14895
+ *
14896
+ * @private helper of the 3D octopus avatar visuals
14897
+ */
14898
+ function drawProjectedOrganicMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
14899
+ const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
14900
+ if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
14901
+ return;
14902
+ }
14903
+ const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
14904
+ context.beginPath();
14905
+ context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
14906
+ context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
14907
+ context.strokeStyle = `${palette.ink}b8`;
14908
+ context.lineWidth = Math.max(1.1, size * 0.009);
14909
+ context.lineCap = 'round';
14910
+ context.stroke();
14911
+ }
14912
+ /**
14913
+ * Draws one filled projected quad.
14914
+ *
14915
+ * @private helper of the 3D octopus avatar visuals
14916
+ */
14917
+ function drawProjectedQuad(context, corners, fillStyle) {
14918
+ context.beginPath();
14919
+ context.moveTo(corners[0].x, corners[0].y);
14920
+ context.lineTo(corners[1].x, corners[1].y);
14921
+ context.lineTo(corners[2].x, corners[2].y);
14922
+ context.lineTo(corners[3].x, corners[3].y);
14923
+ context.closePath();
14924
+ context.fillStyle = fillStyle;
14925
+ context.fill();
14926
+ }
14927
+ /**
14928
+ * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
14929
+ *
14930
+ * @private helper of the 3D octopus avatar visuals
14931
+ */
14932
+ function formatAlphaHex(opacity) {
14933
+ return Math.round(clampNumber$1(opacity, 0, 1) * 255)
14934
+ .toString(16)
14935
+ .padStart(2, '0');
14936
+ }
14937
+
14710
14938
  /* eslint-disable no-magic-numbers */
14711
14939
  /**
14712
14940
  * Light direction used by the organic 3D octopus shading.
14713
14941
  *
14714
14942
  * @private helper of `octopus3dAvatarVisual`
14715
14943
  */
14716
- const LIGHT_DIRECTION = normalizeVector3({
14944
+ const LIGHT_DIRECTION$1 = normalizeVector3({
14717
14945
  x: 0.48,
14718
14946
  y: -0.62,
14719
14947
  z: 0.94,
@@ -14826,17 +15054,17 @@
14826
15054
  for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
14827
15055
  drawTentacleStroke(context, tentacleStroke, palette);
14828
15056
  }
14829
- drawProjectedEye(context, {
15057
+ drawProjectedOrganicEye(context, {
14830
15058
  x: -faceEyeSpacing,
14831
15059
  y: faceEyeYOffset,
14832
15060
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
14833
15061
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
14834
- drawProjectedEye(context, {
15062
+ drawProjectedOrganicEye(context, {
14835
15063
  x: faceEyeSpacing,
14836
15064
  y: faceEyeYOffset,
14837
15065
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
14838
15066
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
14839
- drawProjectedMouth(context, [
15067
+ drawProjectedOrganicMouth(context, [
14840
15068
  {
14841
15069
  x: -mouthHalfWidth,
14842
15070
  y: mouthY,
@@ -14923,7 +15151,7 @@
14923
15151
  corners: projectedCorners,
14924
15152
  averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
14925
15153
  transformedCorners.length,
14926
- lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
15154
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
14927
15155
  fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
14928
15156
  outlineColor,
14929
15157
  });
@@ -15105,128 +15333,260 @@
15105
15333
  const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
15106
15334
  return Math.sqrt(remainingDepthRatio) * radiusZ;
15107
15335
  }
15336
+
15337
+ /* eslint-disable no-magic-numbers */
15108
15338
  /**
15109
- * Draws one projected eye on the turned octopus mantle.
15339
+ * Light direction used by the single-mesh octopus shading.
15110
15340
  *
15111
- * @private helper of `octopus3dAvatarVisual`
15341
+ * @private helper of `octopus3d2AvatarVisual`
15112
15342
  */
15113
- function drawProjectedEye(context, localCenter, radiusX, radiusY, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, phase, interaction, eyeStyle) {
15114
- const centerScenePoint = transformScenePoint(localCenter, center, rotationX, rotationY);
15115
- if (centerScenePoint.z <= center.z) {
15116
- return;
15117
- }
15118
- const horizontalScenePoint = transformScenePoint({ x: localCenter.x + radiusX, y: localCenter.y, z: localCenter.z }, center, rotationX, rotationY);
15119
- const verticalScenePoint = transformScenePoint({ x: localCenter.x, y: localCenter.y + radiusY, z: localCenter.z }, center, rotationX, rotationY);
15120
- const projectedCenterPoint = projectScenePoint(centerScenePoint, size, sceneCenterX, sceneCenterY);
15121
- const projectedHorizontalPoint = projectScenePoint(horizontalScenePoint, size, sceneCenterX, sceneCenterY);
15122
- const projectedVerticalPoint = projectScenePoint(verticalScenePoint, size, sceneCenterX, sceneCenterY);
15123
- const projectedRadiusX = Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y);
15124
- const projectedRadiusY = Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y);
15125
- if (projectedRadiusX < size * 0.008 || projectedRadiusY < size * 0.008) {
15126
- return;
15127
- }
15128
- const { pupilOffsetX, pupilOffsetY } = resolveOrganicEyeMotion({
15129
- radiusX: projectedRadiusX,
15130
- radiusY: projectedRadiusY,
15131
- timeMs,
15132
- phase,
15133
- interaction,
15134
- });
15135
- const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
15343
+ const LIGHT_DIRECTION = normalizeVector3({
15344
+ x: 0.38,
15345
+ y: -0.6,
15346
+ z: 0.98,
15347
+ });
15348
+ /**
15349
+ * Octopus 3D 2 avatar visual.
15350
+ *
15351
+ * @private built-in avatar visual
15352
+ */
15353
+ const octopus3d2AvatarVisual = {
15354
+ id: 'octopus3d2',
15355
+ title: 'Octopus 3D 2',
15356
+ description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
15357
+ isAnimated: true,
15358
+ supportsPointerTracking: true,
15359
+ render({ context, size, palette, createRandom, timeMs, interaction }) {
15360
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15361
+ const animationRandom = createRandom('octopus3d2-animation-profile');
15362
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
15363
+ const animationPhase = animationRandom() * Math.PI * 2;
15364
+ const sceneCenterX = size * 0.5;
15365
+ const sceneCenterY = size * 0.575;
15366
+ const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
15367
+ const meshCenter = {
15368
+ x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
15369
+ y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
15370
+ z: interaction.intensity * size * 0.018,
15371
+ };
15372
+ const rotationY = -0.14 +
15373
+ Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
15374
+ interaction.bodyOffsetX * 0.2 +
15375
+ interaction.gazeX * 0.78;
15376
+ const rotationX = -0.06 +
15377
+ Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
15378
+ interaction.bodyOffsetY * 0.08 -
15379
+ interaction.gazeY * 0.34;
15380
+ const surfaceOptions = {
15381
+ radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
15382
+ radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
15383
+ radiusZ: size *
15384
+ morphologyProfile.body.bodyRadiusRatio *
15385
+ (0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
15386
+ morphologyProfile,
15387
+ timeMs,
15388
+ animationPhase,
15389
+ };
15390
+ const surfacePatches = resolveVisibleBlobbyOctopusPatches({
15391
+ ...surfaceOptions,
15392
+ center: meshCenter,
15393
+ rotationX,
15394
+ rotationY,
15395
+ sceneCenterX,
15396
+ sceneCenterY,
15397
+ size,
15398
+ palette,
15399
+ });
15400
+ const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
15401
+ const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
15402
+ const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
15403
+ const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
15404
+ const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
15405
+ const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
15406
+ drawAvatarFrame(context, size, palette);
15407
+ drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
15408
+ drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
15409
+ for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
15410
+ drawBlobbySurfacePatch(context, surfacePatch);
15411
+ }
15412
+ const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
15413
+ const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
15414
+ const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
15415
+ const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
15416
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15417
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15418
+ drawProjectedOrganicMouth(context, [
15419
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
15420
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
15421
+ sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
15422
+ ], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
15423
+ },
15424
+ };
15425
+ /**
15426
+ * Draws the deep-water glow behind the continuous octopus mesh.
15427
+ *
15428
+ * @private helper of `octopus3d2AvatarVisual`
15429
+ */
15430
+ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
15431
+ 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);
15432
+ glowGradient.addColorStop(0, `${palette.highlight}5e`);
15433
+ glowGradient.addColorStop(0.38, `${palette.accent}26`);
15434
+ glowGradient.addColorStop(1, `${palette.highlight}00`);
15435
+ context.fillStyle = glowGradient;
15436
+ context.fillRect(0, 0, size, size);
15437
+ 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);
15438
+ lowerGradient.addColorStop(0, `${palette.secondary}22`);
15439
+ lowerGradient.addColorStop(1, `${palette.secondary}00`);
15440
+ context.fillStyle = lowerGradient;
15441
+ context.fillRect(0, 0, size, size);
15442
+ }
15443
+ /**
15444
+ * Draws the soft floor shadow that anchors the single mesh in the frame.
15445
+ *
15446
+ * @private helper of `octopus3d2AvatarVisual`
15447
+ */
15448
+ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
15136
15449
  context.save();
15137
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
15138
- context.rotate(rotation);
15139
- context.beginPath();
15140
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
15141
- context.fillStyle = '#f8fbff';
15142
- context.fill();
15143
- context.clip();
15144
- const irisGradient = context.createRadialGradient(-projectedRadiusX * 0.2, -projectedRadiusY * 0.26, projectedRadiusX * 0.05, 0, 0, projectedRadiusX * 0.92);
15145
- irisGradient.addColorStop(0, palette.highlight);
15146
- irisGradient.addColorStop(0.56, palette.secondary);
15147
- irisGradient.addColorStop(1, palette.shadow);
15148
- context.beginPath();
15149
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.62 * eyeStyle.irisScale, projectedRadiusY * 0.72 * eyeStyle.irisScale, 0, 0, Math.PI * 2);
15150
- context.fillStyle = irisGradient;
15151
- context.fill();
15152
- context.beginPath();
15153
- context.ellipse(pupilOffsetX, pupilOffsetY, projectedRadiusX * 0.15 * eyeStyle.pupilWidthScale, projectedRadiusY * 0.48 * eyeStyle.pupilHeightScale, 0, 0, Math.PI * 2);
15154
- context.fillStyle = palette.ink;
15155
- context.fill();
15450
+ context.fillStyle = `${palette.shadow}66`;
15451
+ context.filter = `blur(${size * 0.024}px)`;
15156
15452
  context.beginPath();
15157
- context.ellipse(pupilOffsetX - projectedRadiusX * 0.22, pupilOffsetY - projectedRadiusY * 0.24, projectedRadiusX * 0.12, projectedRadiusY * 0.14, 0, 0, Math.PI * 2);
15158
- context.fillStyle = '#ffffff';
15453
+ 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);
15159
15454
  context.fill();
15160
15455
  context.restore();
15161
- context.save();
15162
- context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
15163
- context.rotate(rotation);
15164
- context.beginPath();
15165
- context.ellipse(0, 0, projectedRadiusX, projectedRadiusY, 0, 0, Math.PI * 2);
15166
- context.strokeStyle = `${palette.shadow}cc`;
15167
- context.lineWidth = projectedRadiusX * 0.16;
15168
- context.stroke();
15169
- context.beginPath();
15170
- context.moveTo(-projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
15171
- context.quadraticCurveTo(0, -projectedRadiusY * (eyeStyle.upperLidArchRatio - interaction.gazeY * 0.16 + interaction.intensity * 0.08), projectedRadiusX * 0.88, -projectedRadiusY * eyeStyle.upperLidInsetRatio);
15172
- context.strokeStyle = `${palette.shadow}73`;
15173
- context.lineWidth = projectedRadiusX * 0.14;
15174
- context.lineCap = 'round';
15175
- context.stroke();
15176
- if (eyeStyle.lowerLidOpacity > 0) {
15177
- context.beginPath();
15178
- context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
15179
- context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
15180
- context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
15181
- context.lineWidth = projectedRadiusX * 0.08;
15182
- context.lineCap = 'round';
15183
- context.stroke();
15184
- }
15185
- context.restore();
15186
15456
  }
15187
15457
  /**
15188
- * Draws a subtle projected mouth arc across the front of the mantle.
15458
+ * Resolves all visible projected patches for the single blobby octopus mesh.
15189
15459
  *
15190
- * @private helper of `octopus3dAvatarVisual`
15460
+ * @private helper of `octopus3d2AvatarVisual`
15191
15461
  */
15192
- function drawProjectedMouth(context, localPoints, center, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size) {
15193
- const scenePoints = localPoints.map((localPoint) => transformScenePoint(localPoint, center, rotationX, rotationY));
15194
- if (scenePoints.some((scenePoint) => scenePoint.z <= center.z)) {
15195
- return;
15462
+ function resolveVisibleBlobbyOctopusPatches(options) {
15463
+ const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
15464
+ const latitudePatchCount = 12;
15465
+ const longitudePatchCount = 24;
15466
+ const surfacePatches = [];
15467
+ for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
15468
+ const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
15469
+ const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
15470
+ const centerLatitude = (startLatitude + endLatitude) / 2;
15471
+ const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
15472
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
15473
+ const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
15474
+ const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
15475
+ const centerLongitude = (startLongitude + endLongitude) / 2;
15476
+ const localCorners = [
15477
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
15478
+ sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
15479
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
15480
+ sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
15481
+ ];
15482
+ const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
15483
+ const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
15484
+ if (surfaceNormal.z <= 0.01) {
15485
+ continue;
15486
+ }
15487
+ const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
15488
+ surfacePatches.push({
15489
+ corners: projectedCorners,
15490
+ averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
15491
+ transformedCorners.length,
15492
+ lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
15493
+ fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
15494
+ outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
15495
+ });
15496
+ }
15196
15497
  }
15197
- const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
15198
- context.beginPath();
15199
- context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
15200
- context.quadraticCurveTo(projectedPoints[1].x, projectedPoints[1].y, projectedPoints[2].x, projectedPoints[2].y);
15201
- context.strokeStyle = `${palette.ink}b8`;
15202
- context.lineWidth = Math.max(1.1, size * 0.009);
15203
- context.lineCap = 'round';
15204
- context.stroke();
15498
+ return surfacePatches;
15205
15499
  }
15206
15500
  /**
15207
- * Draws one filled projected quad.
15501
+ * Samples one point on the continuous Octopus 3D 2 surface.
15502
+ *
15503
+ * The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
15504
+ * instead of switching to separately rendered tentacles.
15505
+ *
15506
+ * @private helper of `octopus3d2AvatarVisual`
15507
+ */
15508
+ function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
15509
+ const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
15510
+ const cosineLatitude = Math.max(0, Math.cos(latitude));
15511
+ const verticalProgress = (Math.sin(latitude) + 1) / 2;
15512
+ const upperBlend = Math.pow(1 - verticalProgress, 1.2);
15513
+ const lowerBlend = Math.pow(verticalProgress, 1.42);
15514
+ const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
15515
+ const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
15516
+ const horizontalScale = 1.02 +
15517
+ skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
15518
+ upperBlend * 0.08;
15519
+ const depthScale = 1.04 +
15520
+ upperBlend * 0.16 +
15521
+ Math.max(0, Math.cos(longitude)) * 0.1 +
15522
+ skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
15523
+ Math.max(0, -Math.cos(longitude)) * 0.04;
15524
+ const lowerDrop = skirtEnvelope *
15525
+ radiusY *
15526
+ (0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
15527
+ const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
15528
+ const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
15529
+ return {
15530
+ x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
15531
+ y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
15532
+ upperBlend * radiusY * 0.1 +
15533
+ lowerDrop +
15534
+ Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
15535
+ z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
15536
+ };
15537
+ }
15538
+ /**
15539
+ * Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
15208
15540
  *
15209
- * @private helper of `octopus3dAvatarVisual`
15541
+ * @private helper of `octopus3d2AvatarVisual`
15210
15542
  */
15211
- function drawProjectedQuad(context, corners, fillStyle) {
15212
- context.beginPath();
15213
- context.moveTo(corners[0].x, corners[0].y);
15214
- context.lineTo(corners[1].x, corners[1].y);
15215
- context.lineTo(corners[2].x, corners[2].y);
15216
- context.lineTo(corners[3].x, corners[3].y);
15217
- context.closePath();
15218
- context.fillStyle = fillStyle;
15219
- context.fill();
15543
+ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
15544
+ const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
15545
+ return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
15220
15546
  }
15221
15547
  /**
15222
- * Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
15548
+ * Resolves one base fill tone for a surface patch on the single octopus mesh.
15223
15549
  *
15224
- * @private helper of `octopus3dAvatarVisual`
15550
+ * @private helper of `octopus3d2AvatarVisual`
15225
15551
  */
15226
- function formatAlphaHex(opacity) {
15227
- return Math.round(clampNumber$1(opacity, 0, 1) * 255)
15228
- .toString(16)
15229
- .padStart(2, '0');
15552
+ function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
15553
+ const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
15554
+ if (tonalProgress < 0.16) {
15555
+ return palette.highlight;
15556
+ }
15557
+ if (tonalProgress < 0.34) {
15558
+ return palette.secondary;
15559
+ }
15560
+ if (tonalProgress < 0.72) {
15561
+ return forwardness > 0.58 ? palette.secondary : palette.primary;
15562
+ }
15563
+ return `${palette.shadow}f2`;
15564
+ }
15565
+ /**
15566
+ * Draws one projected patch with soft octopus shading.
15567
+ *
15568
+ * @private helper of `octopus3d2AvatarVisual`
15569
+ */
15570
+ function drawBlobbySurfacePatch(context, surfacePatch) {
15571
+ drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
15572
+ if (surfacePatch.lightIntensity > 0) {
15573
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
15574
+ }
15575
+ else if (surfacePatch.lightIntensity < 0) {
15576
+ drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
15577
+ }
15578
+ context.save();
15579
+ context.beginPath();
15580
+ context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
15581
+ for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
15582
+ context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
15583
+ }
15584
+ context.closePath();
15585
+ context.strokeStyle = surfacePatch.outlineColor;
15586
+ context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
15587
+ context.lineJoin = 'round';
15588
+ context.stroke();
15589
+ context.restore();
15230
15590
  }
15231
15591
 
15232
15592
  /* eslint-disable no-magic-numbers */
@@ -15998,6 +16358,7 @@
15998
16358
  octopus2AvatarVisual,
15999
16359
  octopus3AvatarVisual,
16000
16360
  octopus3dAvatarVisual,
16361
+ octopus3d2AvatarVisual,
16001
16362
  asciiOctopusAvatarVisual,
16002
16363
  minecraftAvatarVisual,
16003
16364
  minecraft2AvatarVisual,
@@ -21410,11 +21771,11 @@
21410
21771
  });
21411
21772
  }
21412
21773
  /**
21413
- * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
21774
+ * Prepares one attachment for best-effort text decoding.
21414
21775
  *
21415
- * @private internal utility for shared text decoding
21776
+ * @private function of decodeAttachmentAsText
21416
21777
  */
21417
- function decodeAttachmentAsText(input, options = {}) {
21778
+ function createDecodeAttachmentPreparation(input, options) {
21418
21779
  var _a;
21419
21780
  const maxBytes = Math.max(1, Math.floor((_a = options.maxBytes) !== null && _a !== void 0 ? _a : DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES));
21420
21781
  const forceText = options.forceText === true;
@@ -21428,54 +21789,102 @@
21428
21789
  const inspection = inspectBytes(truncatedBytes);
21429
21790
  const trustedTextMime = isTrustedTextMimeType(mimeType);
21430
21791
  const trustedBinaryMime = isTrustedBinaryMimeType(mimeType);
21792
+ const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
21431
21793
  if (isTruncated) {
21432
21794
  warnings.push(`Decoded only the first ${maxBytes} bytes of \`${input.filename}\` because the attachment exceeded the text preview limit.`);
21433
21795
  }
21434
- const shouldTreatAsBinary = (trustedBinaryMime || inspection.looksBinary) && !trustedTextMime;
21435
- if (shouldTreatAsBinary && !forceText) {
21436
- warnings.push('File content looks binary, so text decoding was skipped.');
21437
- return {
21438
- text: '',
21439
- encodingUsed: 'binary',
21440
- confidence: 1,
21441
- warnings,
21442
- wasBinary: true,
21443
- isTruncated,
21444
- };
21796
+ return {
21797
+ warnings,
21798
+ charset,
21799
+ bom,
21800
+ inspection,
21801
+ truncatedBytes,
21802
+ isTruncated,
21803
+ forceText,
21804
+ shouldTreatAsBinary,
21805
+ };
21806
+ }
21807
+ /**
21808
+ * Returns an early result when the attachment should stay classified as binary.
21809
+ *
21810
+ * @private function of decodeAttachmentAsText
21811
+ */
21812
+ function createBinaryDecodeResult(preparation) {
21813
+ if (!preparation.shouldTreatAsBinary) {
21814
+ return null;
21445
21815
  }
21446
- if (shouldTreatAsBinary && forceText) {
21447
- warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
21816
+ if (preparation.forceText) {
21817
+ preparation.warnings.push('File content looks binary, but text decoding was forced with `forceText`.');
21818
+ return null;
21448
21819
  }
21449
- if (charset && !isSupportedEncoding(charset)) {
21450
- warnings.push(`Ignored unsupported declared charset \`${charset}\` and used best-effort detection instead.`);
21820
+ preparation.warnings.push('File content looks binary, so text decoding was skipped.');
21821
+ return {
21822
+ text: '',
21823
+ encodingUsed: 'binary',
21824
+ confidence: 1,
21825
+ warnings: preparation.warnings,
21826
+ wasBinary: true,
21827
+ isTruncated: preparation.isTruncated,
21828
+ };
21829
+ }
21830
+ /**
21831
+ * Warns when the declared charset cannot be used by the runtime decoder.
21832
+ *
21833
+ * @private function of decodeAttachmentAsText
21834
+ */
21835
+ function addUnsupportedCharsetWarning(preparation) {
21836
+ if (preparation.charset && !isSupportedEncoding(preparation.charset)) {
21837
+ preparation.warnings.push(`Ignored unsupported declared charset \`${preparation.charset}\` and used best-effort detection instead.`);
21451
21838
  }
21452
- const bytesToDecode = bom ? truncatedBytes.subarray(bom.offset) : truncatedBytes;
21453
- const candidates = buildCandidateEncodings({
21454
- mimeType,
21455
- charset: charset && isSupportedEncoding(charset) ? charset : null,
21456
- bom,
21457
- inspection,
21458
- });
21459
- const decodedCandidates = candidates
21839
+ }
21840
+ /**
21841
+ * Returns the byte slice that should actually be decoded as text.
21842
+ *
21843
+ * @private function of decodeAttachmentAsText
21844
+ */
21845
+ function getBytesToDecode(preparation) {
21846
+ return preparation.bom ? preparation.truncatedBytes.subarray(preparation.bom.offset) : preparation.truncatedBytes;
21847
+ }
21848
+ /**
21849
+ * Decodes all candidate encodings and sorts the successful results by score.
21850
+ *
21851
+ * @private function of decodeAttachmentAsText
21852
+ */
21853
+ function decodeAttachmentCandidates(preparation) {
21854
+ return buildCandidateEncodings({
21855
+ charset: preparation.charset && isSupportedEncoding(preparation.charset) ? preparation.charset : null,
21856
+ bom: preparation.bom,
21857
+ inspection: preparation.inspection,
21858
+ })
21460
21859
  .map(({ encoding, source }) => {
21461
- const decoded = decodeWithEncoding(bytesToDecode, encoding);
21860
+ const decoded = decodeWithEncoding(getBytesToDecode(preparation), encoding);
21462
21861
  return decoded ? { ...decoded, source } : null;
21463
21862
  })
21464
21863
  .filter((candidate) => candidate !== null)
21465
21864
  .sort((left, right) => left.score - right.score);
21466
- const bestCandidate = decodedCandidates[0];
21467
- if (!bestCandidate) {
21468
- warnings.push('No supported text decoder was available.');
21469
- return {
21470
- text: '',
21471
- encodingUsed: 'binary',
21472
- confidence: 0,
21473
- warnings,
21474
- wasBinary: true,
21475
- isTruncated,
21476
- };
21477
- }
21478
- const secondBestCandidate = decodedCandidates[1];
21865
+ }
21866
+ /**
21867
+ * Returns the fallback result used when no text decoder could be applied.
21868
+ *
21869
+ * @private function of decodeAttachmentAsText
21870
+ */
21871
+ function createNoDecoderAvailableResult(preparation) {
21872
+ preparation.warnings.push('No supported text decoder was available.');
21873
+ return {
21874
+ text: '',
21875
+ encodingUsed: 'binary',
21876
+ confidence: 0,
21877
+ warnings: preparation.warnings,
21878
+ wasBinary: true,
21879
+ isTruncated: preparation.isTruncated,
21880
+ };
21881
+ }
21882
+ /**
21883
+ * Estimates confidence for the winning decoded text candidate.
21884
+ *
21885
+ * @private function of decodeAttachmentAsText
21886
+ */
21887
+ function computeDecodeConfidence(bestCandidate, secondBestCandidate, preparation) {
21479
21888
  const baseConfidence = bestCandidate.source === 'bom'
21480
21889
  ? 1
21481
21890
  : bestCandidate.source === 'charset'
@@ -21488,27 +21897,62 @@
21488
21897
  ? 0.82
21489
21898
  : 0.62;
21490
21899
  const scoreMargin = secondBestCandidate ? Math.max(0, secondBestCandidate.score - bestCandidate.score) : 0.2;
21491
- const confidence = Math.max(0.2, Math.min(shouldTreatAsBinary && forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
21900
+ return Math.max(0.2, Math.min(preparation.shouldTreatAsBinary && preparation.forceText ? 0.45 : 1, baseConfidence + Math.min(0.18, scoreMargin / 2)));
21901
+ }
21902
+ /**
21903
+ * Appends user-facing warnings derived from the chosen decoded text candidate.
21904
+ *
21905
+ * @private function of decodeAttachmentAsText
21906
+ */
21907
+ function addDecodeWarnings(bestCandidate, confidence, preparation) {
21492
21908
  if (bestCandidate.source === 'heuristic' && bestCandidate.encoding !== 'utf-8') {
21493
- warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
21909
+ preparation.warnings.push(`Encoding was guessed as \`${bestCandidate.encoding}\`.`);
21494
21910
  }
21495
21911
  if (bestCandidate.source === 'heuristic' &&
21496
21912
  bestCandidate.encoding === 'utf-8' &&
21497
21913
  bestCandidate.replacementCount > 0) {
21498
- warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
21914
+ preparation.warnings.push('UTF-8 decoding produced replacement characters, so the extracted text may contain errors.');
21499
21915
  }
21500
21916
  if (confidence < 0.6) {
21501
- warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
21917
+ preparation.warnings.push('Decoding confidence is low, so the extracted text may contain errors.');
21502
21918
  }
21919
+ }
21920
+ /**
21921
+ * Creates the final decoded-text result from the chosen candidate and accumulated metadata.
21922
+ *
21923
+ * @private function of decodeAttachmentAsText
21924
+ */
21925
+ function createDecodedTextResult(bestCandidate, confidence, preparation) {
21503
21926
  return {
21504
- text: isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
21927
+ text: preparation.isTruncated ? appendTruncatedMarker(bestCandidate.text) : bestCandidate.text,
21505
21928
  encodingUsed: bestCandidate.encoding,
21506
21929
  confidence,
21507
- warnings,
21930
+ warnings: preparation.warnings,
21508
21931
  wasBinary: false,
21509
- isTruncated,
21932
+ isTruncated: preparation.isTruncated,
21510
21933
  };
21511
21934
  }
21935
+ /**
21936
+ * Best-effort decoder for uploaded or remote file bytes whose extension or encoding may be unknown.
21937
+ *
21938
+ * @private internal utility for shared text decoding
21939
+ */
21940
+ function decodeAttachmentAsText(input, options = {}) {
21941
+ const preparation = createDecodeAttachmentPreparation(input, options);
21942
+ const binaryResult = createBinaryDecodeResult(preparation);
21943
+ if (binaryResult) {
21944
+ return binaryResult;
21945
+ }
21946
+ addUnsupportedCharsetWarning(preparation);
21947
+ const decodedCandidates = decodeAttachmentCandidates(preparation);
21948
+ const bestCandidate = decodedCandidates[0];
21949
+ if (!bestCandidate) {
21950
+ return createNoDecoderAvailableResult(preparation);
21951
+ }
21952
+ const confidence = computeDecodeConfidence(bestCandidate, decodedCandidates[1], preparation);
21953
+ addDecodeWarnings(bestCandidate, confidence, preparation);
21954
+ return createDecodedTextResult(bestCandidate, confidence, preparation);
21955
+ }
21512
21956
 
21513
21957
  /**
21514
21958
  * Base GitHub API URL.
@@ -33866,10 +34310,7 @@
33866
34310
  * @private internal function of `$registeredLlmToolsMessage`
33867
34311
  */
33868
34312
  function createProviderStatusMessages(llmToolStatus, env) {
33869
- return [
33870
- createInstallationStatusMessage(llmToolStatus),
33871
- createConfigurationStatusMessage(llmToolStatus, env),
33872
- ];
34313
+ return [createInstallationStatusMessage(llmToolStatus), createConfigurationStatusMessage(llmToolStatus, env)];
33873
34314
  }
33874
34315
  /**
33875
34316
  * Creates the installation-status sentence for one provider.
@@ -36130,7 +36571,10 @@
36130
36571
  Cannot find model in ${this.options.getTitle()} models with name "${defaultModelName}" which should be used as default.
36131
36572
 
36132
36573
  Available models:
36133
- ${block(this.options.getHardcodedModels().map(({ modelName }) => `- "${modelName}"`).join('\n'))}
36574
+ ${block(this.options
36575
+ .getHardcodedModels()
36576
+ .map(({ modelName }) => `- "${modelName}"`)
36577
+ .join('\n'))}
36134
36578
 
36135
36579
  Model "${defaultModelName}" is probably not available anymore, not installed, inaccessible or misconfigured.
36136
36580
 
@@ -36484,7 +36928,8 @@
36484
36928
  };
36485
36929
  let rawPromptContent = templateParameters(content, { ...parameters, modelName });
36486
36930
  if ('attachments' in prompt && Array.isArray(prompt.attachments) && prompt.attachments.length > 0) {
36487
- rawPromptContent += '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
36931
+ rawPromptContent +=
36932
+ '\n\n' + prompt.attachments.map((attachment) => `Image attachment: ${attachment.url}`).join('\n');
36488
36933
  }
36489
36934
  const rawRequest = {
36490
36935
  ...modelSettings,
@@ -36589,7 +37034,9 @@
36589
37034
  * Schedules one request through the shared limiter and retry policy.
36590
37035
  */
36591
37036
  async executeRateLimitedRequest(requestFn) {
36592
- return this.limiter.schedule(() => this.makeRequestWithNetworkRetry(requestFn)).catch((error) => {
37037
+ return this.limiter
37038
+ .schedule(() => this.makeRequestWithNetworkRetry(requestFn))
37039
+ .catch((error) => {
36593
37040
  assertsError(error);
36594
37041
  if (this.options.isVerbose) {
36595
37042
  console.info(colors__default["default"].bgRed('error'), error);
@@ -37796,7 +38243,9 @@
37796
38243
  pollingState.lastProgressAtMs = nowMs;
37797
38244
  pollingState.lastProgressKey = progressKey;
37798
38245
  }
37799
- if (this.options.isVerbose && (statusCountsKey !== pollingState.lastCountsKey || nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
38246
+ if (this.options.isVerbose &&
38247
+ (statusCountsKey !== pollingState.lastCountsKey ||
38248
+ nowMs - pollingState.lastLogAtMs >= progressLogIntervalMs)) {
37800
38249
  console.info('[🤰]', 'Vector store file batch status', {
37801
38250
  vectorStoreId,
37802
38251
  batchId,