open-multi-agent-kit 1.0.0 → 1.2.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 (244) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/README.md +2 -2
  3. package/dist/cli/help.d.ts.map +1 -1
  4. package/dist/cli/help.js +1 -0
  5. package/dist/cli/help.js.map +1 -1
  6. package/dist/coordination/awareness.d.ts +32 -0
  7. package/dist/coordination/awareness.d.ts.map +1 -0
  8. package/dist/coordination/awareness.js +55 -0
  9. package/dist/coordination/awareness.js.map +1 -0
  10. package/dist/coordination/broker.d.ts +79 -0
  11. package/dist/coordination/broker.d.ts.map +1 -0
  12. package/dist/coordination/broker.js +195 -0
  13. package/dist/coordination/broker.js.map +1 -0
  14. package/dist/coordination/index.d.ts +16 -0
  15. package/dist/coordination/index.d.ts.map +1 -0
  16. package/dist/coordination/index.js +16 -0
  17. package/dist/coordination/index.js.map +1 -0
  18. package/dist/coordination/integration.d.ts +59 -0
  19. package/dist/coordination/integration.d.ts.map +1 -0
  20. package/dist/coordination/integration.js +126 -0
  21. package/dist/coordination/integration.js.map +1 -0
  22. package/dist/coordination/operation.d.ts +106 -0
  23. package/dist/coordination/operation.d.ts.map +1 -0
  24. package/dist/coordination/operation.js +179 -0
  25. package/dist/coordination/operation.js.map +1 -0
  26. package/dist/coordination/resource.d.ts +33 -0
  27. package/dist/coordination/resource.d.ts.map +1 -0
  28. package/dist/coordination/resource.js +89 -0
  29. package/dist/coordination/resource.js.map +1 -0
  30. package/dist/coordination/session.d.ts +74 -0
  31. package/dist/coordination/session.d.ts.map +1 -0
  32. package/dist/coordination/session.js +143 -0
  33. package/dist/coordination/session.js.map +1 -0
  34. package/dist/coordination/types.d.ts +68 -0
  35. package/dist/coordination/types.d.ts.map +1 -0
  36. package/dist/coordination/types.js +26 -0
  37. package/dist/coordination/types.js.map +1 -0
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +28 -7
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/compaction/control-state.d.ts +41 -0
  42. package/dist/core/compaction/control-state.d.ts.map +1 -0
  43. package/dist/core/compaction/control-state.js +85 -0
  44. package/dist/core/compaction/control-state.js.map +1 -0
  45. package/dist/core/compaction/fallback.d.ts +60 -0
  46. package/dist/core/compaction/fallback.d.ts.map +1 -0
  47. package/dist/core/compaction/fallback.js +117 -0
  48. package/dist/core/compaction/fallback.js.map +1 -0
  49. package/dist/core/compaction/index.d.ts +2 -0
  50. package/dist/core/compaction/index.d.ts.map +1 -1
  51. package/dist/core/compaction/index.js +2 -0
  52. package/dist/core/compaction/index.js.map +1 -1
  53. package/dist/core/context-budget-headroom-candidates.d.ts +16 -1
  54. package/dist/core/context-budget-headroom-candidates.d.ts.map +1 -1
  55. package/dist/core/context-budget-headroom-candidates.js +31 -13
  56. package/dist/core/context-budget-headroom-candidates.js.map +1 -1
  57. package/dist/core/context-budget-headroom-types.d.ts +7 -1
  58. package/dist/core/context-budget-headroom-types.d.ts.map +1 -1
  59. package/dist/core/context-budget-headroom-types.js +0 -1
  60. package/dist/core/context-budget-headroom-types.js.map +1 -1
  61. package/dist/core/context-budget-headroom.d.ts +10 -0
  62. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  63. package/dist/core/context-budget-headroom.js +11 -2
  64. package/dist/core/context-budget-headroom.js.map +1 -1
  65. package/dist/core/context-budget-v2-global-pass.d.ts +21 -0
  66. package/dist/core/context-budget-v2-global-pass.d.ts.map +1 -0
  67. package/dist/core/context-budget-v2-global-pass.js +203 -0
  68. package/dist/core/context-budget-v2-global-pass.js.map +1 -0
  69. package/dist/core/context-budget-v2-planned-items.d.ts +12 -0
  70. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -1
  71. package/dist/core/context-budget-v2-planned-items.js +21 -2
  72. package/dist/core/context-budget-v2-planned-items.js.map +1 -1
  73. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  74. package/dist/core/context-budget-v2-planner.js +18 -19
  75. package/dist/core/context-budget-v2-planner.js.map +1 -1
  76. package/dist/core/context-budget-v2-scoring.d.ts +7 -1
  77. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  78. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  79. package/dist/core/context-budget-v2-selection.d.ts +3 -0
  80. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  81. package/dist/core/context-budget-v2-selection.js +6 -2
  82. package/dist/core/context-budget-v2-selection.js.map +1 -1
  83. package/dist/core/context-budget-v2-types.d.ts +6 -1
  84. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  85. package/dist/core/context-budget-v2-types.js +6 -1
  86. package/dist/core/context-budget-v2-types.js.map +1 -1
  87. package/dist/core/provider-default-models.d.ts +1 -0
  88. package/dist/core/provider-default-models.d.ts.map +1 -1
  89. package/dist/core/provider-default-models.js +1 -0
  90. package/dist/core/provider-default-models.js.map +1 -1
  91. package/dist/core/provider-display-names.d.ts.map +1 -1
  92. package/dist/core/provider-display-names.js +1 -0
  93. package/dist/core/provider-display-names.js.map +1 -1
  94. package/dist/core/reasoning-router-resolver.d.ts +7 -0
  95. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  96. package/dist/core/reasoning-router-resolver.js +21 -3
  97. package/dist/core/reasoning-router-resolver.js.map +1 -1
  98. package/dist/core/reasoning-router-v4.d.ts +8 -5
  99. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  100. package/dist/core/reasoning-router-v4.js +8 -5
  101. package/dist/core/reasoning-router-v4.js.map +1 -1
  102. package/dist/core/session-compaction-service.d.ts +15 -0
  103. package/dist/core/session-compaction-service.d.ts.map +1 -1
  104. package/dist/core/session-compaction-service.js +17 -4
  105. package/dist/core/session-compaction-service.js.map +1 -1
  106. package/dist/core/todo-runtime-state.d.ts +12 -0
  107. package/dist/core/todo-runtime-state.d.ts.map +1 -1
  108. package/dist/core/todo-runtime-state.js +23 -0
  109. package/dist/core/todo-runtime-state.js.map +1 -1
  110. package/dist/index.d.ts +2 -0
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +2 -0
  113. package/dist/index.js.map +1 -1
  114. package/dist/metacognition/calibration-selective.d.ts +78 -0
  115. package/dist/metacognition/calibration-selective.d.ts.map +1 -0
  116. package/dist/metacognition/calibration-selective.js +161 -0
  117. package/dist/metacognition/calibration-selective.js.map +1 -0
  118. package/dist/metacognition/calibration.d.ts +62 -0
  119. package/dist/metacognition/calibration.d.ts.map +1 -0
  120. package/dist/metacognition/calibration.js +122 -0
  121. package/dist/metacognition/calibration.js.map +1 -0
  122. package/dist/metacognition/checkpoint.d.ts +60 -0
  123. package/dist/metacognition/checkpoint.d.ts.map +1 -0
  124. package/dist/metacognition/checkpoint.js +57 -0
  125. package/dist/metacognition/checkpoint.js.map +1 -0
  126. package/dist/metacognition/context7.d.ts +48 -0
  127. package/dist/metacognition/context7.d.ts.map +1 -0
  128. package/dist/metacognition/context7.js +154 -0
  129. package/dist/metacognition/context7.js.map +1 -0
  130. package/dist/metacognition/decision.d.ts +44 -0
  131. package/dist/metacognition/decision.d.ts.map +1 -0
  132. package/dist/metacognition/decision.js +111 -0
  133. package/dist/metacognition/decision.js.map +1 -0
  134. package/dist/metacognition/evaluation.d.ts +73 -0
  135. package/dist/metacognition/evaluation.d.ts.map +1 -0
  136. package/dist/metacognition/evaluation.js +97 -0
  137. package/dist/metacognition/evaluation.js.map +1 -0
  138. package/dist/metacognition/index.d.ts +31 -0
  139. package/dist/metacognition/index.d.ts.map +1 -0
  140. package/dist/metacognition/index.js +31 -0
  141. package/dist/metacognition/index.js.map +1 -0
  142. package/dist/metacognition/knowledge-action.d.ts +43 -0
  143. package/dist/metacognition/knowledge-action.d.ts.map +1 -0
  144. package/dist/metacognition/knowledge-action.js +60 -0
  145. package/dist/metacognition/knowledge-action.js.map +1 -0
  146. package/dist/metacognition/knowledge.d.ts +84 -0
  147. package/dist/metacognition/knowledge.d.ts.map +1 -0
  148. package/dist/metacognition/knowledge.js +165 -0
  149. package/dist/metacognition/knowledge.js.map +1 -0
  150. package/dist/metacognition/obligations.d.ts +82 -0
  151. package/dist/metacognition/obligations.d.ts.map +1 -0
  152. package/dist/metacognition/obligations.js +139 -0
  153. package/dist/metacognition/obligations.js.map +1 -0
  154. package/dist/metacognition/observation-validity.d.ts +83 -0
  155. package/dist/metacognition/observation-validity.d.ts.map +1 -0
  156. package/dist/metacognition/observation-validity.js +118 -0
  157. package/dist/metacognition/observation-validity.js.map +1 -0
  158. package/dist/metacognition/observe.d.ts +45 -0
  159. package/dist/metacognition/observe.d.ts.map +1 -0
  160. package/dist/metacognition/observe.js +59 -0
  161. package/dist/metacognition/observe.js.map +1 -0
  162. package/dist/metacognition/policy.d.ts +53 -0
  163. package/dist/metacognition/policy.d.ts.map +1 -0
  164. package/dist/metacognition/policy.js +155 -0
  165. package/dist/metacognition/policy.js.map +1 -0
  166. package/dist/metacognition/predictions.d.ts +78 -0
  167. package/dist/metacognition/predictions.d.ts.map +1 -0
  168. package/dist/metacognition/predictions.js +181 -0
  169. package/dist/metacognition/predictions.js.map +1 -0
  170. package/dist/metacognition/retrieval.d.ts +53 -0
  171. package/dist/metacognition/retrieval.d.ts.map +1 -0
  172. package/dist/metacognition/retrieval.js +170 -0
  173. package/dist/metacognition/retrieval.js.map +1 -0
  174. package/dist/metacognition/risk.d.ts +48 -0
  175. package/dist/metacognition/risk.d.ts.map +1 -0
  176. package/dist/metacognition/risk.js +165 -0
  177. package/dist/metacognition/risk.js.map +1 -0
  178. package/dist/metacognition/route-economics.d.ts +95 -0
  179. package/dist/metacognition/route-economics.d.ts.map +1 -0
  180. package/dist/metacognition/route-economics.js +110 -0
  181. package/dist/metacognition/route-economics.js.map +1 -0
  182. package/dist/metacognition/runtime-bridge.d.ts +54 -0
  183. package/dist/metacognition/runtime-bridge.d.ts.map +1 -0
  184. package/dist/metacognition/runtime-bridge.js +140 -0
  185. package/dist/metacognition/runtime-bridge.js.map +1 -0
  186. package/dist/metacognition/skills.d.ts +42 -0
  187. package/dist/metacognition/skills.d.ts.map +1 -0
  188. package/dist/metacognition/skills.js +220 -0
  189. package/dist/metacognition/skills.js.map +1 -0
  190. package/dist/metacognition/state.d.ts +110 -0
  191. package/dist/metacognition/state.d.ts.map +1 -0
  192. package/dist/metacognition/state.js +52 -0
  193. package/dist/metacognition/state.js.map +1 -0
  194. package/dist/metacognition/validation.d.ts +19 -0
  195. package/dist/metacognition/validation.d.ts.map +1 -0
  196. package/dist/metacognition/validation.js +58 -0
  197. package/dist/metacognition/validation.js.map +1 -0
  198. package/dist/metacognition/verification.d.ts +93 -0
  199. package/dist/metacognition/verification.d.ts.map +1 -0
  200. package/dist/metacognition/verification.js +132 -0
  201. package/dist/metacognition/verification.js.map +1 -0
  202. package/dist/metacognition/verifier.d.ts +49 -0
  203. package/dist/metacognition/verifier.d.ts.map +1 -0
  204. package/dist/metacognition/verifier.js +88 -0
  205. package/dist/metacognition/verifier.js.map +1 -0
  206. package/dist/observation/identity.d.ts +14 -0
  207. package/dist/observation/identity.d.ts.map +1 -0
  208. package/dist/observation/identity.js +29 -0
  209. package/dist/observation/identity.js.map +1 -0
  210. package/dist/observation/index.d.ts +13 -0
  211. package/dist/observation/index.d.ts.map +1 -0
  212. package/dist/observation/index.js +13 -0
  213. package/dist/observation/index.js.map +1 -0
  214. package/dist/observation/observe-mode.d.ts +46 -0
  215. package/dist/observation/observe-mode.d.ts.map +1 -0
  216. package/dist/observation/observe-mode.js +83 -0
  217. package/dist/observation/observe-mode.js.map +1 -0
  218. package/dist/observation/store.d.ts +53 -0
  219. package/dist/observation/store.d.ts.map +1 -0
  220. package/dist/observation/store.js +126 -0
  221. package/dist/observation/store.js.map +1 -0
  222. package/dist/observation/types.d.ts +72 -0
  223. package/dist/observation/types.d.ts.map +1 -0
  224. package/dist/observation/types.js +9 -0
  225. package/dist/observation/types.js.map +1 -0
  226. package/dist/observation/view.d.ts +39 -0
  227. package/dist/observation/view.d.ts.map +1 -0
  228. package/dist/observation/view.js +173 -0
  229. package/dist/observation/view.js.map +1 -0
  230. package/docs/metacognition.md +51 -0
  231. package/docs/model-catalog-refresh.md +59 -0
  232. package/docs/providers.md +53 -0
  233. package/docs/runtime-algorithms.md +107 -3
  234. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  235. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  236. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  237. package/examples/extensions/gondolin/package-lock.json +2 -2
  238. package/examples/extensions/gondolin/package.json +1 -1
  239. package/examples/extensions/sandbox/package-lock.json +2 -2
  240. package/examples/extensions/sandbox/package.json +1 -1
  241. package/examples/extensions/with-deps/package-lock.json +2 -2
  242. package/examples/extensions/with-deps/package.json +1 -1
  243. package/npm-shrinkwrap.json +18 -18
  244. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"context-budget-headroom-types.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-headroom-types.ts"],"names":[],"mappings":"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","sourcesContent":["export type ContextBudgetTierV2 =\n\t| \"system\"\n\t| \"active-goal\"\n\t| \"current-files\"\n\t| \"tools\"\n\t| \"skills\"\n\t| \"mcp\"\n\t| \"history\"\n\t| \"evidence\"\n\t| \"scratch\";\n\nexport type ContextBudgetPriorityV2 = \"hard\" | \"high\" | \"medium\" | \"low\";\n\nexport type ContextEvidenceKindV2 = \"test\" | \"command\" | \"file\" | \"decision\" | \"lane\" | \"user\" | \"none\";\n\nexport type ContextRepresentationKindV2 = \"full\" | \"pointer\" | \"summary\" | \"headroom-compressed\" | \"omit\";\n\nexport interface ContextSourceRefV2 {\n\treadonly uri: string;\n\treadonly symbol?: string;\n\treadonly range?: { readonly startLine: number; readonly endLine: number };\n\treadonly contentHash: string;\n\treadonly retrievable: boolean;\n}\n\nexport type ContextRepresentationFidelityV2 = \"exact\" | \"bounded\" | \"lossy\" | \"reversible\";\n\nexport interface ContextRepresentationCacheMetadataV2 {\n\treadonly hit: boolean;\n\treadonly key: string;\n\treadonly keyKind?: \"exact\" | \"materialized\";\n\treadonly layer?: \"turn\" | \"session\" | \"workspace\" | \"shared\";\n\treadonly rejectedReason?: string;\n}\n\nexport interface ContextRepresentationCandidateV2 {\n\treadonly kind: ContextRepresentationKindV2;\n\treadonly text: string;\n\treadonly estimatedTokens: number;\n\treadonly fidelity: ContextRepresentationFidelityV2;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly summaryHash?: string;\n\treadonly compressorId?: string;\n\treadonly cache?: ContextRepresentationCacheMetadataV2;\n}\n\nexport interface ContextBudgetItemV2 {\n\treadonly id: string;\n\treadonly tier: ContextBudgetTierV2;\n\treadonly priority: ContextBudgetPriorityV2;\n\treadonly text: string;\n\treadonly tokenEstimate?: number;\n\treadonly relevance?: number;\n\treadonly recency?: number;\n\treadonly evidenceValue?: number;\n\treadonly evidenceKind?: ContextEvidenceKindV2;\n\treadonly ageTurns?: number;\n\treadonly redundancyKey?: string;\n\treadonly required?: boolean;\n\treadonly pinReason?: string;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly representations?: readonly ContextRepresentationCandidateV2[];\n}\n\nexport interface HeadroomQualityPolicyV2 {\n\treadonly preferFullForHighPriority: boolean;\n\treadonly preferPointerForRetrievable: boolean;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly headroomThresholdTokens: number;\n\treadonly allowOmit: boolean;\n}\n\nexport const DEFAULT_HEADROOM_QUALITY_POLICY: HeadroomQualityPolicyV2 = {\n\tpreferFullForHighPriority: true,\n\tpreferPointerForRetrievable: true,\n\tsummaryMaxAgeTurns: 4,\n\theadroomThresholdTokens: 400,\n\tallowOmit: true,\n};\n\nexport interface HeadroomRuntimeStatusV2 {\n\treadonly policyId: string;\n\treadonly selector: string;\n\treadonly headroomThresholdTokens: number;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly representations: readonly ContextRepresentationKindV2[];\n}\n\nexport function getHeadroomRuntimeStatus(\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): HeadroomRuntimeStatusV2 {\n\treturn {\n\t\tpolicyId: \"context-budget-v2\",\n\t\tselector: \"headroom-selector\",\n\t\theadroomThresholdTokens: policy.headroomThresholdTokens,\n\t\tsummaryMaxAgeTurns: policy.summaryMaxAgeTurns,\n\t\trepresentations: [\"full\", \"pointer\", \"summary\", \"headroom-compressed\", \"omit\"],\n\t};\n}\n\nexport interface RepresentationBudgetContext {\n\treadonly tierUsedTokens: number;\n\treadonly tierCeilingTokens: number;\n\treadonly remainingGlobalTokens: number;\n}\n\nconst CHARS_PER_TOKEN_HEURISTIC = 4;\n\nexport function heuristicTokenCount(text: string): number {\n\tif (text.length === 0) {\n\t\treturn 0;\n\t}\n\treturn Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fullTextTokens(item: ContextBudgetItemV2): number {\n\tif (item.tokenEstimate !== undefined && Number.isFinite(item.tokenEstimate)) {\n\t\treturn Math.max(0, Math.floor(item.tokenEstimate));\n\t}\n\treturn heuristicTokenCount(item.text);\n}\n\nexport function estimatePointerTokens(ref: ContextSourceRefV2): number {\n\tlet total = ref.uri.length + (ref.contentHash?.length ?? 0) + 16;\n\tif (ref.symbol) total += ref.symbol.length;\n\tif (ref.range) total += 8;\n\treturn Math.max(8, Math.ceil(total / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fnv1aHex(input: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < input.length; i++) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193) >>> 0;\n\t}\n\treturn hash.toString(16).padStart(8, \"0\");\n}\n"]}
1
+ {"version":3,"file":"context-budget-headroom-types.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-headroom-types.ts"],"names":[],"mappings":"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","sourcesContent":["export type ContextBudgetTierV2 =\n\t| \"system\"\n\t| \"active-goal\"\n\t| \"current-files\"\n\t| \"tools\"\n\t| \"skills\"\n\t| \"mcp\"\n\t| \"history\"\n\t| \"evidence\"\n\t| \"scratch\";\n\nexport type ContextBudgetPriorityV2 = \"hard\" | \"high\" | \"medium\" | \"low\";\n\nexport type ContextEvidenceKindV2 = \"test\" | \"command\" | \"file\" | \"decision\" | \"lane\" | \"user\" | \"none\";\n\nexport type ContextRepresentationKindV2 = \"full\" | \"pointer\" | \"summary\" | \"headroom-compressed\" | \"omit\";\n\nexport interface ContextSourceRefV2 {\n\treadonly uri: string;\n\treadonly symbol?: string;\n\treadonly range?: { readonly startLine: number; readonly endLine: number };\n\treadonly contentHash: string;\n\treadonly retrievable: boolean;\n}\n\nexport type ContextRepresentationFidelityV2 = \"exact\" | \"bounded\" | \"lossy\" | \"reversible\";\n\nexport interface ContextRepresentationCacheMetadataV2 {\n\treadonly hit: boolean;\n\treadonly key: string;\n\treadonly keyKind?: \"exact\" | \"materialized\";\n\treadonly layer?: \"turn\" | \"session\" | \"workspace\" | \"shared\";\n\treadonly rejectedReason?: string;\n}\n\nexport interface ContextRepresentationCandidateV2 {\n\treadonly kind: ContextRepresentationKindV2;\n\treadonly text: string;\n\treadonly estimatedTokens: number;\n\treadonly fidelity: ContextRepresentationFidelityV2;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly summaryHash?: string;\n\treadonly compressorId?: string;\n\treadonly cache?: ContextRepresentationCacheMetadataV2;\n}\n\nexport interface ContextBudgetItemV2 {\n\treadonly id: string;\n\treadonly tier: ContextBudgetTierV2;\n\treadonly priority: ContextBudgetPriorityV2;\n\treadonly text: string;\n\treadonly tokenEstimate?: number;\n\treadonly relevance?: number;\n\treadonly recency?: number;\n\treadonly evidenceValue?: number;\n\treadonly evidenceKind?: ContextEvidenceKindV2;\n\treadonly ageTurns?: number;\n\treadonly redundancyKey?: string;\n\treadonly required?: boolean;\n\treadonly pinReason?: string;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly representations?: readonly ContextRepresentationCandidateV2[];\n}\n\nexport interface HeadroomQualityPolicyV2 {\n\t/**\n\t * @deprecated Never read by the candidate generator or the selector; the\n\t * priority weight already prefers full text for high-priority items. Kept\n\t * optional so existing configs still type-check. Flipping it changes no\n\t * decision (see context-budget-quality-policy-semantics.test.ts).\n\t */\n\treadonly preferFullForHighPriority?: boolean;\n\treadonly preferPointerForRetrievable: boolean;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly headroomThresholdTokens: number;\n\treadonly allowOmit: boolean;\n}\n\nexport const DEFAULT_HEADROOM_QUALITY_POLICY: HeadroomQualityPolicyV2 = {\n\tpreferPointerForRetrievable: true,\n\tsummaryMaxAgeTurns: 4,\n\theadroomThresholdTokens: 400,\n\tallowOmit: true,\n};\n\nexport interface HeadroomRuntimeStatusV2 {\n\treadonly policyId: string;\n\treadonly selector: string;\n\treadonly headroomThresholdTokens: number;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly representations: readonly ContextRepresentationKindV2[];\n}\n\nexport function getHeadroomRuntimeStatus(\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): HeadroomRuntimeStatusV2 {\n\treturn {\n\t\tpolicyId: \"context-budget-v2\",\n\t\tselector: \"headroom-selector\",\n\t\theadroomThresholdTokens: policy.headroomThresholdTokens,\n\t\tsummaryMaxAgeTurns: policy.summaryMaxAgeTurns,\n\t\trepresentations: [\"full\", \"pointer\", \"summary\", \"headroom-compressed\", \"omit\"],\n\t};\n}\n\nexport interface RepresentationBudgetContext {\n\treadonly tierUsedTokens: number;\n\treadonly tierCeilingTokens: number;\n\treadonly remainingGlobalTokens: number;\n}\n\nconst CHARS_PER_TOKEN_HEURISTIC = 4;\n\nexport function heuristicTokenCount(text: string): number {\n\tif (text.length === 0) {\n\t\treturn 0;\n\t}\n\treturn Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fullTextTokens(item: ContextBudgetItemV2): number {\n\tif (item.tokenEstimate !== undefined && Number.isFinite(item.tokenEstimate)) {\n\t\treturn Math.max(0, Math.floor(item.tokenEstimate));\n\t}\n\treturn heuristicTokenCount(item.text);\n}\n\nexport function estimatePointerTokens(ref: ContextSourceRefV2): number {\n\tlet total = ref.uri.length + (ref.contentHash?.length ?? 0) + 16;\n\tif (ref.symbol) total += ref.symbol.length;\n\tif (ref.range) total += 8;\n\treturn Math.max(8, Math.ceil(total / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fnv1aHex(input: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < input.length; i++) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193) >>> 0;\n\t}\n\treturn hash.toString(16).padStart(8, \"0\");\n}\n"]}
@@ -1,5 +1,4 @@
1
1
  export const DEFAULT_HEADROOM_QUALITY_POLICY = {
2
- preferFullForHighPriority: true,
3
2
  preferPointerForRetrievable: true,
4
3
  summaryMaxAgeTurns: 4,
5
4
  headroomThresholdTokens: 400,
@@ -1 +1 @@
1
- {"version":3,"file":"context-budget-headroom-types.js","sourceRoot":"","sources":["../../src/core/context-budget-headroom-types.ts"],"names":[],"mappings":"AAwEA,MAAM,CAAC,MAAM,+BAA+B,GAA4B;IACvE,yBAAyB,EAAE,IAAI;IAC/B,2BAA2B,EAAE,IAAI;IACjC,kBAAkB,EAAE,CAAC;IACrB,uBAAuB,EAAE,GAAG;IAC5B,SAAS,EAAE,IAAI;CACf,CAAC;AAUF,MAAM,UAAU,wBAAwB,CACvC,MAAM,GAA4B,+BAA+B,EACvC;IAC1B,OAAO;QACN,QAAQ,EAAE,mBAAmB;QAC7B,QAAQ,EAAE,mBAAmB;QAC7B,uBAAuB,EAAE,MAAM,CAAC,uBAAuB;QACvD,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;QAC7C,eAAe,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,CAAC;KAC9E,CAAC;AAAA,CACF;AAQD,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAEpC,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAU;IACzD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,yBAAyB,CAAC,CAAC,CAAC;AAAA,CACvE;AAED,MAAM,UAAU,cAAc,CAAC,IAAyB,EAAU;IACjE,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACtC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAuB,EAAU;IACtE,IAAI,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;IACjE,IAAI,GAAG,CAAC,MAAM;QAAE,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;IAC3C,IAAI,GAAG,CAAC,KAAK;QAAE,KAAK,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,yBAAyB,CAAC,CAAC,CAAC;AAAA,CACjE;AAED,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAU;IAC/C,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAAA,CAC1C","sourcesContent":["export type ContextBudgetTierV2 =\n\t| \"system\"\n\t| \"active-goal\"\n\t| \"current-files\"\n\t| \"tools\"\n\t| \"skills\"\n\t| \"mcp\"\n\t| \"history\"\n\t| \"evidence\"\n\t| \"scratch\";\n\nexport type ContextBudgetPriorityV2 = \"hard\" | \"high\" | \"medium\" | \"low\";\n\nexport type ContextEvidenceKindV2 = \"test\" | \"command\" | \"file\" | \"decision\" | \"lane\" | \"user\" | \"none\";\n\nexport type ContextRepresentationKindV2 = \"full\" | \"pointer\" | \"summary\" | \"headroom-compressed\" | \"omit\";\n\nexport interface ContextSourceRefV2 {\n\treadonly uri: string;\n\treadonly symbol?: string;\n\treadonly range?: { readonly startLine: number; readonly endLine: number };\n\treadonly contentHash: string;\n\treadonly retrievable: boolean;\n}\n\nexport type ContextRepresentationFidelityV2 = \"exact\" | \"bounded\" | \"lossy\" | \"reversible\";\n\nexport interface ContextRepresentationCacheMetadataV2 {\n\treadonly hit: boolean;\n\treadonly key: string;\n\treadonly keyKind?: \"exact\" | \"materialized\";\n\treadonly layer?: \"turn\" | \"session\" | \"workspace\" | \"shared\";\n\treadonly rejectedReason?: string;\n}\n\nexport interface ContextRepresentationCandidateV2 {\n\treadonly kind: ContextRepresentationKindV2;\n\treadonly text: string;\n\treadonly estimatedTokens: number;\n\treadonly fidelity: ContextRepresentationFidelityV2;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly summaryHash?: string;\n\treadonly compressorId?: string;\n\treadonly cache?: ContextRepresentationCacheMetadataV2;\n}\n\nexport interface ContextBudgetItemV2 {\n\treadonly id: string;\n\treadonly tier: ContextBudgetTierV2;\n\treadonly priority: ContextBudgetPriorityV2;\n\treadonly text: string;\n\treadonly tokenEstimate?: number;\n\treadonly relevance?: number;\n\treadonly recency?: number;\n\treadonly evidenceValue?: number;\n\treadonly evidenceKind?: ContextEvidenceKindV2;\n\treadonly ageTurns?: number;\n\treadonly redundancyKey?: string;\n\treadonly required?: boolean;\n\treadonly pinReason?: string;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly representations?: readonly ContextRepresentationCandidateV2[];\n}\n\nexport interface HeadroomQualityPolicyV2 {\n\treadonly preferFullForHighPriority: boolean;\n\treadonly preferPointerForRetrievable: boolean;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly headroomThresholdTokens: number;\n\treadonly allowOmit: boolean;\n}\n\nexport const DEFAULT_HEADROOM_QUALITY_POLICY: HeadroomQualityPolicyV2 = {\n\tpreferFullForHighPriority: true,\n\tpreferPointerForRetrievable: true,\n\tsummaryMaxAgeTurns: 4,\n\theadroomThresholdTokens: 400,\n\tallowOmit: true,\n};\n\nexport interface HeadroomRuntimeStatusV2 {\n\treadonly policyId: string;\n\treadonly selector: string;\n\treadonly headroomThresholdTokens: number;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly representations: readonly ContextRepresentationKindV2[];\n}\n\nexport function getHeadroomRuntimeStatus(\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): HeadroomRuntimeStatusV2 {\n\treturn {\n\t\tpolicyId: \"context-budget-v2\",\n\t\tselector: \"headroom-selector\",\n\t\theadroomThresholdTokens: policy.headroomThresholdTokens,\n\t\tsummaryMaxAgeTurns: policy.summaryMaxAgeTurns,\n\t\trepresentations: [\"full\", \"pointer\", \"summary\", \"headroom-compressed\", \"omit\"],\n\t};\n}\n\nexport interface RepresentationBudgetContext {\n\treadonly tierUsedTokens: number;\n\treadonly tierCeilingTokens: number;\n\treadonly remainingGlobalTokens: number;\n}\n\nconst CHARS_PER_TOKEN_HEURISTIC = 4;\n\nexport function heuristicTokenCount(text: string): number {\n\tif (text.length === 0) {\n\t\treturn 0;\n\t}\n\treturn Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fullTextTokens(item: ContextBudgetItemV2): number {\n\tif (item.tokenEstimate !== undefined && Number.isFinite(item.tokenEstimate)) {\n\t\treturn Math.max(0, Math.floor(item.tokenEstimate));\n\t}\n\treturn heuristicTokenCount(item.text);\n}\n\nexport function estimatePointerTokens(ref: ContextSourceRefV2): number {\n\tlet total = ref.uri.length + (ref.contentHash?.length ?? 0) + 16;\n\tif (ref.symbol) total += ref.symbol.length;\n\tif (ref.range) total += 8;\n\treturn Math.max(8, Math.ceil(total / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fnv1aHex(input: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < input.length; i++) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193) >>> 0;\n\t}\n\treturn hash.toString(16).padStart(8, \"0\");\n}\n"]}
1
+ {"version":3,"file":"context-budget-headroom-types.js","sourceRoot":"","sources":["../../src/core/context-budget-headroom-types.ts"],"names":[],"mappings":"AA8EA,MAAM,CAAC,MAAM,+BAA+B,GAA4B;IACvE,2BAA2B,EAAE,IAAI;IACjC,kBAAkB,EAAE,CAAC;IACrB,uBAAuB,EAAE,GAAG;IAC5B,SAAS,EAAE,IAAI;CACf,CAAC;AAUF,MAAM,UAAU,wBAAwB,CACvC,MAAM,GAA4B,+BAA+B,EACvC;IAC1B,OAAO;QACN,QAAQ,EAAE,mBAAmB;QAC7B,QAAQ,EAAE,mBAAmB;QAC7B,uBAAuB,EAAE,MAAM,CAAC,uBAAuB;QACvD,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;QAC7C,eAAe,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,CAAC;KAC9E,CAAC;AAAA,CACF;AAQD,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAEpC,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAU;IACzD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,yBAAyB,CAAC,CAAC,CAAC;AAAA,CACvE;AAED,MAAM,UAAU,cAAc,CAAC,IAAyB,EAAU;IACjE,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACtC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAuB,EAAU;IACtE,IAAI,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;IACjE,IAAI,GAAG,CAAC,MAAM;QAAE,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;IAC3C,IAAI,GAAG,CAAC,KAAK;QAAE,KAAK,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,yBAAyB,CAAC,CAAC,CAAC;AAAA,CACjE;AAED,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAU;IAC/C,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAAA,CAC1C","sourcesContent":["export type ContextBudgetTierV2 =\n\t| \"system\"\n\t| \"active-goal\"\n\t| \"current-files\"\n\t| \"tools\"\n\t| \"skills\"\n\t| \"mcp\"\n\t| \"history\"\n\t| \"evidence\"\n\t| \"scratch\";\n\nexport type ContextBudgetPriorityV2 = \"hard\" | \"high\" | \"medium\" | \"low\";\n\nexport type ContextEvidenceKindV2 = \"test\" | \"command\" | \"file\" | \"decision\" | \"lane\" | \"user\" | \"none\";\n\nexport type ContextRepresentationKindV2 = \"full\" | \"pointer\" | \"summary\" | \"headroom-compressed\" | \"omit\";\n\nexport interface ContextSourceRefV2 {\n\treadonly uri: string;\n\treadonly symbol?: string;\n\treadonly range?: { readonly startLine: number; readonly endLine: number };\n\treadonly contentHash: string;\n\treadonly retrievable: boolean;\n}\n\nexport type ContextRepresentationFidelityV2 = \"exact\" | \"bounded\" | \"lossy\" | \"reversible\";\n\nexport interface ContextRepresentationCacheMetadataV2 {\n\treadonly hit: boolean;\n\treadonly key: string;\n\treadonly keyKind?: \"exact\" | \"materialized\";\n\treadonly layer?: \"turn\" | \"session\" | \"workspace\" | \"shared\";\n\treadonly rejectedReason?: string;\n}\n\nexport interface ContextRepresentationCandidateV2 {\n\treadonly kind: ContextRepresentationKindV2;\n\treadonly text: string;\n\treadonly estimatedTokens: number;\n\treadonly fidelity: ContextRepresentationFidelityV2;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly summaryHash?: string;\n\treadonly compressorId?: string;\n\treadonly cache?: ContextRepresentationCacheMetadataV2;\n}\n\nexport interface ContextBudgetItemV2 {\n\treadonly id: string;\n\treadonly tier: ContextBudgetTierV2;\n\treadonly priority: ContextBudgetPriorityV2;\n\treadonly text: string;\n\treadonly tokenEstimate?: number;\n\treadonly relevance?: number;\n\treadonly recency?: number;\n\treadonly evidenceValue?: number;\n\treadonly evidenceKind?: ContextEvidenceKindV2;\n\treadonly ageTurns?: number;\n\treadonly redundancyKey?: string;\n\treadonly required?: boolean;\n\treadonly pinReason?: string;\n\treadonly sourceRef?: ContextSourceRefV2;\n\treadonly representations?: readonly ContextRepresentationCandidateV2[];\n}\n\nexport interface HeadroomQualityPolicyV2 {\n\t/**\n\t * @deprecated Never read by the candidate generator or the selector; the\n\t * priority weight already prefers full text for high-priority items. Kept\n\t * optional so existing configs still type-check. Flipping it changes no\n\t * decision (see context-budget-quality-policy-semantics.test.ts).\n\t */\n\treadonly preferFullForHighPriority?: boolean;\n\treadonly preferPointerForRetrievable: boolean;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly headroomThresholdTokens: number;\n\treadonly allowOmit: boolean;\n}\n\nexport const DEFAULT_HEADROOM_QUALITY_POLICY: HeadroomQualityPolicyV2 = {\n\tpreferPointerForRetrievable: true,\n\tsummaryMaxAgeTurns: 4,\n\theadroomThresholdTokens: 400,\n\tallowOmit: true,\n};\n\nexport interface HeadroomRuntimeStatusV2 {\n\treadonly policyId: string;\n\treadonly selector: string;\n\treadonly headroomThresholdTokens: number;\n\treadonly summaryMaxAgeTurns: number;\n\treadonly representations: readonly ContextRepresentationKindV2[];\n}\n\nexport function getHeadroomRuntimeStatus(\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): HeadroomRuntimeStatusV2 {\n\treturn {\n\t\tpolicyId: \"context-budget-v2\",\n\t\tselector: \"headroom-selector\",\n\t\theadroomThresholdTokens: policy.headroomThresholdTokens,\n\t\tsummaryMaxAgeTurns: policy.summaryMaxAgeTurns,\n\t\trepresentations: [\"full\", \"pointer\", \"summary\", \"headroom-compressed\", \"omit\"],\n\t};\n}\n\nexport interface RepresentationBudgetContext {\n\treadonly tierUsedTokens: number;\n\treadonly tierCeilingTokens: number;\n\treadonly remainingGlobalTokens: number;\n}\n\nconst CHARS_PER_TOKEN_HEURISTIC = 4;\n\nexport function heuristicTokenCount(text: string): number {\n\tif (text.length === 0) {\n\t\treturn 0;\n\t}\n\treturn Math.max(1, Math.ceil(text.length / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fullTextTokens(item: ContextBudgetItemV2): number {\n\tif (item.tokenEstimate !== undefined && Number.isFinite(item.tokenEstimate)) {\n\t\treturn Math.max(0, Math.floor(item.tokenEstimate));\n\t}\n\treturn heuristicTokenCount(item.text);\n}\n\nexport function estimatePointerTokens(ref: ContextSourceRefV2): number {\n\tlet total = ref.uri.length + (ref.contentHash?.length ?? 0) + 16;\n\tif (ref.symbol) total += ref.symbol.length;\n\tif (ref.range) total += 8;\n\treturn Math.max(8, Math.ceil(total / CHARS_PER_TOKEN_HEURISTIC));\n}\n\nexport function fnv1aHex(input: string): string {\n\tlet hash = 0x811c9dc5;\n\tfor (let i = 0; i < input.length; i++) {\n\t\thash ^= input.charCodeAt(i);\n\t\thash = Math.imul(hash, 0x01000193) >>> 0;\n\t}\n\treturn hash.toString(16).padStart(8, \"0\");\n}\n"]}
@@ -12,6 +12,16 @@ import { type ContextBudgetItemV2, type ContextRepresentationCandidateV2, type H
12
12
  */
13
13
  export declare function isRepresentationEligible(candidate: ContextRepresentationCandidateV2, item: ContextBudgetItemV2, policy?: HeadroomQualityPolicyV2): boolean;
14
14
  export declare function chooseHeadroomRepresentation(item: ContextBudgetItemV2, budget: RepresentationBudgetContext, policy?: HeadroomQualityPolicyV2): ContextRepresentationCandidateV2;
15
+ /**
16
+ * The selector's own preference score for one representation — the closest
17
+ * thing this policy has to a utility function, and the quantity the planner's
18
+ * exchange passes compare when they promote or step an item down. Exported so
19
+ * an oracle can score a plan with the same rule the planner used instead of
20
+ * restating the formula. `tight` is the congestion flag
21
+ * {@link chooseHeadroomRepresentation} derives from the budget context; pass
22
+ * `false` for a congestion-free reference score.
23
+ */
24
+ export declare function scoreRepresentationPreferenceV2(candidate: ContextRepresentationCandidateV2, item: ContextBudgetItemV2, policy?: HeadroomQualityPolicyV2, tight?: boolean): number;
15
25
  export { deriveRepresentationCandidates } from "./context-budget-headroom-candidates.ts";
16
26
  export { type ContextBudgetItemV2, type ContextBudgetPriorityV2, type ContextBudgetTierV2, type ContextEvidenceKindV2, type ContextRepresentationCacheMetadataV2, type ContextRepresentationCandidateV2, type ContextRepresentationFidelityV2, type ContextRepresentationKindV2, type ContextSourceRefV2, DEFAULT_HEADROOM_QUALITY_POLICY, estimatePointerTokens, fnv1aHex, fullTextTokens, getHeadroomRuntimeStatus, type HeadroomQualityPolicyV2, type HeadroomRuntimeStatusV2, heuristicTokenCount, type RepresentationBudgetContext, } from "./context-budget-headroom-types.ts";
17
27
  //# sourceMappingURL=context-budget-headroom.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-budget-headroom.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-headroom.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,mBAAmB,EAExB,KAAK,gCAAgC,EAGrC,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,MAAM,oCAAoC,CAAC;AAqB5C;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACvC,SAAS,EAAE,gCAAgC,EAC3C,IAAI,EAAE,mBAAmB,EACzB,MAAM,GAAE,uBAAyD,GAC/D,OAAO,CAkBT;AAED,wBAAgB,4BAA4B,CAC3C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE,2BAA2B,EACnC,MAAM,GAAE,uBAAyD,GAC/D,gCAAgC,CA2ClC;AAgGD,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EACN,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,oCAAoC,EACzC,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,EACpC,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,+BAA+B,EAC/B,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,wBAAwB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,mBAAmB,EACnB,KAAK,2BAA2B,GAChC,MAAM,oCAAoC,CAAC","sourcesContent":["import { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nimport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextRepresentationCandidateV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\tfullTextTokens,\n\ttype HeadroomQualityPolicyV2,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n\nconst PRIORITY_WEIGHT: Record<ContextBudgetPriorityV2, number> = {\n\thard: 1_000_000,\n\thigh: 120,\n\tmedium: 60,\n\tlow: 15,\n};\n\nconst FIDELITY_BONUS: Record<ContextRepresentationCandidateV2[\"fidelity\"], number> = {\n\texact: 6,\n\tbounded: 3,\n\treversible: 4,\n\tlossy: 0,\n};\n\nconst CACHE_HIT_BONUS = 8;\nconst TIGHT_CACHE_HIT_BONUS = 20;\nconst MATERIALIZED_CACHE_HIT_BONUS = 8;\nconst TIGHT_MATERIALIZED_CACHE_HIT_BONUS = 35;\n\n/**\n * Budget-independent admissibility for one representation candidate — the\n * same gate {@link chooseHeadroomRepresentation} applies via its per-kind\n * rejections. Ranking and selection must share this set, otherwise a cheap\n * representation the chooser could never pick distorts the density\n * denominator (an item ranked on a 1-token pointer it can only use as a\n * 80-token full text is admitted ahead of genuinely denser items).\n *\n * Budget feasibility (`estimatedTokens <= spendable`) is deliberately NOT\n * here: it is a ranking-time property, not an admissibility property.\n */\nexport function isRepresentationEligible(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): boolean {\n\tif (candidate.kind === \"omit\") return policy.allowOmit;\n\t// A non-full representation that does not actually save tokens is never a\n\t// real choice; counting it as admissible only flatters the cost model.\n\tif (candidate.kind !== \"full\" && candidate.estimatedTokens >= fullTextTokens(item)) return false;\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tswitch (candidate.kind) {\n\t\tcase \"full\":\n\t\t\treturn true;\n\t\tcase \"pointer\":\n\t\t\treturn policy.preferPointerForRetrievable && retrievable;\n\t\tcase \"headroom-compressed\":\n\t\t\treturn retrievable;\n\t\tcase \"summary\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nexport function chooseHeadroomRepresentation(\n\titem: ContextBudgetItemV2,\n\tbudget: RepresentationBudgetContext,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): ContextRepresentationCandidateV2 {\n\tconst candidates = item.representations ?? deriveRepresentationCandidates(item, policy);\n\tconst full = fullTextTokens(item);\n\tconst tierOver = budget.tierUsedTokens >= budget.tierCeilingTokens;\n\tconst globalTight = budget.remainingGlobalTokens < full;\n\tconst tight = tierOver || globalTight;\n\t// A candidate must fit BOTH the global remainder and the tier remainder, so a\n\t// smaller representation is chosen instead of over-selecting full text and\n\t// having the caller drop the whole item at the tier ceiling.\n\tconst spendable = Math.min(\n\t\tbudget.remainingGlobalTokens,\n\t\tMath.max(0, budget.tierCeilingTokens - budget.tierUsedTokens),\n\t);\n\n\tif (item.priority === \"hard\" || item.required) {\n\t\tconst fullCandidate = candidates.find((candidate) => candidate.kind === \"full\");\n\t\tif (fullCandidate) {\n\t\t\treturn fullCandidate;\n\t\t}\n\t}\n\n\tlet best: ContextRepresentationCandidateV2 | undefined;\n\tlet bestScore = Number.NEGATIVE_INFINITY;\n\tfor (const candidate of candidates) {\n\t\tif (!isRepresentationEligible(candidate, item, policy)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isMoreExpensiveThanFull(candidate, full)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (candidate.kind !== \"omit\" && candidate.estimatedTokens > spendable) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst score = representationPreference(candidate, item, policy, tight);\n\t\tif (\n\t\t\tscore > bestScore ||\n\t\t\t(score === bestScore && best !== undefined && compareCandidatesForChoice(candidate, best) < 0)\n\t\t) {\n\t\t\tbest = candidate;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best ?? candidates.find((candidate) => candidate.kind === \"omit\") ?? omitCandidate(item);\n}\n\nfunction omitCandidate(item: ContextBudgetItemV2): ContextRepresentationCandidateV2 {\n\treturn {\n\t\tkind: \"omit\",\n\t\ttext: \"\",\n\t\testimatedTokens: 0,\n\t\tfidelity: \"lossy\",\n\t\tsourceRef: item.sourceRef,\n\t};\n}\n\nfunction isMoreExpensiveThanFull(candidate: ContextRepresentationCandidateV2, fullTokens: number): boolean {\n\treturn candidate.kind !== \"full\" && candidate.kind !== \"omit\" && candidate.estimatedTokens >= fullTokens;\n}\n\nfunction representationPreference(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2,\n\ttight: boolean,\n): number {\n\tconst priority = PRIORITY_WEIGHT[item.priority];\n\tconst cost = candidate.estimatedTokens;\n\tconst fidelity = FIDELITY_BONUS[candidate.fidelity];\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tconst cacheBonus =\n\t\tcandidate.cache?.hit === true\n\t\t\t? candidate.cache.keyKind === \"materialized\"\n\t\t\t\t? tight\n\t\t\t\t\t? TIGHT_MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t\t: MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t: tight\n\t\t\t\t\t? TIGHT_CACHE_HIT_BONUS\n\t\t\t\t\t: CACHE_HIT_BONUS\n\t\t\t: 0;\n\n\tswitch (candidate.kind) {\n\t\tcase \"full\": {\n\t\t\tlet score = priority + fidelity + cacheBonus - cost * 0.05;\n\t\t\tif (tight) score -= 50;\n\t\t\tif (isHistoryLike(item) && (item.ageTurns ?? 0) >= policy.summaryMaxAgeTurns) {\n\t\t\t\tscore -= 40;\n\t\t\t}\n\t\t\treturn score;\n\t\t}\n\t\tcase \"pointer\": {\n\t\t\tif (!policy.preferPointerForRetrievable || !retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tlet score = 40 + fidelity + cacheBonus - cost * 0.2;\n\t\t\tif (tight) score += 20;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"summary\": {\n\t\t\tconst base = isHistoryLike(item) ? 30 : 10;\n\t\t\tlet score = base + ageBonus(item) + fidelity + cacheBonus - cost * 0.3;\n\t\t\tif (tight) score += 15;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"headroom-compressed\": {\n\t\t\tif (!retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tconst large = fullTextTokens(item) > policy.headroomThresholdTokens ? 35 : 5;\n\t\t\tlet score = large + 10 + fidelity + cacheBonus - cost * 0.5;\n\t\t\tif (tight) score += 18;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"omit\": {\n\t\t\tif (!policy.allowOmit) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tif (tight && item.priority === \"low\" && retrievable) {\n\t\t\t\treturn 12;\n\t\t\t}\n\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t}\n\t}\n}\n\nfunction ageBonus(item: ContextBudgetItemV2): number {\n\treturn Math.min(20, (item.ageTurns ?? 0) * 3);\n}\n\nfunction isHistoryLike(item: ContextBudgetItemV2): boolean {\n\treturn item.tier === \"history\" || item.tier === \"evidence\" || item.tier === \"scratch\";\n}\n\nfunction compareCandidatesForChoice(a: ContextRepresentationCandidateV2, b: ContextRepresentationCandidateV2): number {\n\tif (a.estimatedTokens !== b.estimatedTokens) {\n\t\treturn a.estimatedTokens - b.estimatedTokens;\n\t}\n\treturn a.kind.localeCompare(b.kind);\n}\n\nexport { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nexport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextBudgetTierV2,\n\ttype ContextEvidenceKindV2,\n\ttype ContextRepresentationCacheMetadataV2,\n\ttype ContextRepresentationCandidateV2,\n\ttype ContextRepresentationFidelityV2,\n\ttype ContextRepresentationKindV2,\n\ttype ContextSourceRefV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\testimatePointerTokens,\n\tfnv1aHex,\n\tfullTextTokens,\n\tgetHeadroomRuntimeStatus,\n\ttype HeadroomQualityPolicyV2,\n\ttype HeadroomRuntimeStatusV2,\n\theuristicTokenCount,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n"]}
1
+ {"version":3,"file":"context-budget-headroom.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-headroom.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,mBAAmB,EAExB,KAAK,gCAAgC,EAGrC,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,MAAM,oCAAoC,CAAC;AAqB5C;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACvC,SAAS,EAAE,gCAAgC,EAC3C,IAAI,EAAE,mBAAmB,EACzB,MAAM,GAAE,uBAAyD,GAC/D,OAAO,CAkBT;AAED,wBAAgB,4BAA4B,CAC3C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE,2BAA2B,EACnC,MAAM,GAAE,uBAAyD,GAC/D,gCAAgC,CA2ClC;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC9C,SAAS,EAAE,gCAAgC,EAC3C,IAAI,EAAE,mBAAmB,EACzB,MAAM,GAAE,uBAAyD,EACjE,KAAK,UAAQ,GACX,MAAM,CA0DR;AAiBD,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EACN,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,oCAAoC,EACzC,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,EACpC,KAAK,2BAA2B,EAChC,KAAK,kBAAkB,EACvB,+BAA+B,EAC/B,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,wBAAwB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,mBAAmB,EACnB,KAAK,2BAA2B,GAChC,MAAM,oCAAoC,CAAC","sourcesContent":["import { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nimport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextRepresentationCandidateV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\tfullTextTokens,\n\ttype HeadroomQualityPolicyV2,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n\nconst PRIORITY_WEIGHT: Record<ContextBudgetPriorityV2, number> = {\n\thard: 1_000_000,\n\thigh: 120,\n\tmedium: 60,\n\tlow: 15,\n};\n\nconst FIDELITY_BONUS: Record<ContextRepresentationCandidateV2[\"fidelity\"], number> = {\n\texact: 6,\n\tbounded: 3,\n\treversible: 4,\n\tlossy: 0,\n};\n\nconst CACHE_HIT_BONUS = 8;\nconst TIGHT_CACHE_HIT_BONUS = 20;\nconst MATERIALIZED_CACHE_HIT_BONUS = 8;\nconst TIGHT_MATERIALIZED_CACHE_HIT_BONUS = 35;\n\n/**\n * Budget-independent admissibility for one representation candidate — the\n * same gate {@link chooseHeadroomRepresentation} applies via its per-kind\n * rejections. Ranking and selection must share this set, otherwise a cheap\n * representation the chooser could never pick distorts the density\n * denominator (an item ranked on a 1-token pointer it can only use as a\n * 80-token full text is admitted ahead of genuinely denser items).\n *\n * Budget feasibility (`estimatedTokens <= spendable`) is deliberately NOT\n * here: it is a ranking-time property, not an admissibility property.\n */\nexport function isRepresentationEligible(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): boolean {\n\tif (candidate.kind === \"omit\") return policy.allowOmit;\n\t// A non-full representation that does not actually save tokens is never a\n\t// real choice; counting it as admissible only flatters the cost model.\n\tif (candidate.kind !== \"full\" && candidate.estimatedTokens >= fullTextTokens(item)) return false;\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tswitch (candidate.kind) {\n\t\tcase \"full\":\n\t\t\treturn true;\n\t\tcase \"pointer\":\n\t\t\treturn policy.preferPointerForRetrievable && retrievable;\n\t\tcase \"headroom-compressed\":\n\t\t\treturn retrievable;\n\t\tcase \"summary\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nexport function chooseHeadroomRepresentation(\n\titem: ContextBudgetItemV2,\n\tbudget: RepresentationBudgetContext,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): ContextRepresentationCandidateV2 {\n\tconst candidates = item.representations ?? deriveRepresentationCandidates(item, policy);\n\tconst full = fullTextTokens(item);\n\tconst tierOver = budget.tierUsedTokens >= budget.tierCeilingTokens;\n\tconst globalTight = budget.remainingGlobalTokens < full;\n\tconst tight = tierOver || globalTight;\n\t// A candidate must fit BOTH the global remainder and the tier remainder, so a\n\t// smaller representation is chosen instead of over-selecting full text and\n\t// having the caller drop the whole item at the tier ceiling.\n\tconst spendable = Math.min(\n\t\tbudget.remainingGlobalTokens,\n\t\tMath.max(0, budget.tierCeilingTokens - budget.tierUsedTokens),\n\t);\n\n\tif (item.priority === \"hard\" || item.required) {\n\t\tconst fullCandidate = candidates.find((candidate) => candidate.kind === \"full\");\n\t\tif (fullCandidate) {\n\t\t\treturn fullCandidate;\n\t\t}\n\t}\n\n\tlet best: ContextRepresentationCandidateV2 | undefined;\n\tlet bestScore = Number.NEGATIVE_INFINITY;\n\tfor (const candidate of candidates) {\n\t\tif (!isRepresentationEligible(candidate, item, policy)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isMoreExpensiveThanFull(candidate, full)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (candidate.kind !== \"omit\" && candidate.estimatedTokens > spendable) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst score = scoreRepresentationPreferenceV2(candidate, item, policy, tight);\n\t\tif (\n\t\t\tscore > bestScore ||\n\t\t\t(score === bestScore && best !== undefined && compareCandidatesForChoice(candidate, best) < 0)\n\t\t) {\n\t\t\tbest = candidate;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best ?? candidates.find((candidate) => candidate.kind === \"omit\") ?? omitCandidate(item);\n}\n\nfunction omitCandidate(item: ContextBudgetItemV2): ContextRepresentationCandidateV2 {\n\treturn {\n\t\tkind: \"omit\",\n\t\ttext: \"\",\n\t\testimatedTokens: 0,\n\t\tfidelity: \"lossy\",\n\t\tsourceRef: item.sourceRef,\n\t};\n}\n\nfunction isMoreExpensiveThanFull(candidate: ContextRepresentationCandidateV2, fullTokens: number): boolean {\n\treturn candidate.kind !== \"full\" && candidate.kind !== \"omit\" && candidate.estimatedTokens >= fullTokens;\n}\n\n/**\n * The selector's own preference score for one representation — the closest\n * thing this policy has to a utility function, and the quantity the planner's\n * exchange passes compare when they promote or step an item down. Exported so\n * an oracle can score a plan with the same rule the planner used instead of\n * restating the formula. `tight` is the congestion flag\n * {@link chooseHeadroomRepresentation} derives from the budget context; pass\n * `false` for a congestion-free reference score.\n */\nexport function scoreRepresentationPreferenceV2(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n\ttight = false,\n): number {\n\tconst priority = PRIORITY_WEIGHT[item.priority];\n\tconst cost = candidate.estimatedTokens;\n\tconst fidelity = FIDELITY_BONUS[candidate.fidelity];\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tconst cacheBonus =\n\t\tcandidate.cache?.hit === true\n\t\t\t? candidate.cache.keyKind === \"materialized\"\n\t\t\t\t? tight\n\t\t\t\t\t? TIGHT_MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t\t: MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t: tight\n\t\t\t\t\t? TIGHT_CACHE_HIT_BONUS\n\t\t\t\t\t: CACHE_HIT_BONUS\n\t\t\t: 0;\n\n\tswitch (candidate.kind) {\n\t\tcase \"full\": {\n\t\t\tlet score = priority + fidelity + cacheBonus - cost * 0.05;\n\t\t\tif (tight) score -= 50;\n\t\t\tif (isHistoryLike(item) && (item.ageTurns ?? 0) >= policy.summaryMaxAgeTurns) {\n\t\t\t\tscore -= 40;\n\t\t\t}\n\t\t\treturn score;\n\t\t}\n\t\tcase \"pointer\": {\n\t\t\tif (!policy.preferPointerForRetrievable || !retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tlet score = 40 + fidelity + cacheBonus - cost * 0.2;\n\t\t\tif (tight) score += 20;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"summary\": {\n\t\t\tconst base = isHistoryLike(item) ? 30 : 10;\n\t\t\tlet score = base + ageBonus(item) + fidelity + cacheBonus - cost * 0.3;\n\t\t\tif (tight) score += 15;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"headroom-compressed\": {\n\t\t\tif (!retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tconst large = fullTextTokens(item) > policy.headroomThresholdTokens ? 35 : 5;\n\t\t\tlet score = large + 10 + fidelity + cacheBonus - cost * 0.5;\n\t\t\tif (tight) score += 18;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"omit\": {\n\t\t\tif (!policy.allowOmit) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tif (tight && item.priority === \"low\" && retrievable) {\n\t\t\t\treturn 12;\n\t\t\t}\n\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t}\n\t}\n}\n\nfunction ageBonus(item: ContextBudgetItemV2): number {\n\treturn Math.min(20, (item.ageTurns ?? 0) * 3);\n}\n\nfunction isHistoryLike(item: ContextBudgetItemV2): boolean {\n\treturn item.tier === \"history\" || item.tier === \"evidence\" || item.tier === \"scratch\";\n}\n\nfunction compareCandidatesForChoice(a: ContextRepresentationCandidateV2, b: ContextRepresentationCandidateV2): number {\n\tif (a.estimatedTokens !== b.estimatedTokens) {\n\t\treturn a.estimatedTokens - b.estimatedTokens;\n\t}\n\treturn a.kind.localeCompare(b.kind);\n}\n\nexport { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nexport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextBudgetTierV2,\n\ttype ContextEvidenceKindV2,\n\ttype ContextRepresentationCacheMetadataV2,\n\ttype ContextRepresentationCandidateV2,\n\ttype ContextRepresentationFidelityV2,\n\ttype ContextRepresentationKindV2,\n\ttype ContextSourceRefV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\testimatePointerTokens,\n\tfnv1aHex,\n\tfullTextTokens,\n\tgetHeadroomRuntimeStatus,\n\ttype HeadroomQualityPolicyV2,\n\ttype HeadroomRuntimeStatusV2,\n\theuristicTokenCount,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n"]}
@@ -76,7 +76,7 @@ export function chooseHeadroomRepresentation(item, budget, policy = DEFAULT_HEAD
76
76
  if (candidate.kind !== "omit" && candidate.estimatedTokens > spendable) {
77
77
  continue;
78
78
  }
79
- const score = representationPreference(candidate, item, policy, tight);
79
+ const score = scoreRepresentationPreferenceV2(candidate, item, policy, tight);
80
80
  if (score > bestScore ||
81
81
  (score === bestScore && best !== undefined && compareCandidatesForChoice(candidate, best) < 0)) {
82
82
  best = candidate;
@@ -97,7 +97,16 @@ function omitCandidate(item) {
97
97
  function isMoreExpensiveThanFull(candidate, fullTokens) {
98
98
  return candidate.kind !== "full" && candidate.kind !== "omit" && candidate.estimatedTokens >= fullTokens;
99
99
  }
100
- function representationPreference(candidate, item, policy, tight) {
100
+ /**
101
+ * The selector's own preference score for one representation — the closest
102
+ * thing this policy has to a utility function, and the quantity the planner's
103
+ * exchange passes compare when they promote or step an item down. Exported so
104
+ * an oracle can score a plan with the same rule the planner used instead of
105
+ * restating the formula. `tight` is the congestion flag
106
+ * {@link chooseHeadroomRepresentation} derives from the budget context; pass
107
+ * `false` for a congestion-free reference score.
108
+ */
109
+ export function scoreRepresentationPreferenceV2(candidate, item, policy = DEFAULT_HEADROOM_QUALITY_POLICY, tight = false) {
101
110
  const priority = PRIORITY_WEIGHT[item.priority];
102
111
  const cost = candidate.estimatedTokens;
103
112
  const fidelity = FIDELITY_BONUS[candidate.fidelity];
@@ -1 +1 @@
1
- {"version":3,"file":"context-budget-headroom.js","sourceRoot":"","sources":["../../src/core/context-budget-headroom.ts"],"names":[],"mappings":"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","sourcesContent":["import { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nimport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextRepresentationCandidateV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\tfullTextTokens,\n\ttype HeadroomQualityPolicyV2,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n\nconst PRIORITY_WEIGHT: Record<ContextBudgetPriorityV2, number> = {\n\thard: 1_000_000,\n\thigh: 120,\n\tmedium: 60,\n\tlow: 15,\n};\n\nconst FIDELITY_BONUS: Record<ContextRepresentationCandidateV2[\"fidelity\"], number> = {\n\texact: 6,\n\tbounded: 3,\n\treversible: 4,\n\tlossy: 0,\n};\n\nconst CACHE_HIT_BONUS = 8;\nconst TIGHT_CACHE_HIT_BONUS = 20;\nconst MATERIALIZED_CACHE_HIT_BONUS = 8;\nconst TIGHT_MATERIALIZED_CACHE_HIT_BONUS = 35;\n\n/**\n * Budget-independent admissibility for one representation candidate — the\n * same gate {@link chooseHeadroomRepresentation} applies via its per-kind\n * rejections. Ranking and selection must share this set, otherwise a cheap\n * representation the chooser could never pick distorts the density\n * denominator (an item ranked on a 1-token pointer it can only use as a\n * 80-token full text is admitted ahead of genuinely denser items).\n *\n * Budget feasibility (`estimatedTokens <= spendable`) is deliberately NOT\n * here: it is a ranking-time property, not an admissibility property.\n */\nexport function isRepresentationEligible(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): boolean {\n\tif (candidate.kind === \"omit\") return policy.allowOmit;\n\t// A non-full representation that does not actually save tokens is never a\n\t// real choice; counting it as admissible only flatters the cost model.\n\tif (candidate.kind !== \"full\" && candidate.estimatedTokens >= fullTextTokens(item)) return false;\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tswitch (candidate.kind) {\n\t\tcase \"full\":\n\t\t\treturn true;\n\t\tcase \"pointer\":\n\t\t\treturn policy.preferPointerForRetrievable && retrievable;\n\t\tcase \"headroom-compressed\":\n\t\t\treturn retrievable;\n\t\tcase \"summary\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nexport function chooseHeadroomRepresentation(\n\titem: ContextBudgetItemV2,\n\tbudget: RepresentationBudgetContext,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): ContextRepresentationCandidateV2 {\n\tconst candidates = item.representations ?? deriveRepresentationCandidates(item, policy);\n\tconst full = fullTextTokens(item);\n\tconst tierOver = budget.tierUsedTokens >= budget.tierCeilingTokens;\n\tconst globalTight = budget.remainingGlobalTokens < full;\n\tconst tight = tierOver || globalTight;\n\t// A candidate must fit BOTH the global remainder and the tier remainder, so a\n\t// smaller representation is chosen instead of over-selecting full text and\n\t// having the caller drop the whole item at the tier ceiling.\n\tconst spendable = Math.min(\n\t\tbudget.remainingGlobalTokens,\n\t\tMath.max(0, budget.tierCeilingTokens - budget.tierUsedTokens),\n\t);\n\n\tif (item.priority === \"hard\" || item.required) {\n\t\tconst fullCandidate = candidates.find((candidate) => candidate.kind === \"full\");\n\t\tif (fullCandidate) {\n\t\t\treturn fullCandidate;\n\t\t}\n\t}\n\n\tlet best: ContextRepresentationCandidateV2 | undefined;\n\tlet bestScore = Number.NEGATIVE_INFINITY;\n\tfor (const candidate of candidates) {\n\t\tif (!isRepresentationEligible(candidate, item, policy)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isMoreExpensiveThanFull(candidate, full)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (candidate.kind !== \"omit\" && candidate.estimatedTokens > spendable) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst score = representationPreference(candidate, item, policy, tight);\n\t\tif (\n\t\t\tscore > bestScore ||\n\t\t\t(score === bestScore && best !== undefined && compareCandidatesForChoice(candidate, best) < 0)\n\t\t) {\n\t\t\tbest = candidate;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best ?? candidates.find((candidate) => candidate.kind === \"omit\") ?? omitCandidate(item);\n}\n\nfunction omitCandidate(item: ContextBudgetItemV2): ContextRepresentationCandidateV2 {\n\treturn {\n\t\tkind: \"omit\",\n\t\ttext: \"\",\n\t\testimatedTokens: 0,\n\t\tfidelity: \"lossy\",\n\t\tsourceRef: item.sourceRef,\n\t};\n}\n\nfunction isMoreExpensiveThanFull(candidate: ContextRepresentationCandidateV2, fullTokens: number): boolean {\n\treturn candidate.kind !== \"full\" && candidate.kind !== \"omit\" && candidate.estimatedTokens >= fullTokens;\n}\n\nfunction representationPreference(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2,\n\ttight: boolean,\n): number {\n\tconst priority = PRIORITY_WEIGHT[item.priority];\n\tconst cost = candidate.estimatedTokens;\n\tconst fidelity = FIDELITY_BONUS[candidate.fidelity];\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tconst cacheBonus =\n\t\tcandidate.cache?.hit === true\n\t\t\t? candidate.cache.keyKind === \"materialized\"\n\t\t\t\t? tight\n\t\t\t\t\t? TIGHT_MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t\t: MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t: tight\n\t\t\t\t\t? TIGHT_CACHE_HIT_BONUS\n\t\t\t\t\t: CACHE_HIT_BONUS\n\t\t\t: 0;\n\n\tswitch (candidate.kind) {\n\t\tcase \"full\": {\n\t\t\tlet score = priority + fidelity + cacheBonus - cost * 0.05;\n\t\t\tif (tight) score -= 50;\n\t\t\tif (isHistoryLike(item) && (item.ageTurns ?? 0) >= policy.summaryMaxAgeTurns) {\n\t\t\t\tscore -= 40;\n\t\t\t}\n\t\t\treturn score;\n\t\t}\n\t\tcase \"pointer\": {\n\t\t\tif (!policy.preferPointerForRetrievable || !retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tlet score = 40 + fidelity + cacheBonus - cost * 0.2;\n\t\t\tif (tight) score += 20;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"summary\": {\n\t\t\tconst base = isHistoryLike(item) ? 30 : 10;\n\t\t\tlet score = base + ageBonus(item) + fidelity + cacheBonus - cost * 0.3;\n\t\t\tif (tight) score += 15;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"headroom-compressed\": {\n\t\t\tif (!retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tconst large = fullTextTokens(item) > policy.headroomThresholdTokens ? 35 : 5;\n\t\t\tlet score = large + 10 + fidelity + cacheBonus - cost * 0.5;\n\t\t\tif (tight) score += 18;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"omit\": {\n\t\t\tif (!policy.allowOmit) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tif (tight && item.priority === \"low\" && retrievable) {\n\t\t\t\treturn 12;\n\t\t\t}\n\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t}\n\t}\n}\n\nfunction ageBonus(item: ContextBudgetItemV2): number {\n\treturn Math.min(20, (item.ageTurns ?? 0) * 3);\n}\n\nfunction isHistoryLike(item: ContextBudgetItemV2): boolean {\n\treturn item.tier === \"history\" || item.tier === \"evidence\" || item.tier === \"scratch\";\n}\n\nfunction compareCandidatesForChoice(a: ContextRepresentationCandidateV2, b: ContextRepresentationCandidateV2): number {\n\tif (a.estimatedTokens !== b.estimatedTokens) {\n\t\treturn a.estimatedTokens - b.estimatedTokens;\n\t}\n\treturn a.kind.localeCompare(b.kind);\n}\n\nexport { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nexport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextBudgetTierV2,\n\ttype ContextEvidenceKindV2,\n\ttype ContextRepresentationCacheMetadataV2,\n\ttype ContextRepresentationCandidateV2,\n\ttype ContextRepresentationFidelityV2,\n\ttype ContextRepresentationKindV2,\n\ttype ContextSourceRefV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\testimatePointerTokens,\n\tfnv1aHex,\n\tfullTextTokens,\n\tgetHeadroomRuntimeStatus,\n\ttype HeadroomQualityPolicyV2,\n\ttype HeadroomRuntimeStatusV2,\n\theuristicTokenCount,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n"]}
1
+ {"version":3,"file":"context-budget-headroom.js","sourceRoot":"","sources":["../../src/core/context-budget-headroom.ts"],"names":[],"mappings":"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","sourcesContent":["import { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nimport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextRepresentationCandidateV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\tfullTextTokens,\n\ttype HeadroomQualityPolicyV2,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n\nconst PRIORITY_WEIGHT: Record<ContextBudgetPriorityV2, number> = {\n\thard: 1_000_000,\n\thigh: 120,\n\tmedium: 60,\n\tlow: 15,\n};\n\nconst FIDELITY_BONUS: Record<ContextRepresentationCandidateV2[\"fidelity\"], number> = {\n\texact: 6,\n\tbounded: 3,\n\treversible: 4,\n\tlossy: 0,\n};\n\nconst CACHE_HIT_BONUS = 8;\nconst TIGHT_CACHE_HIT_BONUS = 20;\nconst MATERIALIZED_CACHE_HIT_BONUS = 8;\nconst TIGHT_MATERIALIZED_CACHE_HIT_BONUS = 35;\n\n/**\n * Budget-independent admissibility for one representation candidate — the\n * same gate {@link chooseHeadroomRepresentation} applies via its per-kind\n * rejections. Ranking and selection must share this set, otherwise a cheap\n * representation the chooser could never pick distorts the density\n * denominator (an item ranked on a 1-token pointer it can only use as a\n * 80-token full text is admitted ahead of genuinely denser items).\n *\n * Budget feasibility (`estimatedTokens <= spendable`) is deliberately NOT\n * here: it is a ranking-time property, not an admissibility property.\n */\nexport function isRepresentationEligible(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): boolean {\n\tif (candidate.kind === \"omit\") return policy.allowOmit;\n\t// A non-full representation that does not actually save tokens is never a\n\t// real choice; counting it as admissible only flatters the cost model.\n\tif (candidate.kind !== \"full\" && candidate.estimatedTokens >= fullTextTokens(item)) return false;\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tswitch (candidate.kind) {\n\t\tcase \"full\":\n\t\t\treturn true;\n\t\tcase \"pointer\":\n\t\t\treturn policy.preferPointerForRetrievable && retrievable;\n\t\tcase \"headroom-compressed\":\n\t\t\treturn retrievable;\n\t\tcase \"summary\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nexport function chooseHeadroomRepresentation(\n\titem: ContextBudgetItemV2,\n\tbudget: RepresentationBudgetContext,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n): ContextRepresentationCandidateV2 {\n\tconst candidates = item.representations ?? deriveRepresentationCandidates(item, policy);\n\tconst full = fullTextTokens(item);\n\tconst tierOver = budget.tierUsedTokens >= budget.tierCeilingTokens;\n\tconst globalTight = budget.remainingGlobalTokens < full;\n\tconst tight = tierOver || globalTight;\n\t// A candidate must fit BOTH the global remainder and the tier remainder, so a\n\t// smaller representation is chosen instead of over-selecting full text and\n\t// having the caller drop the whole item at the tier ceiling.\n\tconst spendable = Math.min(\n\t\tbudget.remainingGlobalTokens,\n\t\tMath.max(0, budget.tierCeilingTokens - budget.tierUsedTokens),\n\t);\n\n\tif (item.priority === \"hard\" || item.required) {\n\t\tconst fullCandidate = candidates.find((candidate) => candidate.kind === \"full\");\n\t\tif (fullCandidate) {\n\t\t\treturn fullCandidate;\n\t\t}\n\t}\n\n\tlet best: ContextRepresentationCandidateV2 | undefined;\n\tlet bestScore = Number.NEGATIVE_INFINITY;\n\tfor (const candidate of candidates) {\n\t\tif (!isRepresentationEligible(candidate, item, policy)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isMoreExpensiveThanFull(candidate, full)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (candidate.kind !== \"omit\" && candidate.estimatedTokens > spendable) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst score = scoreRepresentationPreferenceV2(candidate, item, policy, tight);\n\t\tif (\n\t\t\tscore > bestScore ||\n\t\t\t(score === bestScore && best !== undefined && compareCandidatesForChoice(candidate, best) < 0)\n\t\t) {\n\t\t\tbest = candidate;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best ?? candidates.find((candidate) => candidate.kind === \"omit\") ?? omitCandidate(item);\n}\n\nfunction omitCandidate(item: ContextBudgetItemV2): ContextRepresentationCandidateV2 {\n\treturn {\n\t\tkind: \"omit\",\n\t\ttext: \"\",\n\t\testimatedTokens: 0,\n\t\tfidelity: \"lossy\",\n\t\tsourceRef: item.sourceRef,\n\t};\n}\n\nfunction isMoreExpensiveThanFull(candidate: ContextRepresentationCandidateV2, fullTokens: number): boolean {\n\treturn candidate.kind !== \"full\" && candidate.kind !== \"omit\" && candidate.estimatedTokens >= fullTokens;\n}\n\n/**\n * The selector's own preference score for one representation — the closest\n * thing this policy has to a utility function, and the quantity the planner's\n * exchange passes compare when they promote or step an item down. Exported so\n * an oracle can score a plan with the same rule the planner used instead of\n * restating the formula. `tight` is the congestion flag\n * {@link chooseHeadroomRepresentation} derives from the budget context; pass\n * `false` for a congestion-free reference score.\n */\nexport function scoreRepresentationPreferenceV2(\n\tcandidate: ContextRepresentationCandidateV2,\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 = DEFAULT_HEADROOM_QUALITY_POLICY,\n\ttight = false,\n): number {\n\tconst priority = PRIORITY_WEIGHT[item.priority];\n\tconst cost = candidate.estimatedTokens;\n\tconst fidelity = FIDELITY_BONUS[candidate.fidelity];\n\tconst retrievable = item.sourceRef?.retrievable === true;\n\tconst cacheBonus =\n\t\tcandidate.cache?.hit === true\n\t\t\t? candidate.cache.keyKind === \"materialized\"\n\t\t\t\t? tight\n\t\t\t\t\t? TIGHT_MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t\t: MATERIALIZED_CACHE_HIT_BONUS\n\t\t\t\t: tight\n\t\t\t\t\t? TIGHT_CACHE_HIT_BONUS\n\t\t\t\t\t: CACHE_HIT_BONUS\n\t\t\t: 0;\n\n\tswitch (candidate.kind) {\n\t\tcase \"full\": {\n\t\t\tlet score = priority + fidelity + cacheBonus - cost * 0.05;\n\t\t\tif (tight) score -= 50;\n\t\t\tif (isHistoryLike(item) && (item.ageTurns ?? 0) >= policy.summaryMaxAgeTurns) {\n\t\t\t\tscore -= 40;\n\t\t\t}\n\t\t\treturn score;\n\t\t}\n\t\tcase \"pointer\": {\n\t\t\tif (!policy.preferPointerForRetrievable || !retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tlet score = 40 + fidelity + cacheBonus - cost * 0.2;\n\t\t\tif (tight) score += 20;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"summary\": {\n\t\t\tconst base = isHistoryLike(item) ? 30 : 10;\n\t\t\tlet score = base + ageBonus(item) + fidelity + cacheBonus - cost * 0.3;\n\t\t\tif (tight) score += 15;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"headroom-compressed\": {\n\t\t\tif (!retrievable) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tconst large = fullTextTokens(item) > policy.headroomThresholdTokens ? 35 : 5;\n\t\t\tlet score = large + 10 + fidelity + cacheBonus - cost * 0.5;\n\t\t\tif (tight) score += 18;\n\t\t\treturn score;\n\t\t}\n\t\tcase \"omit\": {\n\t\t\tif (!policy.allowOmit) {\n\t\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t\t}\n\t\t\tif (tight && item.priority === \"low\" && retrievable) {\n\t\t\t\treturn 12;\n\t\t\t}\n\t\t\treturn Number.NEGATIVE_INFINITY;\n\t\t}\n\t}\n}\n\nfunction ageBonus(item: ContextBudgetItemV2): number {\n\treturn Math.min(20, (item.ageTurns ?? 0) * 3);\n}\n\nfunction isHistoryLike(item: ContextBudgetItemV2): boolean {\n\treturn item.tier === \"history\" || item.tier === \"evidence\" || item.tier === \"scratch\";\n}\n\nfunction compareCandidatesForChoice(a: ContextRepresentationCandidateV2, b: ContextRepresentationCandidateV2): number {\n\tif (a.estimatedTokens !== b.estimatedTokens) {\n\t\treturn a.estimatedTokens - b.estimatedTokens;\n\t}\n\treturn a.kind.localeCompare(b.kind);\n}\n\nexport { deriveRepresentationCandidates } from \"./context-budget-headroom-candidates.ts\";\nexport {\n\ttype ContextBudgetItemV2,\n\ttype ContextBudgetPriorityV2,\n\ttype ContextBudgetTierV2,\n\ttype ContextEvidenceKindV2,\n\ttype ContextRepresentationCacheMetadataV2,\n\ttype ContextRepresentationCandidateV2,\n\ttype ContextRepresentationFidelityV2,\n\ttype ContextRepresentationKindV2,\n\ttype ContextSourceRefV2,\n\tDEFAULT_HEADROOM_QUALITY_POLICY,\n\testimatePointerTokens,\n\tfnv1aHex,\n\tfullTextTokens,\n\tgetHeadroomRuntimeStatus,\n\ttype HeadroomQualityPolicyV2,\n\ttype HeadroomRuntimeStatusV2,\n\theuristicTokenCount,\n\ttype RepresentationBudgetContext,\n} from \"./context-budget-headroom-types.ts\";\n"]}
@@ -0,0 +1,21 @@
1
+ import type { PlannedItemV2 } from "./context-budget-v2-scoring.ts";
2
+ import { type OptionalSelectionState } from "./context-budget-v2-selection.ts";
3
+ /** Selection state after the floor-reservation passes; `usedTokens` is the running total so far. */
4
+ export type GlobalPassState = Omit<OptionalSelectionState, "floorReservation">;
5
+ export type PromotionState = Pick<GlobalPassState, "allocation" | "available" | "cache" | "qualityPolicy" | "resolvedCandidates" | "selection" | "tierUsed" | "usedTokens">;
6
+ /**
7
+ * Everything that happens once every tier has spent its floor:
8
+ *
9
+ * 1. the global pass admits still-unselected optional items in rank order
10
+ * under their tier ceilings;
11
+ * 2. the exchange pass lets a still-omitted item pay for its admission by
12
+ * stepping selected items down to their cheapest admissible form (F04);
13
+ * 3. the promotion pass spends whatever is left on costlier representations
14
+ * the policy prefers (F03).
15
+ *
16
+ * Breadth is settled before quality: an exchange that admits another item
17
+ * cannot be undone by a promotion, because promotion only spends the budget
18
+ * the exchange left. Returns the final token usage.
19
+ */
20
+ export declare function runGlobalSelectionPass(optionalPlanned: readonly PlannedItemV2[], state: GlobalPassState): number;
21
+ //# sourceMappingURL=context-budget-v2-global-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-budget-v2-global-pass.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-global-pass.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,KAAK,sBAAsB,EAAkC,MAAM,kCAAkC,CAAC;AAG/G,oGAAoG;AACpG,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,sBAAsB,EAAE,kBAAkB,CAAC,CAAC;AAE/E,MAAM,MAAM,cAAc,GAAG,IAAI,CAChC,eAAe,EACb,YAAY,GACZ,WAAW,GACX,OAAO,GACP,eAAe,GACf,oBAAoB,GACpB,WAAW,GACX,UAAU,GACV,YAAY,CACd,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,SAAS,aAAa,EAAE,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAQhH","sourcesContent":["import {\n\ttype ContextRepresentationCandidateV2,\n\tchooseHeadroomRepresentation,\n\tderiveRepresentationCandidates,\n\tisRepresentationEligible,\n\tscoreRepresentationPreferenceV2,\n} from \"./context-budget-headroom.ts\";\nimport { writeRepresentationCacheV2 } from \"./context-budget-v2-cache.ts\";\nimport type { PlannedItemV2 } from \"./context-budget-v2-scoring.ts\";\nimport { type OptionalSelectionState, selectOptionalItem, toSelected } from \"./context-budget-v2-selection.ts\";\nimport type { SelectedRepresentationV2 } from \"./context-budget-v2-types.ts\";\n\n/** Selection state after the floor-reservation passes; `usedTokens` is the running total so far. */\nexport type GlobalPassState = Omit<OptionalSelectionState, \"floorReservation\">;\n\nexport type PromotionState = Pick<\n\tGlobalPassState,\n\t| \"allocation\"\n\t| \"available\"\n\t| \"cache\"\n\t| \"qualityPolicy\"\n\t| \"resolvedCandidates\"\n\t| \"selection\"\n\t| \"tierUsed\"\n\t| \"usedTokens\"\n>;\n\n/**\n * Everything that happens once every tier has spent its floor:\n *\n * 1. the global pass admits still-unselected optional items in rank order\n * under their tier ceilings;\n * 2. the exchange pass lets a still-omitted item pay for its admission by\n * stepping selected items down to their cheapest admissible form (F04);\n * 3. the promotion pass spends whatever is left on costlier representations\n * the policy prefers (F03).\n *\n * Breadth is settled before quality: an exchange that admits another item\n * cannot be undone by a promotion, because promotion only spends the budget\n * the exchange left. Returns the final token usage.\n */\nexport function runGlobalSelectionPass(optionalPlanned: readonly PlannedItemV2[], state: GlobalPassState): number {\n\tlet usedTokens = state.usedTokens;\n\tfor (const planned of optionalPlanned) {\n\t\tif (state.selection.has(planned.item.id)) continue;\n\t\tusedTokens = selectOptionalItem(planned, { ...state, usedTokens });\n\t}\n\tusedTokens = exchangeForOmittedItems(optionalPlanned, { ...state, usedTokens });\n\treturn promoteSelectedRepresentations(optionalPlanned, { ...state, usedTokens });\n}\n\n/** Representations the planner priced for this item, in the order selection saw them. */\nfunction candidatesFor(planned: PlannedItemV2, state: PromotionState): readonly ContextRepresentationCandidateV2[] {\n\treturn (\n\t\tstate.resolvedCandidates?.get(planned.item.id) ??\n\t\tplanned.candidates ??\n\t\tplanned.item.representations ??\n\t\tderiveRepresentationCandidates(planned.item, state.qualityPolicy)\n\t);\n}\n\n/** Cheapest representation the policy would allow at all, ignoring budget. */\nfunction cheapestAdmissible(\n\tplanned: PlannedItemV2,\n\tstate: PromotionState,\n): ContextRepresentationCandidateV2 | undefined {\n\tlet best: ContextRepresentationCandidateV2 | undefined;\n\tfor (const candidate of candidatesFor(planned, state)) {\n\t\tif (candidate.kind === \"omit\") continue;\n\t\tif (!isRepresentationEligible(candidate, planned.item, state.qualityPolicy)) continue;\n\t\tif (best === undefined || candidate.estimatedTokens < best.estimatedTokens) best = candidate;\n\t}\n\treturn best;\n}\n\ninterface PlannedExchange {\n\treadonly swaps: ReadonlyArray<{\n\t\treadonly donor: PlannedItemV2;\n\t\treadonly from: SelectedRepresentationV2;\n\t\treadonly to: ContextRepresentationCandidateV2;\n\t}>;\n\treadonly freed: number;\n}\n\n/**\n * Plan the cheapest set of step-downs that makes room for `cost` tokens in\n * `target`'s tier, or `undefined` when no admissible set exists.\n *\n * Donors are ordered by the preference the policy loses per token freed, so\n * the item whose fidelity matters least to the policy yields first. A donor is\n * skipped when stepping it down would drop its tier below the floor it is\n * currently honouring: the floor is a reservation, not a budget to raid. An\n * empty swap list is legitimate — an earlier exchange may already have freed\n * enough for this item.\n */\nfunction planExchange(\n\ttarget: PlannedItemV2,\n\tcost: number,\n\toptionalPlanned: readonly PlannedItemV2[],\n\tstate: PromotionState,\n\tusedTokens: number,\n): PlannedExchange | undefined {\n\tconst ceilingOf = (tier: PlannedItemV2[\"item\"][\"tier\"]): number =>\n\t\tstate.allocation.get(tier)?.ceiling ?? state.available;\n\tconst floorOf = (tier: PlannedItemV2[\"item\"][\"tier\"]): number => state.allocation.get(tier)?.floor ?? 0;\n\tconst projected = new Map<string, number>();\n\tconst tierNow = (tier: PlannedItemV2[\"item\"][\"tier\"]): number => projected.get(tier) ?? state.tierUsed[tier];\n\n\tconst donors = [];\n\tfor (const donor of optionalPlanned) {\n\t\tif (donor.item.id === target.item.id) continue;\n\t\tconst current = state.selection.get(donor.item.id);\n\t\tif (current === undefined || current.kind === \"omit\") continue;\n\t\tconst cheap = cheapestAdmissible(donor, state);\n\t\tif (cheap === undefined || cheap.estimatedTokens >= current.estimatedTokens) continue;\n\t\tconst freed = current.estimatedTokens - cheap.estimatedTokens;\n\t\tconst from = toCandidate(current);\n\t\tconst loss =\n\t\t\tscoreRepresentationPreferenceV2(from, donor.item, state.qualityPolicy, false) -\n\t\t\tscoreRepresentationPreferenceV2(cheap, donor.item, state.qualityPolicy, false);\n\t\tdonors.push({ donor, current, cheap, freed, lossPerToken: loss / freed });\n\t}\n\tdonors.sort((a, b) => a.lossPerToken - b.lossPerToken || a.donor.item.id.localeCompare(b.donor.item.id));\n\n\tconst swaps: Array<PlannedExchange[\"swaps\"][number]> = [];\n\tlet freed = 0;\n\tconst fits = (): boolean =>\n\t\tcost <= state.available - (usedTokens - freed) && tierNow(target.item.tier) + cost <= ceilingOf(target.item.tier);\n\tfor (const entry of donors) {\n\t\tif (fits()) break;\n\t\tconst after = tierNow(entry.donor.item.tier) - entry.freed;\n\t\tif (tierNow(entry.donor.item.tier) >= floorOf(entry.donor.item.tier) && after < floorOf(entry.donor.item.tier)) {\n\t\t\tcontinue;\n\t\t}\n\t\tprojected.set(entry.donor.item.tier, after);\n\t\tswaps.push({ donor: entry.donor, from: entry.current, to: entry.cheap });\n\t\tfreed += entry.freed;\n\t}\n\treturn fits() ? { swaps, freed } : undefined;\n}\n\n/**\n * Admit still-omitted items by exchanging fidelity for coverage (audit F04).\n *\n * Ranking prices an item by its cheapest admissible representation but the\n * selector may admit it at full text, so one item ranked on a 10-token pointer\n * can spend 100 and displace two 45-token items the policy would rather keep.\n * After the global pass, each omitted item in rank order may buy its cheapest\n * admissible form by stepping selected items down to theirs. The whole exchange\n * is applied or none of it is; tier floors and ceilings hold throughout. This\n * is a bounded repair, not joint `(item, representation)` optimization.\n */\nfunction exchangeForOmittedItems(optionalPlanned: readonly PlannedItemV2[], state: GlobalPassState): number {\n\tlet usedTokens = state.usedTokens;\n\tfor (const target of optionalPlanned) {\n\t\tif (state.selection.has(target.item.id)) continue;\n\t\tconst admit = cheapestAdmissible(target, state);\n\t\tif (admit === undefined) continue;\n\t\tconst exchange = planExchange(target, admit.estimatedTokens, optionalPlanned, state, usedTokens);\n\t\tif (exchange === undefined) continue;\n\t\tfor (const swap of exchange.swaps) {\n\t\t\tstate.tierUsed[swap.donor.item.tier] += swap.to.estimatedTokens - swap.from.estimatedTokens;\n\t\t\tstate.selection.set(swap.donor.item.id, toSelected(swap.donor.item.id, swap.to));\n\t\t\twriteSelected(swap.donor, swap.to, state);\n\t\t}\n\t\tstate.tierUsed[target.item.tier] += admit.estimatedTokens;\n\t\tstate.selection.set(target.item.id, toSelected(target.item.id, admit));\n\t\twriteSelected(target, admit, state);\n\t\t// The global pass already recorded this item as omitted; an admitted item\n\t\t// must leave that list, or the plan reports it as both included and\n\t\t// omitted and bills its full text in omittedTokens.\n\t\tconst omittedIndex = state.omitted.findIndex((entry) => entry.id === target.item.id);\n\t\tif (omittedIndex >= 0) state.omitted.splice(omittedIndex, 1);\n\t\tusedTokens = usedTokens - exchange.freed + admit.estimatedTokens;\n\t}\n\treturn usedTokens;\n}\n\nfunction writeSelected(\n\tplanned: PlannedItemV2,\n\tselected: ContextRepresentationCandidateV2,\n\tstate: PromotionState,\n): void {\n\tif (!state.cache) return;\n\twriteRepresentationCacheV2({\n\t\tplanned,\n\t\tselected,\n\t\tcache: state.cache,\n\t\tmaterializedEnabled: planned.item.representations === undefined,\n\t});\n}\n\n/**\n * Re-offer each selected optional item its costlier representations against\n * the leftover budget (audit F03). The floor pass admits an item at whatever\n * fits the tier's remaining floor and the global pass then skips it by id,\n * so a pointer chosen under a small floor stayed a pointer while most of the\n * budget went unused. Runs once, in selection-rank order, after the global\n * pass; a swap happens only when the selection policy itself prefers the\n * costlier representation under the real remainder and it fits both the\n * tier ceiling and the global budget. It never demotes, never touches hard\n * items, and never spends beyond either cap. Returns the updated token usage.\n */\nfunction promoteSelectedRepresentations(optionalPlanned: readonly PlannedItemV2[], state: PromotionState): number {\n\tlet usedTokens = state.usedTokens;\n\tfor (const planned of optionalPlanned) {\n\t\tconst current = state.selection.get(planned.item.id);\n\t\tif (current === undefined || current.kind === \"omit\") continue;\n\t\tconst remaining = state.available - usedTokens;\n\t\tif (remaining <= 0) break;\n\t\tconst ceiling = state.allocation.get(planned.item.tier)?.ceiling ?? state.available;\n\t\tconst tierUsedWithout = state.tierUsed[planned.item.tier] - current.estimatedTokens;\n\t\tconst upgrades = candidatesFor(planned, state).filter(\n\t\t\t(candidate) =>\n\t\t\t\tcandidate.kind !== \"omit\" &&\n\t\t\t\tcandidate.estimatedTokens > current.estimatedTokens &&\n\t\t\t\tcandidate.estimatedTokens <= remaining + current.estimatedTokens &&\n\t\t\t\ttierUsedWithout + candidate.estimatedTokens <= ceiling,\n\t\t);\n\t\tif (upgrades.length === 0) continue;\n\t\t// Let the policy rank the current choice against every affordable upgrade\n\t\t// under the real leftover; only a strictly costlier winner is a promotion.\n\t\tconst chosen = chooseHeadroomRepresentation(\n\t\t\t{ ...planned.item, representations: [...upgrades, toCandidate(current)] },\n\t\t\t{\n\t\t\t\ttierUsedTokens: tierUsedWithout,\n\t\t\t\ttierCeilingTokens: ceiling,\n\t\t\t\tremainingGlobalTokens: remaining + current.estimatedTokens,\n\t\t\t},\n\t\t\tstate.qualityPolicy,\n\t\t);\n\t\tif (chosen.kind === \"omit\" || chosen.estimatedTokens <= current.estimatedTokens) continue;\n\t\twriteSelected(planned, chosen, state);\n\t\tstate.tierUsed[planned.item.tier] = tierUsedWithout + chosen.estimatedTokens;\n\t\tusedTokens += chosen.estimatedTokens - current.estimatedTokens;\n\t\tstate.selection.set(planned.item.id, toSelected(planned.item.id, chosen));\n\t}\n\treturn usedTokens;\n}\n\nfunction toCandidate(selected: SelectedRepresentationV2): ContextRepresentationCandidateV2 {\n\treturn {\n\t\tkind: selected.kind,\n\t\ttext: selected.text,\n\t\testimatedTokens: selected.estimatedTokens,\n\t\tfidelity: selected.fidelity,\n\t\tsourceRef: selected.sourceRef,\n\t\tsummaryHash: selected.summaryHash,\n\t\tcompressorId: selected.compressorId,\n\t\tcache: selected.cache,\n\t};\n}\n"]}
@@ -0,0 +1,203 @@
1
+ import { chooseHeadroomRepresentation, deriveRepresentationCandidates, isRepresentationEligible, scoreRepresentationPreferenceV2, } from "./context-budget-headroom.js";
2
+ import { writeRepresentationCacheV2 } from "./context-budget-v2-cache.js";
3
+ import { selectOptionalItem, toSelected } from "./context-budget-v2-selection.js";
4
+ /**
5
+ * Everything that happens once every tier has spent its floor:
6
+ *
7
+ * 1. the global pass admits still-unselected optional items in rank order
8
+ * under their tier ceilings;
9
+ * 2. the exchange pass lets a still-omitted item pay for its admission by
10
+ * stepping selected items down to their cheapest admissible form (F04);
11
+ * 3. the promotion pass spends whatever is left on costlier representations
12
+ * the policy prefers (F03).
13
+ *
14
+ * Breadth is settled before quality: an exchange that admits another item
15
+ * cannot be undone by a promotion, because promotion only spends the budget
16
+ * the exchange left. Returns the final token usage.
17
+ */
18
+ export function runGlobalSelectionPass(optionalPlanned, state) {
19
+ let usedTokens = state.usedTokens;
20
+ for (const planned of optionalPlanned) {
21
+ if (state.selection.has(planned.item.id))
22
+ continue;
23
+ usedTokens = selectOptionalItem(planned, { ...state, usedTokens });
24
+ }
25
+ usedTokens = exchangeForOmittedItems(optionalPlanned, { ...state, usedTokens });
26
+ return promoteSelectedRepresentations(optionalPlanned, { ...state, usedTokens });
27
+ }
28
+ /** Representations the planner priced for this item, in the order selection saw them. */
29
+ function candidatesFor(planned, state) {
30
+ return (state.resolvedCandidates?.get(planned.item.id) ??
31
+ planned.candidates ??
32
+ planned.item.representations ??
33
+ deriveRepresentationCandidates(planned.item, state.qualityPolicy));
34
+ }
35
+ /** Cheapest representation the policy would allow at all, ignoring budget. */
36
+ function cheapestAdmissible(planned, state) {
37
+ let best;
38
+ for (const candidate of candidatesFor(planned, state)) {
39
+ if (candidate.kind === "omit")
40
+ continue;
41
+ if (!isRepresentationEligible(candidate, planned.item, state.qualityPolicy))
42
+ continue;
43
+ if (best === undefined || candidate.estimatedTokens < best.estimatedTokens)
44
+ best = candidate;
45
+ }
46
+ return best;
47
+ }
48
+ /**
49
+ * Plan the cheapest set of step-downs that makes room for `cost` tokens in
50
+ * `target`'s tier, or `undefined` when no admissible set exists.
51
+ *
52
+ * Donors are ordered by the preference the policy loses per token freed, so
53
+ * the item whose fidelity matters least to the policy yields first. A donor is
54
+ * skipped when stepping it down would drop its tier below the floor it is
55
+ * currently honouring: the floor is a reservation, not a budget to raid. An
56
+ * empty swap list is legitimate — an earlier exchange may already have freed
57
+ * enough for this item.
58
+ */
59
+ function planExchange(target, cost, optionalPlanned, state, usedTokens) {
60
+ const ceilingOf = (tier) => state.allocation.get(tier)?.ceiling ?? state.available;
61
+ const floorOf = (tier) => state.allocation.get(tier)?.floor ?? 0;
62
+ const projected = new Map();
63
+ const tierNow = (tier) => projected.get(tier) ?? state.tierUsed[tier];
64
+ const donors = [];
65
+ for (const donor of optionalPlanned) {
66
+ if (donor.item.id === target.item.id)
67
+ continue;
68
+ const current = state.selection.get(donor.item.id);
69
+ if (current === undefined || current.kind === "omit")
70
+ continue;
71
+ const cheap = cheapestAdmissible(donor, state);
72
+ if (cheap === undefined || cheap.estimatedTokens >= current.estimatedTokens)
73
+ continue;
74
+ const freed = current.estimatedTokens - cheap.estimatedTokens;
75
+ const from = toCandidate(current);
76
+ const loss = scoreRepresentationPreferenceV2(from, donor.item, state.qualityPolicy, false) -
77
+ scoreRepresentationPreferenceV2(cheap, donor.item, state.qualityPolicy, false);
78
+ donors.push({ donor, current, cheap, freed, lossPerToken: loss / freed });
79
+ }
80
+ donors.sort((a, b) => a.lossPerToken - b.lossPerToken || a.donor.item.id.localeCompare(b.donor.item.id));
81
+ const swaps = [];
82
+ let freed = 0;
83
+ const fits = () => cost <= state.available - (usedTokens - freed) && tierNow(target.item.tier) + cost <= ceilingOf(target.item.tier);
84
+ for (const entry of donors) {
85
+ if (fits())
86
+ break;
87
+ const after = tierNow(entry.donor.item.tier) - entry.freed;
88
+ if (tierNow(entry.donor.item.tier) >= floorOf(entry.donor.item.tier) && after < floorOf(entry.donor.item.tier)) {
89
+ continue;
90
+ }
91
+ projected.set(entry.donor.item.tier, after);
92
+ swaps.push({ donor: entry.donor, from: entry.current, to: entry.cheap });
93
+ freed += entry.freed;
94
+ }
95
+ return fits() ? { swaps, freed } : undefined;
96
+ }
97
+ /**
98
+ * Admit still-omitted items by exchanging fidelity for coverage (audit F04).
99
+ *
100
+ * Ranking prices an item by its cheapest admissible representation but the
101
+ * selector may admit it at full text, so one item ranked on a 10-token pointer
102
+ * can spend 100 and displace two 45-token items the policy would rather keep.
103
+ * After the global pass, each omitted item in rank order may buy its cheapest
104
+ * admissible form by stepping selected items down to theirs. The whole exchange
105
+ * is applied or none of it is; tier floors and ceilings hold throughout. This
106
+ * is a bounded repair, not joint `(item, representation)` optimization.
107
+ */
108
+ function exchangeForOmittedItems(optionalPlanned, state) {
109
+ let usedTokens = state.usedTokens;
110
+ for (const target of optionalPlanned) {
111
+ if (state.selection.has(target.item.id))
112
+ continue;
113
+ const admit = cheapestAdmissible(target, state);
114
+ if (admit === undefined)
115
+ continue;
116
+ const exchange = planExchange(target, admit.estimatedTokens, optionalPlanned, state, usedTokens);
117
+ if (exchange === undefined)
118
+ continue;
119
+ for (const swap of exchange.swaps) {
120
+ state.tierUsed[swap.donor.item.tier] += swap.to.estimatedTokens - swap.from.estimatedTokens;
121
+ state.selection.set(swap.donor.item.id, toSelected(swap.donor.item.id, swap.to));
122
+ writeSelected(swap.donor, swap.to, state);
123
+ }
124
+ state.tierUsed[target.item.tier] += admit.estimatedTokens;
125
+ state.selection.set(target.item.id, toSelected(target.item.id, admit));
126
+ writeSelected(target, admit, state);
127
+ // The global pass already recorded this item as omitted; an admitted item
128
+ // must leave that list, or the plan reports it as both included and
129
+ // omitted and bills its full text in omittedTokens.
130
+ const omittedIndex = state.omitted.findIndex((entry) => entry.id === target.item.id);
131
+ if (omittedIndex >= 0)
132
+ state.omitted.splice(omittedIndex, 1);
133
+ usedTokens = usedTokens - exchange.freed + admit.estimatedTokens;
134
+ }
135
+ return usedTokens;
136
+ }
137
+ function writeSelected(planned, selected, state) {
138
+ if (!state.cache)
139
+ return;
140
+ writeRepresentationCacheV2({
141
+ planned,
142
+ selected,
143
+ cache: state.cache,
144
+ materializedEnabled: planned.item.representations === undefined,
145
+ });
146
+ }
147
+ /**
148
+ * Re-offer each selected optional item its costlier representations against
149
+ * the leftover budget (audit F03). The floor pass admits an item at whatever
150
+ * fits the tier's remaining floor and the global pass then skips it by id,
151
+ * so a pointer chosen under a small floor stayed a pointer while most of the
152
+ * budget went unused. Runs once, in selection-rank order, after the global
153
+ * pass; a swap happens only when the selection policy itself prefers the
154
+ * costlier representation under the real remainder and it fits both the
155
+ * tier ceiling and the global budget. It never demotes, never touches hard
156
+ * items, and never spends beyond either cap. Returns the updated token usage.
157
+ */
158
+ function promoteSelectedRepresentations(optionalPlanned, state) {
159
+ let usedTokens = state.usedTokens;
160
+ for (const planned of optionalPlanned) {
161
+ const current = state.selection.get(planned.item.id);
162
+ if (current === undefined || current.kind === "omit")
163
+ continue;
164
+ const remaining = state.available - usedTokens;
165
+ if (remaining <= 0)
166
+ break;
167
+ const ceiling = state.allocation.get(planned.item.tier)?.ceiling ?? state.available;
168
+ const tierUsedWithout = state.tierUsed[planned.item.tier] - current.estimatedTokens;
169
+ const upgrades = candidatesFor(planned, state).filter((candidate) => candidate.kind !== "omit" &&
170
+ candidate.estimatedTokens > current.estimatedTokens &&
171
+ candidate.estimatedTokens <= remaining + current.estimatedTokens &&
172
+ tierUsedWithout + candidate.estimatedTokens <= ceiling);
173
+ if (upgrades.length === 0)
174
+ continue;
175
+ // Let the policy rank the current choice against every affordable upgrade
176
+ // under the real leftover; only a strictly costlier winner is a promotion.
177
+ const chosen = chooseHeadroomRepresentation({ ...planned.item, representations: [...upgrades, toCandidate(current)] }, {
178
+ tierUsedTokens: tierUsedWithout,
179
+ tierCeilingTokens: ceiling,
180
+ remainingGlobalTokens: remaining + current.estimatedTokens,
181
+ }, state.qualityPolicy);
182
+ if (chosen.kind === "omit" || chosen.estimatedTokens <= current.estimatedTokens)
183
+ continue;
184
+ writeSelected(planned, chosen, state);
185
+ state.tierUsed[planned.item.tier] = tierUsedWithout + chosen.estimatedTokens;
186
+ usedTokens += chosen.estimatedTokens - current.estimatedTokens;
187
+ state.selection.set(planned.item.id, toSelected(planned.item.id, chosen));
188
+ }
189
+ return usedTokens;
190
+ }
191
+ function toCandidate(selected) {
192
+ return {
193
+ kind: selected.kind,
194
+ text: selected.text,
195
+ estimatedTokens: selected.estimatedTokens,
196
+ fidelity: selected.fidelity,
197
+ sourceRef: selected.sourceRef,
198
+ summaryHash: selected.summaryHash,
199
+ compressorId: selected.compressorId,
200
+ cache: selected.cache,
201
+ };
202
+ }
203
+ //# sourceMappingURL=context-budget-v2-global-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-budget-v2-global-pass.js","sourceRoot":"","sources":["../../src/core/context-budget-v2-global-pass.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype ContextRepresentationCandidateV2,\n\tchooseHeadroomRepresentation,\n\tderiveRepresentationCandidates,\n\tisRepresentationEligible,\n\tscoreRepresentationPreferenceV2,\n} from \"./context-budget-headroom.ts\";\nimport { writeRepresentationCacheV2 } from \"./context-budget-v2-cache.ts\";\nimport type { PlannedItemV2 } from \"./context-budget-v2-scoring.ts\";\nimport { type OptionalSelectionState, selectOptionalItem, toSelected } from \"./context-budget-v2-selection.ts\";\nimport type { SelectedRepresentationV2 } from \"./context-budget-v2-types.ts\";\n\n/** Selection state after the floor-reservation passes; `usedTokens` is the running total so far. */\nexport type GlobalPassState = Omit<OptionalSelectionState, \"floorReservation\">;\n\nexport type PromotionState = Pick<\n\tGlobalPassState,\n\t| \"allocation\"\n\t| \"available\"\n\t| \"cache\"\n\t| \"qualityPolicy\"\n\t| \"resolvedCandidates\"\n\t| \"selection\"\n\t| \"tierUsed\"\n\t| \"usedTokens\"\n>;\n\n/**\n * Everything that happens once every tier has spent its floor:\n *\n * 1. the global pass admits still-unselected optional items in rank order\n * under their tier ceilings;\n * 2. the exchange pass lets a still-omitted item pay for its admission by\n * stepping selected items down to their cheapest admissible form (F04);\n * 3. the promotion pass spends whatever is left on costlier representations\n * the policy prefers (F03).\n *\n * Breadth is settled before quality: an exchange that admits another item\n * cannot be undone by a promotion, because promotion only spends the budget\n * the exchange left. Returns the final token usage.\n */\nexport function runGlobalSelectionPass(optionalPlanned: readonly PlannedItemV2[], state: GlobalPassState): number {\n\tlet usedTokens = state.usedTokens;\n\tfor (const planned of optionalPlanned) {\n\t\tif (state.selection.has(planned.item.id)) continue;\n\t\tusedTokens = selectOptionalItem(planned, { ...state, usedTokens });\n\t}\n\tusedTokens = exchangeForOmittedItems(optionalPlanned, { ...state, usedTokens });\n\treturn promoteSelectedRepresentations(optionalPlanned, { ...state, usedTokens });\n}\n\n/** Representations the planner priced for this item, in the order selection saw them. */\nfunction candidatesFor(planned: PlannedItemV2, state: PromotionState): readonly ContextRepresentationCandidateV2[] {\n\treturn (\n\t\tstate.resolvedCandidates?.get(planned.item.id) ??\n\t\tplanned.candidates ??\n\t\tplanned.item.representations ??\n\t\tderiveRepresentationCandidates(planned.item, state.qualityPolicy)\n\t);\n}\n\n/** Cheapest representation the policy would allow at all, ignoring budget. */\nfunction cheapestAdmissible(\n\tplanned: PlannedItemV2,\n\tstate: PromotionState,\n): ContextRepresentationCandidateV2 | undefined {\n\tlet best: ContextRepresentationCandidateV2 | undefined;\n\tfor (const candidate of candidatesFor(planned, state)) {\n\t\tif (candidate.kind === \"omit\") continue;\n\t\tif (!isRepresentationEligible(candidate, planned.item, state.qualityPolicy)) continue;\n\t\tif (best === undefined || candidate.estimatedTokens < best.estimatedTokens) best = candidate;\n\t}\n\treturn best;\n}\n\ninterface PlannedExchange {\n\treadonly swaps: ReadonlyArray<{\n\t\treadonly donor: PlannedItemV2;\n\t\treadonly from: SelectedRepresentationV2;\n\t\treadonly to: ContextRepresentationCandidateV2;\n\t}>;\n\treadonly freed: number;\n}\n\n/**\n * Plan the cheapest set of step-downs that makes room for `cost` tokens in\n * `target`'s tier, or `undefined` when no admissible set exists.\n *\n * Donors are ordered by the preference the policy loses per token freed, so\n * the item whose fidelity matters least to the policy yields first. A donor is\n * skipped when stepping it down would drop its tier below the floor it is\n * currently honouring: the floor is a reservation, not a budget to raid. An\n * empty swap list is legitimate — an earlier exchange may already have freed\n * enough for this item.\n */\nfunction planExchange(\n\ttarget: PlannedItemV2,\n\tcost: number,\n\toptionalPlanned: readonly PlannedItemV2[],\n\tstate: PromotionState,\n\tusedTokens: number,\n): PlannedExchange | undefined {\n\tconst ceilingOf = (tier: PlannedItemV2[\"item\"][\"tier\"]): number =>\n\t\tstate.allocation.get(tier)?.ceiling ?? state.available;\n\tconst floorOf = (tier: PlannedItemV2[\"item\"][\"tier\"]): number => state.allocation.get(tier)?.floor ?? 0;\n\tconst projected = new Map<string, number>();\n\tconst tierNow = (tier: PlannedItemV2[\"item\"][\"tier\"]): number => projected.get(tier) ?? state.tierUsed[tier];\n\n\tconst donors = [];\n\tfor (const donor of optionalPlanned) {\n\t\tif (donor.item.id === target.item.id) continue;\n\t\tconst current = state.selection.get(donor.item.id);\n\t\tif (current === undefined || current.kind === \"omit\") continue;\n\t\tconst cheap = cheapestAdmissible(donor, state);\n\t\tif (cheap === undefined || cheap.estimatedTokens >= current.estimatedTokens) continue;\n\t\tconst freed = current.estimatedTokens - cheap.estimatedTokens;\n\t\tconst from = toCandidate(current);\n\t\tconst loss =\n\t\t\tscoreRepresentationPreferenceV2(from, donor.item, state.qualityPolicy, false) -\n\t\t\tscoreRepresentationPreferenceV2(cheap, donor.item, state.qualityPolicy, false);\n\t\tdonors.push({ donor, current, cheap, freed, lossPerToken: loss / freed });\n\t}\n\tdonors.sort((a, b) => a.lossPerToken - b.lossPerToken || a.donor.item.id.localeCompare(b.donor.item.id));\n\n\tconst swaps: Array<PlannedExchange[\"swaps\"][number]> = [];\n\tlet freed = 0;\n\tconst fits = (): boolean =>\n\t\tcost <= state.available - (usedTokens - freed) && tierNow(target.item.tier) + cost <= ceilingOf(target.item.tier);\n\tfor (const entry of donors) {\n\t\tif (fits()) break;\n\t\tconst after = tierNow(entry.donor.item.tier) - entry.freed;\n\t\tif (tierNow(entry.donor.item.tier) >= floorOf(entry.donor.item.tier) && after < floorOf(entry.donor.item.tier)) {\n\t\t\tcontinue;\n\t\t}\n\t\tprojected.set(entry.donor.item.tier, after);\n\t\tswaps.push({ donor: entry.donor, from: entry.current, to: entry.cheap });\n\t\tfreed += entry.freed;\n\t}\n\treturn fits() ? { swaps, freed } : undefined;\n}\n\n/**\n * Admit still-omitted items by exchanging fidelity for coverage (audit F04).\n *\n * Ranking prices an item by its cheapest admissible representation but the\n * selector may admit it at full text, so one item ranked on a 10-token pointer\n * can spend 100 and displace two 45-token items the policy would rather keep.\n * After the global pass, each omitted item in rank order may buy its cheapest\n * admissible form by stepping selected items down to theirs. The whole exchange\n * is applied or none of it is; tier floors and ceilings hold throughout. This\n * is a bounded repair, not joint `(item, representation)` optimization.\n */\nfunction exchangeForOmittedItems(optionalPlanned: readonly PlannedItemV2[], state: GlobalPassState): number {\n\tlet usedTokens = state.usedTokens;\n\tfor (const target of optionalPlanned) {\n\t\tif (state.selection.has(target.item.id)) continue;\n\t\tconst admit = cheapestAdmissible(target, state);\n\t\tif (admit === undefined) continue;\n\t\tconst exchange = planExchange(target, admit.estimatedTokens, optionalPlanned, state, usedTokens);\n\t\tif (exchange === undefined) continue;\n\t\tfor (const swap of exchange.swaps) {\n\t\t\tstate.tierUsed[swap.donor.item.tier] += swap.to.estimatedTokens - swap.from.estimatedTokens;\n\t\t\tstate.selection.set(swap.donor.item.id, toSelected(swap.donor.item.id, swap.to));\n\t\t\twriteSelected(swap.donor, swap.to, state);\n\t\t}\n\t\tstate.tierUsed[target.item.tier] += admit.estimatedTokens;\n\t\tstate.selection.set(target.item.id, toSelected(target.item.id, admit));\n\t\twriteSelected(target, admit, state);\n\t\t// The global pass already recorded this item as omitted; an admitted item\n\t\t// must leave that list, or the plan reports it as both included and\n\t\t// omitted and bills its full text in omittedTokens.\n\t\tconst omittedIndex = state.omitted.findIndex((entry) => entry.id === target.item.id);\n\t\tif (omittedIndex >= 0) state.omitted.splice(omittedIndex, 1);\n\t\tusedTokens = usedTokens - exchange.freed + admit.estimatedTokens;\n\t}\n\treturn usedTokens;\n}\n\nfunction writeSelected(\n\tplanned: PlannedItemV2,\n\tselected: ContextRepresentationCandidateV2,\n\tstate: PromotionState,\n): void {\n\tif (!state.cache) return;\n\twriteRepresentationCacheV2({\n\t\tplanned,\n\t\tselected,\n\t\tcache: state.cache,\n\t\tmaterializedEnabled: planned.item.representations === undefined,\n\t});\n}\n\n/**\n * Re-offer each selected optional item its costlier representations against\n * the leftover budget (audit F03). The floor pass admits an item at whatever\n * fits the tier's remaining floor and the global pass then skips it by id,\n * so a pointer chosen under a small floor stayed a pointer while most of the\n * budget went unused. Runs once, in selection-rank order, after the global\n * pass; a swap happens only when the selection policy itself prefers the\n * costlier representation under the real remainder and it fits both the\n * tier ceiling and the global budget. It never demotes, never touches hard\n * items, and never spends beyond either cap. Returns the updated token usage.\n */\nfunction promoteSelectedRepresentations(optionalPlanned: readonly PlannedItemV2[], state: PromotionState): number {\n\tlet usedTokens = state.usedTokens;\n\tfor (const planned of optionalPlanned) {\n\t\tconst current = state.selection.get(planned.item.id);\n\t\tif (current === undefined || current.kind === \"omit\") continue;\n\t\tconst remaining = state.available - usedTokens;\n\t\tif (remaining <= 0) break;\n\t\tconst ceiling = state.allocation.get(planned.item.tier)?.ceiling ?? state.available;\n\t\tconst tierUsedWithout = state.tierUsed[planned.item.tier] - current.estimatedTokens;\n\t\tconst upgrades = candidatesFor(planned, state).filter(\n\t\t\t(candidate) =>\n\t\t\t\tcandidate.kind !== \"omit\" &&\n\t\t\t\tcandidate.estimatedTokens > current.estimatedTokens &&\n\t\t\t\tcandidate.estimatedTokens <= remaining + current.estimatedTokens &&\n\t\t\t\ttierUsedWithout + candidate.estimatedTokens <= ceiling,\n\t\t);\n\t\tif (upgrades.length === 0) continue;\n\t\t// Let the policy rank the current choice against every affordable upgrade\n\t\t// under the real leftover; only a strictly costlier winner is a promotion.\n\t\tconst chosen = chooseHeadroomRepresentation(\n\t\t\t{ ...planned.item, representations: [...upgrades, toCandidate(current)] },\n\t\t\t{\n\t\t\t\ttierUsedTokens: tierUsedWithout,\n\t\t\t\ttierCeilingTokens: ceiling,\n\t\t\t\tremainingGlobalTokens: remaining + current.estimatedTokens,\n\t\t\t},\n\t\t\tstate.qualityPolicy,\n\t\t);\n\t\tif (chosen.kind === \"omit\" || chosen.estimatedTokens <= current.estimatedTokens) continue;\n\t\twriteSelected(planned, chosen, state);\n\t\tstate.tierUsed[planned.item.tier] = tierUsedWithout + chosen.estimatedTokens;\n\t\tusedTokens += chosen.estimatedTokens - current.estimatedTokens;\n\t\tstate.selection.set(planned.item.id, toSelected(planned.item.id, chosen));\n\t}\n\treturn usedTokens;\n}\n\nfunction toCandidate(selected: SelectedRepresentationV2): ContextRepresentationCandidateV2 {\n\treturn {\n\t\tkind: selected.kind,\n\t\ttext: selected.text,\n\t\testimatedTokens: selected.estimatedTokens,\n\t\tfidelity: selected.fidelity,\n\t\tsourceRef: selected.sourceRef,\n\t\tsummaryHash: selected.summaryHash,\n\t\tcompressorId: selected.compressorId,\n\t\tcache: selected.cache,\n\t};\n}\n"]}
@@ -1,5 +1,17 @@
1
1
  import { type ContextBudgetItemV2, deriveRepresentationCandidates } from "./context-budget-headroom.ts";
2
2
  import { type TokenCounterAdapter } from "./context-budget-token-counter.ts";
3
3
  import { type PlannedItemV2 } from "./context-budget-v2-scoring.ts";
4
+ /**
5
+ * Identity of the adapter that actually prices this run's text.
6
+ *
7
+ * Every representation price now depends on the planner's counter (audit F01),
8
+ * but the materialized representation key is bucketed rather than exact, so the
9
+ * static `heuristic-v1` default let two counters share one key space: a run
10
+ * could admit text at a price its own counter never produced, and the entry's
11
+ * `tokenizer_mismatch` check compared that constant against itself. Probe a
12
+ * non-empty string, because `countText("")` short-circuits to the fallback
13
+ * estimator in the registry and would report the wrong adapter.
14
+ */
15
+ export declare function resolveEffectiveTokenizerIdV2(tokenCounter: TokenCounterAdapter | undefined, modelId: string): string;
4
16
  export declare function createPlannedItems(items: readonly ContextBudgetItemV2[], tokenCounter: TokenCounterAdapter | undefined, modelId: string, qualityPolicy?: Parameters<typeof deriveRepresentationCandidates>[1]): PlannedItemV2[];
5
17
  //# sourceMappingURL=context-budget-v2-planned-items.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-budget-v2-planned-items.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-planned-items.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,mBAAmB,EAExB,8BAA8B,EAI9B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAA8B,KAAK,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAEzG,OAAO,EAAE,KAAK,aAAa,EAA4B,MAAM,gCAAgC,CAAC;AAwB9F,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,YAAY,EAAE,mBAAmB,GAAG,SAAS,EAC7C,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC,CAAC,GAClE,aAAa,EAAE,CAsBjB","sourcesContent":["import {\n\ttype ContextBudgetItemV2,\n\ttype ContextRepresentationCandidateV2,\n\tderiveRepresentationCandidates,\n\tfullTextTokens,\n\ttype HeadroomQualityPolicyV2,\n\tisRepresentationEligible,\n} from \"./context-budget-headroom.ts\";\nimport { createFallbackTokenCounter, type TokenCounterAdapter } from \"./context-budget-token-counter.ts\";\nimport { contentHashOf } from \"./context-budget-v2-plan-hash.ts\";\nimport { type PlannedItemV2, scoreContextBudgetItemV2 } from \"./context-budget-v2-scoring.ts\";\n\n// Cheapest representation the chooser could actually pick: only candidates\n// passing the selection policy gate count, so a pointer the chooser would\n// reject cannot make an item look cheaper to admit than it is.\nfunction minAdmissibleTokens(\n\tcandidates: readonly ContextRepresentationCandidateV2[],\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 | undefined,\n\tfullTokens: number,\n): number {\n\tlet min = Number.POSITIVE_INFINITY;\n\tfor (const candidate of candidates) {\n\t\tif (\n\t\t\tcandidate.kind !== \"omit\" &&\n\t\t\tisRepresentationEligible(candidate, item, policy) &&\n\t\t\tcandidate.estimatedTokens < min\n\t\t) {\n\t\t\tmin = candidate.estimatedTokens;\n\t\t}\n\t}\n\treturn Math.max(1, Number.isFinite(min) ? min : fullTokens);\n}\n\nexport function createPlannedItems(\n\titems: readonly ContextBudgetItemV2[],\n\ttokenCounter: TokenCounterAdapter | undefined,\n\tmodelId: string,\n\tqualityPolicy?: Parameters<typeof deriveRepresentationCandidates>[1],\n): PlannedItemV2[] {\n\tconst counter = tokenCounter ?? createFallbackTokenCounter();\n\treturn items.map((item) => {\n\t\tconst overrideTokenEstimate = item.tokenEstimate ?? counter.countText(item.text, modelId).tokens;\n\t\tconst itemWithTokens =\n\t\t\titem.tokenEstimate === undefined ? { ...item, tokenEstimate: overrideTokenEstimate } : item;\n\t\tconst fullTokens = fullTextTokens(itemWithTokens);\n\t\tconst isHard = item.priority === \"hard\" || item.required === true;\n\t\tconst baseScore = isHard ? Number.POSITIVE_INFINITY : scoreContextBudgetItemV2(itemWithTokens, fullTokens);\n\t\tconst candidates =\n\t\t\titemWithTokens.representations ?? deriveRepresentationCandidates(itemWithTokens, qualityPolicy);\n\t\treturn {\n\t\t\titem: itemWithTokens,\n\t\t\tfullTokens,\n\t\t\tadmissibleTokens: minAdmissibleTokens(candidates, itemWithTokens, qualityPolicy, fullTokens),\n\t\t\tcontentHash: contentHashOf(item.text),\n\t\t\tbaseScore,\n\t\t\tredundancyPenalty: 0,\n\t\t\teffectiveScore: baseScore,\n\t\t\tisHard,\n\t\t};\n\t});\n}\n"]}
1
+ {"version":3,"file":"context-budget-v2-planned-items.d.ts","sourceRoot":"","sources":["../../src/core/context-budget-v2-planned-items.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,mBAAmB,EAExB,8BAA8B,EAI9B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAA8B,KAAK,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAEzG,OAAO,EAAE,KAAK,aAAa,EAA4B,MAAM,gCAAgC,CAAC;AAwB9F;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,mBAAmB,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpH;AAED,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,YAAY,EAAE,mBAAmB,GAAG,SAAS,EAC7C,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC,CAAC,GAClE,aAAa,EAAE,CA2BjB","sourcesContent":["import {\n\ttype ContextBudgetItemV2,\n\ttype ContextRepresentationCandidateV2,\n\tderiveRepresentationCandidates,\n\tfullTextTokens,\n\ttype HeadroomQualityPolicyV2,\n\tisRepresentationEligible,\n} from \"./context-budget-headroom.ts\";\nimport { createFallbackTokenCounter, type TokenCounterAdapter } from \"./context-budget-token-counter.ts\";\nimport { contentHashOf } from \"./context-budget-v2-plan-hash.ts\";\nimport { type PlannedItemV2, scoreContextBudgetItemV2 } from \"./context-budget-v2-scoring.ts\";\n\n// Cheapest representation the chooser could actually pick: only candidates\n// passing the selection policy gate count, so a pointer the chooser would\n// reject cannot make an item look cheaper to admit than it is.\nfunction minAdmissibleTokens(\n\tcandidates: readonly ContextRepresentationCandidateV2[],\n\titem: ContextBudgetItemV2,\n\tpolicy: HeadroomQualityPolicyV2 | undefined,\n\tfullTokens: number,\n): number {\n\tlet min = Number.POSITIVE_INFINITY;\n\tfor (const candidate of candidates) {\n\t\tif (\n\t\t\tcandidate.kind !== \"omit\" &&\n\t\t\tisRepresentationEligible(candidate, item, policy) &&\n\t\t\tcandidate.estimatedTokens < min\n\t\t) {\n\t\t\tmin = candidate.estimatedTokens;\n\t\t}\n\t}\n\treturn Math.max(1, Number.isFinite(min) ? min : fullTokens);\n}\n\n/**\n * Identity of the adapter that actually prices this run's text.\n *\n * Every representation price now depends on the planner's counter (audit F01),\n * but the materialized representation key is bucketed rather than exact, so the\n * static `heuristic-v1` default let two counters share one key space: a run\n * could admit text at a price its own counter never produced, and the entry's\n * `tokenizer_mismatch` check compared that constant against itself. Probe a\n * non-empty string, because `countText(\"\")` short-circuits to the fallback\n * estimator in the registry and would report the wrong adapter.\n */\nexport function resolveEffectiveTokenizerIdV2(tokenCounter: TokenCounterAdapter | undefined, modelId: string): string {\n\treturn (tokenCounter ?? createFallbackTokenCounter()).countText(\" \", modelId).adapterId;\n}\n\nexport function createPlannedItems(\n\titems: readonly ContextBudgetItemV2[],\n\ttokenCounter: TokenCounterAdapter | undefined,\n\tmodelId: string,\n\tqualityPolicy?: Parameters<typeof deriveRepresentationCandidates>[1],\n): PlannedItemV2[] {\n\tconst counter = tokenCounter ?? createFallbackTokenCounter();\n\t// One counter prices the full text and every derived representation, so\n\t// the selector compares like with like (audit F01: a ratio-priced summary\n\t// was admitted into a budget its materialized text could not fit).\n\tconst countTokens = (text: string): number => counter.countText(text, modelId).tokens;\n\treturn items.map((item) => {\n\t\tconst overrideTokenEstimate = item.tokenEstimate ?? countTokens(item.text);\n\t\tconst itemWithTokens =\n\t\t\titem.tokenEstimate === undefined ? { ...item, tokenEstimate: overrideTokenEstimate } : item;\n\t\tconst fullTokens = fullTextTokens(itemWithTokens);\n\t\tconst isHard = item.priority === \"hard\" || item.required === true;\n\t\tconst baseScore = isHard ? Number.POSITIVE_INFINITY : scoreContextBudgetItemV2(itemWithTokens, fullTokens);\n\t\tconst candidates =\n\t\t\titemWithTokens.representations ?? deriveRepresentationCandidates(itemWithTokens, qualityPolicy, countTokens);\n\t\treturn {\n\t\t\titem: itemWithTokens,\n\t\t\tfullTokens,\n\t\t\tadmissibleTokens: minAdmissibleTokens(candidates, itemWithTokens, qualityPolicy, fullTokens),\n\t\t\tcandidates,\n\t\t\tcontentHash: contentHashOf(item.text),\n\t\t\tbaseScore,\n\t\t\tredundancyPenalty: 0,\n\t\t\teffectiveScore: baseScore,\n\t\t\tisHard,\n\t\t};\n\t});\n}\n"]}