promptopskit 0.8.2 → 0.9.1

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 (69) hide show
  1. package/README.md +20 -6
  2. package/SKILL.md +10 -4
  3. package/dist/{chunk-DI7Q4T7A.js → chunk-7GMCNHXJ.js} +3 -3
  4. package/dist/{chunk-PDVUVZAU.js → chunk-C5HHTLZX.js} +2 -2
  5. package/dist/{chunk-Z3DPPJNU.js → chunk-D23T3X4V.js} +2 -2
  6. package/dist/{chunk-ZVCKP4EM.js → chunk-EDHPVR2F.js} +3 -3
  7. package/dist/{chunk-UQLAWU67.js → chunk-O3SRIDTH.js} +2 -2
  8. package/dist/{chunk-5XH2GROP.js → chunk-QHAIOVQ3.js} +2 -2
  9. package/dist/{chunk-RVAFVWR2.js → chunk-QZHR2YMK.js} +136 -36
  10. package/dist/chunk-QZHR2YMK.js.map +1 -0
  11. package/dist/{chunk-E4GEKWVP.js → chunk-VOXMOGU5.js} +5 -2
  12. package/dist/chunk-VOXMOGU5.js.map +1 -0
  13. package/dist/cli/index.js +8 -2
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/index.cjs +138 -35
  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 +8 -8
  20. package/dist/providers/anthropic.cjs +138 -35
  21. package/dist/providers/anthropic.cjs.map +1 -1
  22. package/dist/providers/anthropic.d.cts +2 -2
  23. package/dist/providers/anthropic.d.ts +2 -2
  24. package/dist/providers/anthropic.js +3 -3
  25. package/dist/providers/gemini.cjs +138 -35
  26. package/dist/providers/gemini.cjs.map +1 -1
  27. package/dist/providers/gemini.d.cts +2 -2
  28. package/dist/providers/gemini.d.ts +2 -2
  29. package/dist/providers/gemini.js +3 -3
  30. package/dist/providers/llmasaservice.cjs +138 -35
  31. package/dist/providers/llmasaservice.cjs.map +1 -1
  32. package/dist/providers/llmasaservice.d.cts +2 -2
  33. package/dist/providers/llmasaservice.d.ts +2 -2
  34. package/dist/providers/llmasaservice.js +4 -4
  35. package/dist/providers/openai-responses.cjs +138 -35
  36. package/dist/providers/openai-responses.cjs.map +1 -1
  37. package/dist/providers/openai-responses.d.cts +2 -2
  38. package/dist/providers/openai-responses.d.ts +2 -2
  39. package/dist/providers/openai-responses.js +3 -3
  40. package/dist/providers/openai.cjs +138 -35
  41. package/dist/providers/openai.cjs.map +1 -1
  42. package/dist/providers/openai.d.cts +2 -2
  43. package/dist/providers/openai.d.ts +2 -2
  44. package/dist/providers/openai.js +3 -3
  45. package/dist/providers/openrouter.cjs +138 -35
  46. package/dist/providers/openrouter.cjs.map +1 -1
  47. package/dist/providers/openrouter.d.cts +2 -2
  48. package/dist/providers/openrouter.d.ts +2 -2
  49. package/dist/providers/openrouter.js +4 -4
  50. package/dist/{schema-BgVLZ2u5.d.cts → schema-BkOMklgI.d.cts} +129 -0
  51. package/dist/{schema-BgVLZ2u5.d.ts → schema-BkOMklgI.d.ts} +129 -0
  52. package/dist/testing.cjs +4 -1
  53. package/dist/testing.cjs.map +1 -1
  54. package/dist/testing.d.cts +1 -1
  55. package/dist/testing.d.ts +1 -1
  56. package/dist/testing.js +1 -1
  57. package/dist/{types-2hnZmCkH.d.cts → types-B4TWRzUY.d.cts} +1 -1
  58. package/dist/{types-DgAT4jSR.d.ts → types-D6VJJo1x.d.ts} +1 -1
  59. package/dist/usagetap/index.d.cts +2 -2
  60. package/dist/usagetap/index.d.ts +2 -2
  61. package/package.json +1 -1
  62. package/dist/chunk-E4GEKWVP.js.map +0 -1
  63. package/dist/chunk-RVAFVWR2.js.map +0 -1
  64. /package/dist/{chunk-DI7Q4T7A.js.map → chunk-7GMCNHXJ.js.map} +0 -0
  65. /package/dist/{chunk-PDVUVZAU.js.map → chunk-C5HHTLZX.js.map} +0 -0
  66. /package/dist/{chunk-Z3DPPJNU.js.map → chunk-D23T3X4V.js.map} +0 -0
  67. /package/dist/{chunk-ZVCKP4EM.js.map → chunk-EDHPVR2F.js.map} +0 -0
  68. /package/dist/{chunk-UQLAWU67.js.map → chunk-O3SRIDTH.js.map} +0 -0
  69. /package/dist/{chunk-5XH2GROP.js.map → chunk-QHAIOVQ3.js.map} +0 -0
package/README.md CHANGED
@@ -5,17 +5,30 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
6
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
7
7
 
8
- **Turn hardcoded AI prompts into versioned, tested application assets.**
8
+ **Prompts are becoming production code. Manage them that way.**
9
9
 
10
- Your prompts are already in Git. PromptOpsKit makes them manageable.
10
+ AI features often start with a few prompt strings. Then model settings, tools, provider quirks, context limits, environment overrides, tests, and customer-specific behavior start spreading across the codebase.
11
11
 
12
- Keep prompts, model settings, tools, input validation, shared instructions, environment overrides, and tests together in Markdown files that live in Git and ship with your app. Render provider-ready request bodies without giving up your SDK, gateway, auth, retries, routing, observability, or billing.
12
+ PromptOpsKit is an open-source toolkit for turning hardcoded AI prompts into versioned, tested application assets. Keep prompts, model settings, tools, input validation, shared instructions, environment overrides, and tests together in Markdown files that live in Git and ship with your app.
13
+
14
+ No hosted dashboard. No gateway required. No vendor lock-in. Render provider-ready request bodies while keeping your SDK, auth, routing, retries, observability, and billing.
13
15
 
14
16
  PromptOpsKit is not a prompt dashboard, LLM gateway, or hosted runtime service. It is the repo-native layer between scattered prompt strings and production AI calls.
15
17
 
18
+ ## Where this fits in the AI production stack
19
+
20
+ PromptOpsKit handles the open-source prompt asset layer: prompts, model settings, tools, input rules, overrides, and tests in Git.
21
+
22
+ As AI features grow, adjacent production concerns usually appear:
23
+
24
+ - **Provider operations** — routing, caching, cost controls, customer attribution, and gateway reliability. See LLMAsAService.
25
+ - **Customer usage and billing** — usage metering, entitlements, limits, alerts, and usage-based billing. See UsageTap.
26
+
27
+ PromptOpsKit does not require either service. It keeps the prompt layer repo-native and transport-light so teams can adopt the rest of their AI production stack when they need it.
28
+
16
29
  ## Why PromptOpsKit?
17
30
 
18
- From scattered prompt glue:
31
+ When prototypes become real product features, prompt glue is hard to review and easy to drift:
19
32
 
20
33
  - Prompt strings live inline in code
21
34
  - Model config and tools drift in separate files
@@ -23,7 +36,7 @@ From scattered prompt glue:
23
36
  - Environment logic hides in if/else branches
24
37
  - Testing is ad hoc and hard to review
25
38
 
26
- To one reviewable asset:
39
+ PromptOpsKit turns that behavior into one reviewable asset:
27
40
 
28
41
  - Prompt, model, tools, and input rules live together
29
42
  - `includes` and `defaults.md` avoid copy-paste drift
@@ -300,6 +313,7 @@ For no-backend compression, use the local heuristic compressor:
300
313
  compression:
301
314
  heuristic:
302
315
  enabled: true
316
+ mode: conservative
303
317
  query_variable: user_question
304
318
  json_to_toon: true
305
319
  ```
@@ -320,7 +334,7 @@ Payload: {{ json_payload | toon }}
320
334
  Source: {{ source_code | compact }}
321
335
  ```
322
336
 
323
- If `json_to_toon: true` or `{{ value | toon }}` cannot parse a complete JSON object or array, PromptOpsKit preserves the original value and returns a `POK031` warning. When `compression.code.enabled: true`, PromptOpsKit skips TheTokenCompany prompt-template compression and returns `POK033` so code is not text-compressed by a backend.
337
+ The local heuristic compressor is extractive. In its default conservative mode it preserves whole source sentences, skips low-confidence matches, includes nearby context when configured capacity allows, and leaves structured blocks unchanged. If `json_to_toon: true` or `{{ value | toon }}` cannot parse a complete JSON object or array, PromptOpsKit preserves the original value and returns a `POK031` warning. When `compression.code.enabled: true`, PromptOpsKit skips TheTokenCompany prompt-template compression and returns `POK033` so code is not text-compressed by a backend.
324
338
 
325
339
  Credit: the local heuristic approach is based on Jason Kneen's [open-thetokenco](https://github.com/jasonkneen/open-thetokenco/tree/main).
326
340
  TOON preprocessing uses a local encode-only implementation inspired by the MIT-licensed [TOON project](https://github.com/toon-format/toon) by Johann Schopplich, without adding `@toon-format/toon` as a runtime dependency.
package/SKILL.md CHANGED
@@ -485,15 +485,19 @@ language context and no backend call should be made:
485
485
  compression:
486
486
  heuristic:
487
487
  enabled: true
488
+ mode: conservative
488
489
  min_tokens: 120
489
490
  max_sentences: 8
490
491
  target_reduction: 0.45
491
492
  query_variable: user_question
492
493
  ```
493
494
 
494
- The heuristic compressor deduplicates text, ranks sentences against `query`,
495
- `query_variable`, or system instructions, and keeps the highest-scoring
496
- sentences inside the token budget. It is heuristic, so use it for context where
495
+ The heuristic compressor deduplicates adjacent repeated sentences, ranks exact
496
+ source sentences against `query`, `query_variable`, or system instructions, and
497
+ keeps the highest-scoring sentences inside the token budget. The default
498
+ `mode: conservative` skips low-confidence compression, preserves adjacent
499
+ context when `max_sentences` allows, avoids token-level truncation, and leaves
500
+ structured blocks unchanged. It is heuristic, so use it for context where
497
501
  extractive reduction is acceptable, not for code or strict structured payloads.
498
502
 
499
503
  Use TOON preprocessing for complete JSON object/array inputs:
@@ -550,6 +554,7 @@ context:
550
554
  compression:
551
555
  heuristic:
552
556
  enabled: true
557
+ mode: conservative
553
558
  query_variable: user_question
554
559
  - name: payload
555
560
  compression:
@@ -570,7 +575,8 @@ Source: {{ source_code | compact }}
570
575
 
571
576
  Placeholder modifiers are single-token shortcuts. Use
572
577
  `context.inputs[].compression` when a placeholder needs options such as
573
- `query_variable`, `json_to_toon`, or `remove_comments: false`.
578
+ `query_variable`, `mode`, `preserve_neighbors`, `fail_on_low_confidence`,
579
+ `json_to_toon`, or `remove_comments: false`.
574
580
 
575
581
  Render results may include:
576
582
 
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  openaiAdapter
3
- } from "./chunk-PDVUVZAU.js";
3
+ } from "./chunk-C5HHTLZX.js";
4
4
  import {
5
5
  applyRawProviderBody,
6
6
  resolveAssetForProvider,
7
7
  withPromptInputSupport
8
- } from "./chunk-RVAFVWR2.js";
8
+ } from "./chunk-QZHR2YMK.js";
9
9
 
10
10
  // src/providers/llmasaservice.ts
11
11
  var LLMASASERVICE_BASE_URL = "https://gateway.llmasaservice.io";
@@ -127,4 +127,4 @@ export {
127
127
  createLLMAsAServiceOpenAIConfig,
128
128
  llmasaserviceAdapter
129
129
  };
130
- //# sourceMappingURL=chunk-DI7Q4T7A.js.map
130
+ //# sourceMappingURL=chunk-7GMCNHXJ.js.map
@@ -4,7 +4,7 @@ import {
4
4
  renderSections,
5
5
  resolveAssetForProvider,
6
6
  withPromptInputSupport
7
- } from "./chunk-RVAFVWR2.js";
7
+ } from "./chunk-QZHR2YMK.js";
8
8
 
9
9
  // src/providers/openai.ts
10
10
  var openaiAdapter = withPromptInputSupport({
@@ -107,4 +107,4 @@ var openaiAdapter = withPromptInputSupport({
107
107
  export {
108
108
  openaiAdapter
109
109
  };
110
- //# sourceMappingURL=chunk-PDVUVZAU.js.map
110
+ //# sourceMappingURL=chunk-C5HHTLZX.js.map
@@ -4,7 +4,7 @@ import {
4
4
  renderSections,
5
5
  resolveAssetForProvider,
6
6
  withPromptInputSupport
7
- } from "./chunk-RVAFVWR2.js";
7
+ } from "./chunk-QZHR2YMK.js";
8
8
 
9
9
  // src/providers/anthropic.ts
10
10
  var anthropicAdapter = withPromptInputSupport({
@@ -144,4 +144,4 @@ var anthropicAdapter = withPromptInputSupport({
144
144
  export {
145
145
  anthropicAdapter
146
146
  };
147
- //# sourceMappingURL=chunk-Z3DPPJNU.js.map
147
+ //# sourceMappingURL=chunk-D23T3X4V.js.map
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  openaiAdapter
3
- } from "./chunk-PDVUVZAU.js";
3
+ } from "./chunk-C5HHTLZX.js";
4
4
  import {
5
5
  applyRawProviderBody,
6
6
  resolveAssetForProvider,
7
7
  withPromptInputSupport
8
- } from "./chunk-RVAFVWR2.js";
8
+ } from "./chunk-QZHR2YMK.js";
9
9
 
10
10
  // src/providers/openrouter.ts
11
11
  var openrouterAdapter = withPromptInputSupport({
@@ -58,4 +58,4 @@ var openrouterAdapter = withPromptInputSupport({
58
58
  export {
59
59
  openrouterAdapter
60
60
  };
61
- //# sourceMappingURL=chunk-ZVCKP4EM.js.map
61
+ //# sourceMappingURL=chunk-EDHPVR2F.js.map
@@ -4,7 +4,7 @@ import {
4
4
  renderSections,
5
5
  resolveAssetForProvider,
6
6
  withPromptInputSupport
7
- } from "./chunk-RVAFVWR2.js";
7
+ } from "./chunk-QZHR2YMK.js";
8
8
 
9
9
  // src/providers/gemini.ts
10
10
  var geminiAdapter = withPromptInputSupport({
@@ -120,4 +120,4 @@ var geminiAdapter = withPromptInputSupport({
120
120
  export {
121
121
  geminiAdapter
122
122
  };
123
- //# sourceMappingURL=chunk-UQLAWU67.js.map
123
+ //# sourceMappingURL=chunk-O3SRIDTH.js.map
@@ -4,7 +4,7 @@ import {
4
4
  renderSections,
5
5
  resolveAssetForProvider,
6
6
  withPromptInputSupport
7
- } from "./chunk-RVAFVWR2.js";
7
+ } from "./chunk-QZHR2YMK.js";
8
8
 
9
9
  // src/providers/openai-responses.ts
10
10
  var openaiResponsesAdapter = withPromptInputSupport({
@@ -112,4 +112,4 @@ var openaiResponsesAdapter = withPromptInputSupport({
112
112
  export {
113
113
  openaiResponsesAdapter
114
114
  };
115
- //# sourceMappingURL=chunk-5XH2GROP.js.map
115
+ //# sourceMappingURL=chunk-QHAIOVQ3.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  loadPromptFile,
3
3
  parsePrompt
4
- } from "./chunk-E4GEKWVP.js";
4
+ } from "./chunk-VOXMOGU5.js";
5
5
 
6
6
  // src/renderer/interpolate.ts
7
7
  var VARIABLE_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)(?:\s*\|\s*(compress|toon|compact|code))?\s*\}\}/g;
@@ -79,7 +79,10 @@ function normalizeContextInputCompression(value) {
79
79
  target_reduction: value.heuristic.target_reduction,
80
80
  query: value.heuristic.query,
81
81
  query_variable: value.heuristic.query_variable,
82
- json_to_toon: value.heuristic.json_to_toon
82
+ json_to_toon: value.heuristic.json_to_toon,
83
+ mode: value.heuristic.mode,
84
+ preserve_neighbors: value.heuristic.preserve_neighbors,
85
+ fail_on_low_confidence: value.heuristic.fail_on_low_confidence
83
86
  }
84
87
  };
85
88
  const code = normalizeCodeCompression(value.code);
@@ -584,7 +587,10 @@ function line(depth, content, indent) {
584
587
  var TOKEN_REGEX = /[\p{L}\p{N}]+|[^\s]/gu;
585
588
  var TOKEN_NORMALIZE_REGEX = /[^\p{L}\p{N}]/gu;
586
589
  var DEDUPE_NORMALIZE_REGEX = /[^\p{L}\p{N}\s]/gu;
587
- var BOILERPLATE_REGEX = /copyright|all rights reserved|disclaimer|confidential/i;
590
+ var BOILERPLATE_REGEX = /copyright|all rights reserved|disclaimer/i;
591
+ var STRUCTURED_BLOCK_REGEX = /(^|\n)\s*```|(^|\n)\s*\|.+\|\s*(\n|$)/;
592
+ 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;
593
+ 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;
588
594
  var MAX_SEGMENT_TOKENS = 120;
589
595
  var STOP_WORDS = /* @__PURE__ */ new Set([
590
596
  "a",
@@ -618,7 +624,8 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
618
624
  var DEFAULT_OPTIONS = {
619
625
  min_tokens: 80,
620
626
  max_sentences: 10,
621
- target_reduction: 0.45
627
+ target_reduction: 0.45,
628
+ mode: "conservative"
622
629
  };
623
630
  function tokenizeForHeuristicCompression(text) {
624
631
  if (!text) {
@@ -652,25 +659,46 @@ function compressHeuristicText(input, options = {}) {
652
659
  warnings: ["JSON-to-TOON skipped because the input is not a complete valid JSON object or array."]
653
660
  };
654
661
  }
655
- const analysis = preprocessContext(inputText);
656
662
  if (inputTokens === 0) {
657
663
  return toCompressionOutput(inputText, inputTokens);
658
664
  }
665
+ const mode = options.mode ?? DEFAULT_OPTIONS.mode;
666
+ const isConservative = mode === "conservative";
667
+ const failOnLowConfidence = options.fail_on_low_confidence ?? isConservative;
668
+ const preserveNeighbors = options.preserve_neighbors ?? isConservative;
659
669
  const minTokens = options.min_tokens ?? DEFAULT_OPTIONS.min_tokens;
660
670
  if (inputTokens <= minTokens) {
661
671
  return toCompressionOutput(inputText, inputTokens);
662
672
  }
673
+ if (isConservative && looksStructured(inputText)) {
674
+ return toCompressionOutput(inputText, inputTokens, [
675
+ "Heuristic compression skipped because the input appears to contain structured blocks; use TOON or code compaction for structured content."
676
+ ]);
677
+ }
678
+ const analysis = preprocessContext(inputText);
663
679
  const targetReduction = options.target_reduction ?? DEFAULT_OPTIONS.target_reduction;
664
680
  const targetTokens = Math.max(1, Math.max(minTokens, Math.floor(inputTokens * (1 - targetReduction))));
665
681
  const maxSentences = options.max_sentences ?? DEFAULT_OPTIONS.max_sentences;
666
682
  const query = options.query ?? "";
667
683
  const terms = queryTerms(query);
684
+ if (failOnLowConfidence && terms.size === 0) {
685
+ return toCompressionOutput(inputText, inputTokens, [
686
+ "Heuristic compression skipped because no usable relevance query terms were available."
687
+ ]);
688
+ }
668
689
  const scoredSentences = analysis.candidates.map((candidate) => ({
669
690
  sentence: candidate.sentence,
670
691
  index: candidate.index,
671
692
  ...scoreSentenceCandidate(candidate, terms),
672
- tokens: candidate.tokenCount
693
+ tokens: candidate.tokenCount,
694
+ isProtected: candidate.isProtected,
695
+ hasEvidence: candidate.hasEvidence
673
696
  }));
697
+ if (failOnLowConfidence && terms.size > 0 && !scoredSentences.some((candidate) => candidate.overlapCount > 0)) {
698
+ return toCompressionOutput(inputText, inputTokens, [
699
+ "Heuristic compression skipped because no sentence matched the relevance query."
700
+ ]);
701
+ }
674
702
  const selected = [];
675
703
  const selectedIndices = /* @__PURE__ */ new Set();
676
704
  const coveredTerms = /* @__PURE__ */ new Set();
@@ -710,19 +738,15 @@ function compressHeuristicText(input, options = {}) {
710
738
  coveredTerms.add(term);
711
739
  }
712
740
  }
713
- selected.sort((left, right) => left.index - right.index);
714
- let output = selected.map((item) => item.sentence).join(" ");
741
+ const finalSelection = preserveNeighbors ? expandSelectionWithNeighbors(selected, scoredSentences, maxSentences) : selected;
742
+ finalSelection.sort((left, right) => left.index - right.index);
743
+ let output = finalSelection.map((item) => item.sentence).join(" ");
715
744
  if (!output) {
716
- output = tokenizeForHeuristicCompression(inputText).slice(0, targetTokens).join(" ");
717
- } else {
718
- const outputTokens = tokenizeForHeuristicCompression(output);
719
- if (outputTokens.length > targetTokens) {
720
- output = outputTokens.slice(0, targetTokens).join(" ");
721
- }
745
+ output = takeWholeCandidatesWithinBudget(analysis.candidates, targetTokens) || inputText;
722
746
  }
723
747
  return toCompressionOutput(output, inputTokens);
724
748
  }
725
- function toCompressionOutput(output, inputTokens) {
749
+ function toCompressionOutput(output, inputTokens, warnings = []) {
726
750
  const outputTokens = estimateHeuristicTokens(output);
727
751
  const tokensSaved = Math.max(0, inputTokens - outputTokens);
728
752
  return {
@@ -730,7 +754,8 @@ function toCompressionOutput(output, inputTokens) {
730
754
  inputTokens,
731
755
  outputTokens,
732
756
  tokensSaved,
733
- compressionRatio: outputTokens === 0 ? 0 : inputTokens / outputTokens
757
+ compressionRatio: outputTokens === 0 ? 0 : inputTokens / outputTokens,
758
+ ...warnings.length > 0 ? { warnings } : {}
734
759
  };
735
760
  }
736
761
  function isBetterTieBreak(candidate, bestCandidate) {
@@ -748,13 +773,6 @@ function normalizeForDeduping(sentence) {
748
773
  function splitIntoSentences(text) {
749
774
  return text.split(/(?<=[.!?])\s+|\n+/g).map((sentence) => sentence.trim()).filter(Boolean);
750
775
  }
751
- function chunkTokens(tokens, chunkSize) {
752
- const chunks = [];
753
- for (let index = 0; index < tokens.length; index += chunkSize) {
754
- chunks.push(tokens.slice(index, index + chunkSize));
755
- }
756
- return chunks;
757
- }
758
776
  function splitOversizedSentence(sentence, maxTokens = MAX_SEGMENT_TOKENS) {
759
777
  const rawTokens = tokenizeForHeuristicCompression(sentence);
760
778
  if (rawTokens.length <= maxTokens) {
@@ -766,10 +784,10 @@ function splitOversizedSentence(sentence, maxTokens = MAX_SEGMENT_TOKENS) {
766
784
  if (estimateHeuristicTokens(segment) <= maxTokens) {
767
785
  return [segment];
768
786
  }
769
- return chunkTokens(tokenizeForHeuristicCompression(segment), maxTokens).map((chunk) => chunk.join(" "));
787
+ return [segment];
770
788
  });
771
789
  }
772
- return chunkTokens(rawTokens, maxTokens).map((chunk) => chunk.join(" "));
790
+ return [sentence];
773
791
  }
774
792
  function normalizeToken(token) {
775
793
  return token.replace(TOKEN_NORMALIZE_REGEX, "").trim();
@@ -778,19 +796,24 @@ function tokenizeNormalized(text) {
778
796
  return tokenizeForHeuristicCompression(text.toLowerCase()).map(normalizeToken).filter(Boolean);
779
797
  }
780
798
  function queryTerms(query) {
781
- return new Set(tokenizeNormalized(query).filter((term) => term.length >= 3 && !STOP_WORDS.has(term)));
799
+ return new Set(tokenizeNormalized(query).filter((term) => {
800
+ if (STOP_WORDS.has(term)) {
801
+ return false;
802
+ }
803
+ return term.length >= 3 || /^(?:[a-z]\d|\d+[a-z]?|[a-z]{2})$/i.test(term);
804
+ }));
782
805
  }
783
806
  function preprocessContext(context) {
784
807
  const originalTokens = estimateHeuristicTokens(context);
785
- const dedupeSet = /* @__PURE__ */ new Set();
786
808
  const candidates = [];
809
+ let previousNormalized = "";
787
810
  for (const sentence of splitIntoSentences(context)) {
788
811
  for (const segment of splitOversizedSentence(sentence)) {
789
812
  const normalized = normalizeForDeduping(segment);
790
- if (!normalized || dedupeSet.has(normalized)) {
813
+ if (!normalized || normalized === previousNormalized) {
791
814
  continue;
792
815
  }
793
- dedupeSet.add(normalized);
816
+ previousNormalized = normalized;
794
817
  const rawTokens = tokenizeForHeuristicCompression(segment);
795
818
  const normalizedTerms = rawTokens.map((token) => normalizeToken(token.toLowerCase())).filter(Boolean);
796
819
  const lowered = segment.toLowerCase();
@@ -799,7 +822,9 @@ function preprocessContext(context) {
799
822
  index: candidates.length,
800
823
  tokenCount: rawTokens.length,
801
824
  uniqueTerms: new Set(normalizedTerms),
802
- isBoilerplate: BOILERPLATE_REGEX.test(lowered)
825
+ isBoilerplate: BOILERPLATE_REGEX.test(lowered),
826
+ isProtected: PROTECTED_SIGNAL_REGEX.test(segment),
827
+ hasEvidence: EVIDENCE_SIGNAL_REGEX.test(segment)
803
828
  });
804
829
  }
805
830
  }
@@ -825,12 +850,62 @@ function scoreSentenceCandidate(candidate, terms) {
825
850
  const densityScore = terms.size > 0 ? overlap / terms.size : 0;
826
851
  const lengthScore = Math.min(candidate.tokenCount, 40) / 40;
827
852
  const boilerplatePenalty = candidate.isBoilerplate ? 0.5 : 0;
853
+ const protectedScore = candidate.isProtected ? 0.9 : 0;
854
+ const evidenceScore = candidate.hasEvidence ? 0.4 : 0;
828
855
  return {
829
- score: overlapScore + densityScore + lengthScore - boilerplatePenalty,
856
+ score: overlapScore + densityScore + lengthScore + protectedScore + evidenceScore - boilerplatePenalty,
830
857
  overlapCount: overlap,
831
858
  overlapTerms
832
859
  };
833
860
  }
861
+ function looksStructured(text) {
862
+ if (STRUCTURED_BLOCK_REGEX.test(text)) {
863
+ return true;
864
+ }
865
+ const lines = text.split(/\r?\n/).map((line2) => line2.trim()).filter(Boolean);
866
+ if (lines.length < 3) {
867
+ return false;
868
+ }
869
+ const listLikeLines = lines.filter((line2) => /^[-*+] |\d+[.)] /.test(line2)).length;
870
+ return listLikeLines >= 3 && listLikeLines / lines.length >= 0.6;
871
+ }
872
+ function expandSelectionWithNeighbors(selected, scoredSentences, maxSentences) {
873
+ if (selected.length === 0 || selected.length >= maxSentences) {
874
+ return selected;
875
+ }
876
+ const byIndex = new Map(scoredSentences.map((candidate) => [candidate.index, candidate]));
877
+ const expanded = new Map(selected.map((candidate) => [candidate.index, candidate]));
878
+ for (const candidate of selected) {
879
+ if (expanded.size >= maxSentences) {
880
+ break;
881
+ }
882
+ if (candidate.overlapCount === 0 && !candidate.isProtected && !candidate.hasEvidence) {
883
+ continue;
884
+ }
885
+ for (const neighborIndex of [candidate.index - 1, candidate.index + 1]) {
886
+ if (expanded.size >= maxSentences) {
887
+ break;
888
+ }
889
+ const neighbor = byIndex.get(neighborIndex);
890
+ if (neighbor && !expanded.has(neighbor.index)) {
891
+ expanded.set(neighbor.index, neighbor);
892
+ }
893
+ }
894
+ }
895
+ return [...expanded.values()];
896
+ }
897
+ function takeWholeCandidatesWithinBudget(candidates, targetTokens) {
898
+ const selected = [];
899
+ let tokens = 0;
900
+ for (const candidate of candidates) {
901
+ if (tokens + candidate.tokenCount > targetTokens) {
902
+ break;
903
+ }
904
+ selected.push(candidate.sentence);
905
+ tokens += candidate.tokenCount;
906
+ }
907
+ return selected.join(" ");
908
+ }
834
909
 
835
910
  // src/code-compaction.ts
836
911
  function compactCode(input, options = {}) {
@@ -996,7 +1071,10 @@ function renderSections(asset, options = {}) {
996
1071
  max_sentences: configuredHeuristic?.max_sentences,
997
1072
  target_reduction: configuredHeuristic?.target_reduction,
998
1073
  query,
999
- json_to_toon: configuredHeuristic?.json_to_toon
1074
+ json_to_toon: configuredHeuristic?.json_to_toon,
1075
+ mode: configuredHeuristic?.mode,
1076
+ preserve_neighbors: configuredHeuristic?.preserve_neighbors,
1077
+ fail_on_low_confidence: configuredHeuristic?.fail_on_low_confidence
1000
1078
  };
1001
1079
  const cacheKey = JSON.stringify([name, value, compressionOptions]);
1002
1080
  const cached = compressionCache.get(cacheKey);
@@ -1520,7 +1598,10 @@ async function applyPromptCompressionForRender(asset, runtime) {
1520
1598
  max_sentences: heuristicConfig?.max_sentences,
1521
1599
  target_reduction: heuristicConfig?.target_reduction,
1522
1600
  query: resolveHeuristicPromptQuery(heuristicConfig, runtime.variables, sections.system_instructions),
1523
- json_to_toon: heuristicConfig?.json_to_toon
1601
+ json_to_toon: heuristicConfig?.json_to_toon,
1602
+ mode: heuristicConfig?.mode,
1603
+ preserve_neighbors: heuristicConfig?.preserve_neighbors,
1604
+ fail_on_low_confidence: heuristicConfig?.fail_on_low_confidence
1524
1605
  });
1525
1606
  promptTemplate = result.output;
1526
1607
  reportHeuristicCompressionWarnings2(warnings, result.warnings, "prompt template");
@@ -1645,10 +1726,29 @@ async function compressWithTheTokenCompany(input, options) {
1645
1726
  );
1646
1727
  }
1647
1728
  const data = await response.json();
1648
- if (typeof data.output !== "string" || typeof data.output_tokens !== "number" || typeof data.input_tokens !== "number" || typeof data.tokens_saved !== "number" || typeof data.compression_ratio !== "number") {
1729
+ const normalized = normalizeTheTokenCompanyCompressResponse(data);
1730
+ if (!normalized) {
1649
1731
  throw new Error("TheTokenCompany compression returned an invalid response payload.");
1650
1732
  }
1651
- return data;
1733
+ return normalized;
1734
+ }
1735
+ function normalizeTheTokenCompanyCompressResponse(data) {
1736
+ if (typeof data.output !== "string" || typeof data.output_tokens !== "number") {
1737
+ return void 0;
1738
+ }
1739
+ const inputTokens = typeof data.input_tokens === "number" ? data.input_tokens : data.original_input_tokens;
1740
+ if (typeof inputTokens !== "number") {
1741
+ return void 0;
1742
+ }
1743
+ const tokensSaved = typeof data.tokens_saved === "number" ? data.tokens_saved : inputTokens - data.output_tokens;
1744
+ const compressionRatio = typeof data.compression_ratio === "number" ? data.compression_ratio : data.output_tokens === 0 ? 0 : inputTokens / data.output_tokens;
1745
+ return {
1746
+ output: data.output,
1747
+ output_tokens: data.output_tokens,
1748
+ input_tokens: inputTokens,
1749
+ tokens_saved: tokensSaved,
1750
+ compression_ratio: compressionRatio
1751
+ };
1652
1752
  }
1653
1753
  function getEnv(name) {
1654
1754
  if (typeof process === "undefined") {
@@ -1757,4 +1857,4 @@ export {
1757
1857
  withPromptInputSupport,
1758
1858
  applyRawProviderBody
1759
1859
  };
1760
- //# sourceMappingURL=chunk-RVAFVWR2.js.map
1860
+ //# sourceMappingURL=chunk-QZHR2YMK.js.map