promptopskit 0.8.1 → 0.9.0

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 (70) hide show
  1. package/README.md +2 -1
  2. package/SKILL.md +10 -4
  3. package/dist/{chunk-ZVCKP4EM.js → chunk-AAQNCAVA.js} +3 -3
  4. package/dist/{chunk-5XH2GROP.js → chunk-E2AIIPQC.js} +2 -2
  5. package/dist/{chunk-UQLAWU67.js → chunk-FJ3D76IV.js} +2 -2
  6. package/dist/{chunk-RVAFVWR2.js → chunk-NS6OTKY2.js} +115 -34
  7. package/dist/chunk-NS6OTKY2.js.map +1 -0
  8. package/dist/{chunk-Z3DPPJNU.js → chunk-Q67E2GGD.js} +2 -2
  9. package/dist/{chunk-PDVUVZAU.js → chunk-SVNQNMMV.js} +2 -2
  10. package/dist/{chunk-E4GEKWVP.js → chunk-VOXMOGU5.js} +5 -2
  11. package/dist/chunk-VOXMOGU5.js.map +1 -0
  12. package/dist/{chunk-DI7Q4T7A.js → chunk-W6XHGSPJ.js} +3 -3
  13. package/dist/cli/index.js +49 -2
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/index.cjs +158 -33
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +4 -4
  18. package/dist/index.d.ts +4 -4
  19. package/dist/index.js +49 -8
  20. package/dist/index.js.map +1 -1
  21. package/dist/providers/anthropic.cjs +117 -33
  22. package/dist/providers/anthropic.cjs.map +1 -1
  23. package/dist/providers/anthropic.d.cts +2 -2
  24. package/dist/providers/anthropic.d.ts +2 -2
  25. package/dist/providers/anthropic.js +3 -3
  26. package/dist/providers/gemini.cjs +117 -33
  27. package/dist/providers/gemini.cjs.map +1 -1
  28. package/dist/providers/gemini.d.cts +2 -2
  29. package/dist/providers/gemini.d.ts +2 -2
  30. package/dist/providers/gemini.js +3 -3
  31. package/dist/providers/llmasaservice.cjs +117 -33
  32. package/dist/providers/llmasaservice.cjs.map +1 -1
  33. package/dist/providers/llmasaservice.d.cts +2 -2
  34. package/dist/providers/llmasaservice.d.ts +2 -2
  35. package/dist/providers/llmasaservice.js +4 -4
  36. package/dist/providers/openai-responses.cjs +117 -33
  37. package/dist/providers/openai-responses.cjs.map +1 -1
  38. package/dist/providers/openai-responses.d.cts +2 -2
  39. package/dist/providers/openai-responses.d.ts +2 -2
  40. package/dist/providers/openai-responses.js +3 -3
  41. package/dist/providers/openai.cjs +117 -33
  42. package/dist/providers/openai.cjs.map +1 -1
  43. package/dist/providers/openai.d.cts +2 -2
  44. package/dist/providers/openai.d.ts +2 -2
  45. package/dist/providers/openai.js +3 -3
  46. package/dist/providers/openrouter.cjs +117 -33
  47. package/dist/providers/openrouter.cjs.map +1 -1
  48. package/dist/providers/openrouter.d.cts +2 -2
  49. package/dist/providers/openrouter.d.ts +2 -2
  50. package/dist/providers/openrouter.js +4 -4
  51. package/dist/{schema-BgVLZ2u5.d.cts → schema-BkOMklgI.d.cts} +129 -0
  52. package/dist/{schema-BgVLZ2u5.d.ts → schema-BkOMklgI.d.ts} +129 -0
  53. package/dist/testing.cjs +4 -1
  54. package/dist/testing.cjs.map +1 -1
  55. package/dist/testing.d.cts +1 -1
  56. package/dist/testing.d.ts +1 -1
  57. package/dist/testing.js +1 -1
  58. package/dist/{types-2hnZmCkH.d.cts → types-B4TWRzUY.d.cts} +1 -1
  59. package/dist/{types-DgAT4jSR.d.ts → types-D6VJJo1x.d.ts} +1 -1
  60. package/dist/usagetap/index.d.cts +2 -2
  61. package/dist/usagetap/index.d.ts +2 -2
  62. package/package.json +1 -1
  63. package/dist/chunk-E4GEKWVP.js.map +0 -1
  64. package/dist/chunk-RVAFVWR2.js.map +0 -1
  65. /package/dist/{chunk-ZVCKP4EM.js.map → chunk-AAQNCAVA.js.map} +0 -0
  66. /package/dist/{chunk-5XH2GROP.js.map → chunk-E2AIIPQC.js.map} +0 -0
  67. /package/dist/{chunk-UQLAWU67.js.map → chunk-FJ3D76IV.js.map} +0 -0
  68. /package/dist/{chunk-Z3DPPJNU.js.map → chunk-Q67E2GGD.js.map} +0 -0
  69. /package/dist/{chunk-PDVUVZAU.js.map → chunk-SVNQNMMV.js.map} +0 -0
  70. /package/dist/{chunk-DI7Q4T7A.js.map → chunk-W6XHGSPJ.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -153,7 +153,10 @@ function normalizeContextInputCompression(value) {
153
153
  target_reduction: value.heuristic.target_reduction,
154
154
  query: value.heuristic.query,
155
155
  query_variable: value.heuristic.query_variable,
156
- json_to_toon: value.heuristic.json_to_toon
156
+ json_to_toon: value.heuristic.json_to_toon,
157
+ mode: value.heuristic.mode,
158
+ preserve_neighbors: value.heuristic.preserve_neighbors,
159
+ fail_on_low_confidence: value.heuristic.fail_on_low_confidence
157
160
  }
158
161
  };
159
162
  const code = normalizeCodeCompression(value.code);
@@ -658,7 +661,10 @@ function line(depth, content, indent) {
658
661
  var TOKEN_REGEX = /[\p{L}\p{N}]+|[^\s]/gu;
659
662
  var TOKEN_NORMALIZE_REGEX = /[^\p{L}\p{N}]/gu;
660
663
  var DEDUPE_NORMALIZE_REGEX = /[^\p{L}\p{N}\s]/gu;
661
- var BOILERPLATE_REGEX = /copyright|all rights reserved|disclaimer|confidential/i;
664
+ var BOILERPLATE_REGEX = /copyright|all rights reserved|disclaimer/i;
665
+ var STRUCTURED_BLOCK_REGEX = /(^|\n)\s*```|(^|\n)\s*\|.+\|\s*(\n|$)/;
666
+ var PROTECTED_SIGNAL_REGEX = /\b(?:must|shall|should|required|requires|requirement|never|do not|don't|only|except|unless|however|but|constraint|constraints|safety|security|deadline|sla)\b|output\s+format/i;
667
+ var EVIDENCE_SIGNAL_REGEX = /https?:\/\/|\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b|[$€£]\s?\d|\b\d+(?:[.,:/-]\d+)*%?\b|\b[A-Z]{2,}[-_A-Z0-9]*\b|\b[A-Z]\d\b/i;
662
668
  var MAX_SEGMENT_TOKENS = 120;
663
669
  var STOP_WORDS = /* @__PURE__ */ new Set([
664
670
  "a",
@@ -692,7 +698,8 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
692
698
  var DEFAULT_OPTIONS = {
693
699
  min_tokens: 80,
694
700
  max_sentences: 10,
695
- target_reduction: 0.45
701
+ target_reduction: 0.45,
702
+ mode: "conservative"
696
703
  };
697
704
  function tokenizeForHeuristicCompression(text) {
698
705
  if (!text) {
@@ -726,25 +733,46 @@ function compressHeuristicText(input, options = {}) {
726
733
  warnings: ["JSON-to-TOON skipped because the input is not a complete valid JSON object or array."]
727
734
  };
728
735
  }
729
- const analysis = preprocessContext(inputText);
730
736
  if (inputTokens === 0) {
731
737
  return toCompressionOutput(inputText, inputTokens);
732
738
  }
739
+ const mode = options.mode ?? DEFAULT_OPTIONS.mode;
740
+ const isConservative = mode === "conservative";
741
+ const failOnLowConfidence = options.fail_on_low_confidence ?? isConservative;
742
+ const preserveNeighbors = options.preserve_neighbors ?? isConservative;
733
743
  const minTokens = options.min_tokens ?? DEFAULT_OPTIONS.min_tokens;
734
744
  if (inputTokens <= minTokens) {
735
745
  return toCompressionOutput(inputText, inputTokens);
736
746
  }
747
+ if (isConservative && looksStructured(inputText)) {
748
+ return toCompressionOutput(inputText, inputTokens, [
749
+ "Heuristic compression skipped because the input appears to contain structured blocks; use TOON or code compaction for structured content."
750
+ ]);
751
+ }
752
+ const analysis = preprocessContext(inputText);
737
753
  const targetReduction = options.target_reduction ?? DEFAULT_OPTIONS.target_reduction;
738
754
  const targetTokens = Math.max(1, Math.max(minTokens, Math.floor(inputTokens * (1 - targetReduction))));
739
755
  const maxSentences = options.max_sentences ?? DEFAULT_OPTIONS.max_sentences;
740
756
  const query = options.query ?? "";
741
757
  const terms = queryTerms(query);
758
+ if (failOnLowConfidence && terms.size === 0) {
759
+ return toCompressionOutput(inputText, inputTokens, [
760
+ "Heuristic compression skipped because no usable relevance query terms were available."
761
+ ]);
762
+ }
742
763
  const scoredSentences = analysis.candidates.map((candidate) => ({
743
764
  sentence: candidate.sentence,
744
765
  index: candidate.index,
745
766
  ...scoreSentenceCandidate(candidate, terms),
746
- tokens: candidate.tokenCount
767
+ tokens: candidate.tokenCount,
768
+ isProtected: candidate.isProtected,
769
+ hasEvidence: candidate.hasEvidence
747
770
  }));
771
+ if (failOnLowConfidence && terms.size > 0 && !scoredSentences.some((candidate) => candidate.overlapCount > 0)) {
772
+ return toCompressionOutput(inputText, inputTokens, [
773
+ "Heuristic compression skipped because no sentence matched the relevance query."
774
+ ]);
775
+ }
748
776
  const selected = [];
749
777
  const selectedIndices = /* @__PURE__ */ new Set();
750
778
  const coveredTerms = /* @__PURE__ */ new Set();
@@ -784,19 +812,15 @@ function compressHeuristicText(input, options = {}) {
784
812
  coveredTerms.add(term);
785
813
  }
786
814
  }
787
- selected.sort((left, right) => left.index - right.index);
788
- let output = selected.map((item) => item.sentence).join(" ");
815
+ const finalSelection = preserveNeighbors ? expandSelectionWithNeighbors(selected, scoredSentences, maxSentences) : selected;
816
+ finalSelection.sort((left, right) => left.index - right.index);
817
+ let output = finalSelection.map((item) => item.sentence).join(" ");
789
818
  if (!output) {
790
- output = tokenizeForHeuristicCompression(inputText).slice(0, targetTokens).join(" ");
791
- } else {
792
- const outputTokens = tokenizeForHeuristicCompression(output);
793
- if (outputTokens.length > targetTokens) {
794
- output = outputTokens.slice(0, targetTokens).join(" ");
795
- }
819
+ output = takeWholeCandidatesWithinBudget(analysis.candidates, targetTokens) || inputText;
796
820
  }
797
821
  return toCompressionOutput(output, inputTokens);
798
822
  }
799
- function toCompressionOutput(output, inputTokens) {
823
+ function toCompressionOutput(output, inputTokens, warnings = []) {
800
824
  const outputTokens = estimateHeuristicTokens(output);
801
825
  const tokensSaved = Math.max(0, inputTokens - outputTokens);
802
826
  return {
@@ -804,7 +828,8 @@ function toCompressionOutput(output, inputTokens) {
804
828
  inputTokens,
805
829
  outputTokens,
806
830
  tokensSaved,
807
- compressionRatio: outputTokens === 0 ? 0 : inputTokens / outputTokens
831
+ compressionRatio: outputTokens === 0 ? 0 : inputTokens / outputTokens,
832
+ ...warnings.length > 0 ? { warnings } : {}
808
833
  };
809
834
  }
810
835
  function isBetterTieBreak(candidate, bestCandidate) {
@@ -822,13 +847,6 @@ function normalizeForDeduping(sentence) {
822
847
  function splitIntoSentences(text) {
823
848
  return text.split(/(?<=[.!?])\s+|\n+/g).map((sentence) => sentence.trim()).filter(Boolean);
824
849
  }
825
- function chunkTokens(tokens, chunkSize) {
826
- const chunks = [];
827
- for (let index = 0; index < tokens.length; index += chunkSize) {
828
- chunks.push(tokens.slice(index, index + chunkSize));
829
- }
830
- return chunks;
831
- }
832
850
  function splitOversizedSentence(sentence, maxTokens = MAX_SEGMENT_TOKENS) {
833
851
  const rawTokens = tokenizeForHeuristicCompression(sentence);
834
852
  if (rawTokens.length <= maxTokens) {
@@ -840,10 +858,10 @@ function splitOversizedSentence(sentence, maxTokens = MAX_SEGMENT_TOKENS) {
840
858
  if (estimateHeuristicTokens(segment) <= maxTokens) {
841
859
  return [segment];
842
860
  }
843
- return chunkTokens(tokenizeForHeuristicCompression(segment), maxTokens).map((chunk) => chunk.join(" "));
861
+ return [segment];
844
862
  });
845
863
  }
846
- return chunkTokens(rawTokens, maxTokens).map((chunk) => chunk.join(" "));
864
+ return [sentence];
847
865
  }
848
866
  function normalizeToken(token) {
849
867
  return token.replace(TOKEN_NORMALIZE_REGEX, "").trim();
@@ -852,19 +870,24 @@ function tokenizeNormalized(text) {
852
870
  return tokenizeForHeuristicCompression(text.toLowerCase()).map(normalizeToken).filter(Boolean);
853
871
  }
854
872
  function queryTerms(query) {
855
- return new Set(tokenizeNormalized(query).filter((term) => term.length >= 3 && !STOP_WORDS.has(term)));
873
+ return new Set(tokenizeNormalized(query).filter((term) => {
874
+ if (STOP_WORDS.has(term)) {
875
+ return false;
876
+ }
877
+ return term.length >= 3 || /^(?:[a-z]\d|\d+[a-z]?|[a-z]{2})$/i.test(term);
878
+ }));
856
879
  }
857
880
  function preprocessContext(context) {
858
881
  const originalTokens = estimateHeuristicTokens(context);
859
- const dedupeSet = /* @__PURE__ */ new Set();
860
882
  const candidates = [];
883
+ let previousNormalized = "";
861
884
  for (const sentence of splitIntoSentences(context)) {
862
885
  for (const segment of splitOversizedSentence(sentence)) {
863
886
  const normalized = normalizeForDeduping(segment);
864
- if (!normalized || dedupeSet.has(normalized)) {
887
+ if (!normalized || normalized === previousNormalized) {
865
888
  continue;
866
889
  }
867
- dedupeSet.add(normalized);
890
+ previousNormalized = normalized;
868
891
  const rawTokens = tokenizeForHeuristicCompression(segment);
869
892
  const normalizedTerms = rawTokens.map((token) => normalizeToken(token.toLowerCase())).filter(Boolean);
870
893
  const lowered = segment.toLowerCase();
@@ -873,7 +896,9 @@ function preprocessContext(context) {
873
896
  index: candidates.length,
874
897
  tokenCount: rawTokens.length,
875
898
  uniqueTerms: new Set(normalizedTerms),
876
- isBoilerplate: BOILERPLATE_REGEX.test(lowered)
899
+ isBoilerplate: BOILERPLATE_REGEX.test(lowered),
900
+ isProtected: PROTECTED_SIGNAL_REGEX.test(segment),
901
+ hasEvidence: EVIDENCE_SIGNAL_REGEX.test(segment)
877
902
  });
878
903
  }
879
904
  }
@@ -899,12 +924,62 @@ function scoreSentenceCandidate(candidate, terms) {
899
924
  const densityScore = terms.size > 0 ? overlap / terms.size : 0;
900
925
  const lengthScore = Math.min(candidate.tokenCount, 40) / 40;
901
926
  const boilerplatePenalty = candidate.isBoilerplate ? 0.5 : 0;
927
+ const protectedScore = candidate.isProtected ? 0.9 : 0;
928
+ const evidenceScore = candidate.hasEvidence ? 0.4 : 0;
902
929
  return {
903
- score: overlapScore + densityScore + lengthScore - boilerplatePenalty,
930
+ score: overlapScore + densityScore + lengthScore + protectedScore + evidenceScore - boilerplatePenalty,
904
931
  overlapCount: overlap,
905
932
  overlapTerms
906
933
  };
907
934
  }
935
+ function looksStructured(text) {
936
+ if (STRUCTURED_BLOCK_REGEX.test(text)) {
937
+ return true;
938
+ }
939
+ const lines = text.split(/\r?\n/).map((line2) => line2.trim()).filter(Boolean);
940
+ if (lines.length < 3) {
941
+ return false;
942
+ }
943
+ const listLikeLines = lines.filter((line2) => /^[-*+] |\d+[.)] /.test(line2)).length;
944
+ return listLikeLines >= 3 && listLikeLines / lines.length >= 0.6;
945
+ }
946
+ function expandSelectionWithNeighbors(selected, scoredSentences, maxSentences) {
947
+ if (selected.length === 0 || selected.length >= maxSentences) {
948
+ return selected;
949
+ }
950
+ const byIndex = new Map(scoredSentences.map((candidate) => [candidate.index, candidate]));
951
+ const expanded = new Map(selected.map((candidate) => [candidate.index, candidate]));
952
+ for (const candidate of selected) {
953
+ if (expanded.size >= maxSentences) {
954
+ break;
955
+ }
956
+ if (candidate.overlapCount === 0 && !candidate.isProtected && !candidate.hasEvidence) {
957
+ continue;
958
+ }
959
+ for (const neighborIndex of [candidate.index - 1, candidate.index + 1]) {
960
+ if (expanded.size >= maxSentences) {
961
+ break;
962
+ }
963
+ const neighbor = byIndex.get(neighborIndex);
964
+ if (neighbor && !expanded.has(neighbor.index)) {
965
+ expanded.set(neighbor.index, neighbor);
966
+ }
967
+ }
968
+ }
969
+ return [...expanded.values()];
970
+ }
971
+ function takeWholeCandidatesWithinBudget(candidates, targetTokens) {
972
+ const selected = [];
973
+ let tokens = 0;
974
+ for (const candidate of candidates) {
975
+ if (tokens + candidate.tokenCount > targetTokens) {
976
+ break;
977
+ }
978
+ selected.push(candidate.sentence);
979
+ tokens += candidate.tokenCount;
980
+ }
981
+ return selected.join(" ");
982
+ }
908
983
 
909
984
  // src/code-compaction.ts
910
985
  function compactCode(input, options = {}) {
@@ -1070,7 +1145,10 @@ function renderSections(asset, options = {}) {
1070
1145
  max_sentences: configuredHeuristic?.max_sentences,
1071
1146
  target_reduction: configuredHeuristic?.target_reduction,
1072
1147
  query,
1073
- json_to_toon: configuredHeuristic?.json_to_toon
1148
+ json_to_toon: configuredHeuristic?.json_to_toon,
1149
+ mode: configuredHeuristic?.mode,
1150
+ preserve_neighbors: configuredHeuristic?.preserve_neighbors,
1151
+ fail_on_low_confidence: configuredHeuristic?.fail_on_low_confidence
1074
1152
  };
1075
1153
  const cacheKey = JSON.stringify([name, value, compressionOptions]);
1076
1154
  const cached = compressionCache.get(cacheKey);
@@ -1436,7 +1514,10 @@ var HeuristicCompressionSchema = import_zod.z.object({
1436
1514
  target_reduction: import_zod.z.number().min(0).max(1).optional(),
1437
1515
  query: import_zod.z.string().optional(),
1438
1516
  query_variable: import_zod.z.string().min(1).optional(),
1439
- json_to_toon: import_zod.z.boolean().optional()
1517
+ json_to_toon: import_zod.z.boolean().optional(),
1518
+ mode: import_zod.z.enum(["conservative", "balanced"]).optional(),
1519
+ preserve_neighbors: import_zod.z.boolean().optional(),
1520
+ fail_on_low_confidence: import_zod.z.boolean().optional()
1440
1521
  });
1441
1522
  var CodeCompactionSchema = import_zod.z.object({
1442
1523
  enabled: import_zod.z.boolean().optional(),
@@ -2421,7 +2502,10 @@ async function applyPromptCompressionForRender(asset, runtime) {
2421
2502
  max_sentences: heuristicConfig?.max_sentences,
2422
2503
  target_reduction: heuristicConfig?.target_reduction,
2423
2504
  query: resolveHeuristicPromptQuery(heuristicConfig, runtime.variables, sections.system_instructions),
2424
- json_to_toon: heuristicConfig?.json_to_toon
2505
+ json_to_toon: heuristicConfig?.json_to_toon,
2506
+ mode: heuristicConfig?.mode,
2507
+ preserve_neighbors: heuristicConfig?.preserve_neighbors,
2508
+ fail_on_low_confidence: heuristicConfig?.fail_on_low_confidence
2425
2509
  });
2426
2510
  promptTemplate = result.output;
2427
2511
  reportHeuristicCompressionWarnings2(warnings, result.warnings, "prompt template");
@@ -3327,6 +3411,7 @@ var RISKY_UNBOUNDED_INPUT_NAMES = [
3327
3411
  "body",
3328
3412
  "context"
3329
3413
  ];
3414
+ var COMPRESS_MODIFIER_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\|\s*compress\s*\}\}/g;
3330
3415
  function validateAsset(asset, frontMatterKeys, filePath) {
3331
3416
  const errors = [];
3332
3417
  const warnings = [];
@@ -3408,6 +3493,26 @@ function validateAsset(asset, frontMatterKeys, filePath) {
3408
3493
  suggestion: "Declare context.inputs to enable input policy validation."
3409
3494
  });
3410
3495
  }
3496
+ if (asset.compression?.heuristic?.enabled === true && asset.compression.heuristic.json_to_toon !== true) {
3497
+ warnings.push({
3498
+ code: "POK055",
3499
+ message: "Prompt-level heuristic compression is enabled for the full prompt template.",
3500
+ filePath,
3501
+ suggestion: "Prefer per-placeholder compression for bulky context values so instructions, output constraints, and safety conditions are not sentence-selected away."
3502
+ });
3503
+ }
3504
+ const systemHeuristicCompressionVariables = findSystemHeuristicCompressionVariables(
3505
+ asset.sections?.system_instructions,
3506
+ declaredInputsByName
3507
+ );
3508
+ if (systemHeuristicCompressionVariables.length > 0) {
3509
+ warnings.push({
3510
+ code: "POK056",
3511
+ message: `System instructions use heuristic compression for ${formatQuotedList(systemHeuristicCompressionVariables)}.`,
3512
+ filePath,
3513
+ suggestion: "Avoid compressing values inside system instructions; move lossy compression to prompt-template context placeholders when possible."
3514
+ });
3515
+ }
3411
3516
  for (const input of contextInputs) {
3412
3517
  const lowerName = input.name.toLowerCase();
3413
3518
  const inputWarningsEnabled = areContextInputWarningsEnabled(input);
@@ -3599,6 +3704,26 @@ function findClosestMatch(input, candidates) {
3599
3704
  }
3600
3705
  return best;
3601
3706
  }
3707
+ function findSystemHeuristicCompressionVariables(systemInstructions, declaredInputsByName) {
3708
+ if (!systemInstructions) {
3709
+ return [];
3710
+ }
3711
+ const variables = /* @__PURE__ */ new Set();
3712
+ for (const match of systemInstructions.matchAll(COMPRESS_MODIFIER_RE)) {
3713
+ variables.add(match[1]);
3714
+ }
3715
+ for (const variable of extractVariables(systemInstructions)) {
3716
+ const input = declaredInputsByName.get(variable);
3717
+ const heuristic = input?.compression?.heuristic;
3718
+ if (heuristic?.enabled === true && heuristic.json_to_toon !== true) {
3719
+ variables.add(variable);
3720
+ }
3721
+ }
3722
+ return [...variables].sort();
3723
+ }
3724
+ function formatQuotedList(values) {
3725
+ return values.map((value) => `"${value}"`).join(", ");
3726
+ }
3602
3727
  function isRecord2(value) {
3603
3728
  return typeof value === "object" && value !== null && !Array.isArray(value);
3604
3729
  }