@remnic/core 10.0.25 → 10.0.26

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 (235) hide show
  1. package/dist/access-admin-ops-surface.js +27 -26
  2. package/dist/access-authorization-probe.js +28 -27
  3. package/dist/access-boundary.js +27 -26
  4. package/dist/access-cli.js +84 -83
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-extraction-force-flush.js +27 -26
  7. package/dist/access-http-briefing.js +28 -27
  8. package/dist/access-http-query.js +29 -28
  9. package/dist/access-http.js +46 -45
  10. package/dist/access-identity-continuity-surface.js +27 -26
  11. package/dist/access-lcm-surface.js +27 -26
  12. package/dist/access-mcp.js +38 -37
  13. package/dist/access-namespace-preflight.js +27 -26
  14. package/dist/access-observe-write-category.js +4 -4
  15. package/dist/access-observe-write-surface.js +27 -26
  16. package/dist/access-operations-batch.js +28 -27
  17. package/dist/access-operations.js +35 -34
  18. package/dist/access-recall-concurrency.js +27 -26
  19. package/dist/access-recall-diagnostics.js +2 -2
  20. package/dist/access-recall-response.js +27 -26
  21. package/dist/access-recall-surface.js +27 -26
  22. package/dist/access-schema.js +3 -3
  23. package/dist/access-service.js +27 -26
  24. package/dist/access-surface-catalog.js +29 -28
  25. package/dist/access-surface-catalog.js.map +1 -1
  26. package/dist/active-recall.js +4 -4
  27. package/dist/briefing.js +4 -4
  28. package/dist/calibration.js +2 -1
  29. package/dist/calibration.js.map +1 -1
  30. package/dist/causal-consolidation.js +5 -5
  31. package/dist/{chunk-ZUCNOTNF.js → chunk-2NQD4N42.js} +12 -12
  32. package/dist/chunk-3VRMUNWL.js +289 -0
  33. package/dist/chunk-3VRMUNWL.js.map +1 -0
  34. package/dist/{chunk-466EGMFQ.js → chunk-42KSEX6C.js} +1 -1
  35. package/dist/chunk-42KSEX6C.js.map +1 -0
  36. package/dist/{chunk-O6ADSFGY.js → chunk-4B7JO3LG.js} +2 -2
  37. package/dist/{chunk-D42L5SQF.js → chunk-5A4QMATS.js} +5 -5
  38. package/dist/{chunk-SSUTJLCF.js → chunk-5ALVWXQG.js} +2 -2
  39. package/dist/{chunk-N36PBG4A.js → chunk-5BNDPLGI.js} +2 -2
  40. package/dist/{chunk-RTV3DB4I.js → chunk-7BFE2UD4.js} +2 -2
  41. package/dist/{chunk-IVUCF3QO.js → chunk-7RTNJ6M2.js} +2 -2
  42. package/dist/{chunk-VPXIKBPX.js → chunk-A3IURDU2.js} +22 -1
  43. package/dist/chunk-A3IURDU2.js.map +1 -0
  44. package/dist/{chunk-VOEBHBWG.js → chunk-B2BAZPVQ.js} +2 -2
  45. package/dist/{chunk-7UHCV5B3.js → chunk-B45WUQUU.js} +2 -2
  46. package/dist/{chunk-2I3OREMR.js → chunk-BGG5BP3M.js} +2 -2
  47. package/dist/{chunk-EML6HJE6.js → chunk-BO3F4X5U.js} +2 -2
  48. package/dist/{chunk-5JXNXKKV.js → chunk-BWOLINYY.js} +2 -2
  49. package/dist/{chunk-V4MB5ONM.js → chunk-BWXKJDDH.js} +4 -4
  50. package/dist/{chunk-OPNSIFFU.js → chunk-CA35LZAE.js} +8 -8
  51. package/dist/{chunk-PDFAX4PK.js → chunk-CT5YXLVB.js} +2 -2
  52. package/dist/{chunk-VUBVNY3W.js → chunk-CZURNXGO.js} +2 -2
  53. package/dist/{chunk-TXE55Z3X.js → chunk-D3I4P2SX.js} +2 -2
  54. package/dist/{chunk-BI3CIDIH.js → chunk-DF2G4IXN.js} +3 -3
  55. package/dist/{chunk-YRKKW4KT.js → chunk-DFL3MBL5.js} +3 -3
  56. package/dist/{chunk-2C5YAVHA.js → chunk-DIHJ2QWP.js} +2 -2
  57. package/dist/{chunk-IBGRIAE5.js → chunk-DVE7B2IR.js} +2 -2
  58. package/dist/{chunk-33G67YZF.js → chunk-DWL6YIZH.js} +2 -2
  59. package/dist/{chunk-YW4ZYASS.js → chunk-E2I5FVEU.js} +2 -2
  60. package/dist/{chunk-ZDUMF4H6.js → chunk-ECBAENW7.js} +14 -14
  61. package/dist/{chunk-QZ7WOCME.js → chunk-EQAIXC4Q.js} +2 -2
  62. package/dist/{chunk-RYYVMZBR.js → chunk-ETPIYK4J.js} +2 -2
  63. package/dist/{chunk-RQMSD4CN.js → chunk-FI6CUYMJ.js} +48 -48
  64. package/dist/{chunk-GXVLEXIX.js → chunk-G7QUQILS.js} +2 -2
  65. package/dist/{chunk-VYVQZRXF.js → chunk-H3AW6QZF.js} +2 -2
  66. package/dist/{chunk-3K6J5GO5.js → chunk-H53F22SJ.js} +3 -3
  67. package/dist/{chunk-NIS3FOMD.js → chunk-HGSERGFX.js} +2 -2
  68. package/dist/{chunk-ZDDDWTC7.js → chunk-HJQXHLQQ.js} +2 -2
  69. package/dist/{chunk-6VWCL2IZ.js → chunk-HNZTKT4J.js} +3 -3
  70. package/dist/{chunk-W3GFSWKE.js → chunk-HWMK3QHK.js} +8 -8
  71. package/dist/{chunk-VVSPHTSU.js → chunk-I75TTE7D.js} +2 -2
  72. package/dist/{chunk-FBNL4BYD.js → chunk-IXUEFTK4.js} +2 -2
  73. package/dist/{chunk-W7NVMERN.js → chunk-JT7GSF3T.js} +2 -2
  74. package/dist/{chunk-LDZD3OQG.js → chunk-KD73Q7EN.js} +2 -2
  75. package/dist/{chunk-HVIXRLXJ.js → chunk-KDMZZSL4.js} +2 -2
  76. package/dist/{chunk-7LCGFUMB.js → chunk-KQAFEZQX.js} +4 -4
  77. package/dist/{chunk-A2IJEV5L.js → chunk-M47LM554.js} +2 -2
  78. package/dist/{chunk-5NO42IYF.js → chunk-MKP4W52S.js} +4 -4
  79. package/dist/{chunk-LT4SMGE4.js → chunk-MRRLXXWA.js} +2 -2
  80. package/dist/{chunk-2JGBX5XY.js → chunk-NJAFC244.js} +2 -2
  81. package/dist/{chunk-OFQOOLQQ.js → chunk-NKGI7JWP.js} +2 -2
  82. package/dist/{chunk-UHBZK3CK.js → chunk-OL7LAGBG.js} +2 -2
  83. package/dist/{chunk-AAQ77A44.js → chunk-QNKEMFZY.js} +2 -2
  84. package/dist/{chunk-XKPLERHV.js → chunk-QP5GYLMS.js} +36 -36
  85. package/dist/{chunk-62CRBI5B.js → chunk-ROF3JORU.js} +2 -2
  86. package/dist/{chunk-OMERTLB7.js → chunk-SA7NPDWJ.js} +2 -2
  87. package/dist/{chunk-QC5TJUSP.js → chunk-TRG3A2AW.js} +2 -2
  88. package/dist/{chunk-CA2R2AD4.js → chunk-UFUWKKCV.js} +31 -31
  89. package/dist/{chunk-DJMGVSST.js → chunk-US4WUDQR.js} +2 -2
  90. package/dist/{chunk-4OEBL5T3.js → chunk-V7KGDLBC.js} +2 -2
  91. package/dist/{chunk-ML7ZJAKJ.js → chunk-VBINQDWP.js} +2 -2
  92. package/dist/{chunk-5FNZ3G6X.js → chunk-VKQI47DJ.js} +4 -4
  93. package/dist/{chunk-UXXLP2PD.js → chunk-X3ZLVGGX.js} +3 -3
  94. package/dist/{chunk-32FVH5OQ.js → chunk-XE5QLP3P.js} +6 -6
  95. package/dist/{chunk-DYJHGJZ4.js → chunk-XSEVBFMD.js} +35 -2
  96. package/dist/chunk-XSEVBFMD.js.map +1 -0
  97. package/dist/{chunk-PVHQSLNN.js → chunk-Y6IF6H2U.js} +2 -2
  98. package/dist/{chunk-CFZ4SXXH.js → chunk-YEF5AAWN.js} +2 -2
  99. package/dist/{chunk-NFB5QTOH.js → chunk-Z2XUE3VT.js} +2 -2
  100. package/dist/{chunk-INBB7D3V.js → chunk-ZXVLOKN4.js} +2 -2
  101. package/dist/cli.js +63 -62
  102. package/dist/coding/export-okf-codegraph.js +3 -3
  103. package/dist/compounding/engine.js +5 -5
  104. package/dist/config.d.ts.map +1 -1
  105. package/dist/config.js +4 -4
  106. package/dist/connectors/codex-materialize-runner.js +4 -4
  107. package/dist/connectors/index.js +4 -4
  108. package/dist/convergence-refresh.js +6 -6
  109. package/dist/corpus-watermark.js +6 -6
  110. package/dist/deep-recall-http-glue.js +4 -4
  111. package/dist/entity-retrieval.js +4 -4
  112. package/dist/external-wiki-access.js +28 -27
  113. package/dist/extraction.js +7 -6
  114. package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
  115. package/dist/harmonic-retrieval.js +4 -4
  116. package/dist/index.js +97 -96
  117. package/dist/lcm/engine.js +2 -2
  118. package/dist/lcm/index.js +5 -5
  119. package/dist/local-llm-rate-limit.d.ts +169 -0
  120. package/dist/local-llm-rate-limit.d.ts.map +1 -0
  121. package/dist/local-llm-rate-limit.js +48 -0
  122. package/dist/local-llm-rate-limit.js.map +1 -0
  123. package/dist/local-llm.d.ts.map +1 -1
  124. package/dist/local-llm.js +2 -1
  125. package/dist/location/index.js +6 -6
  126. package/dist/maintenance/memory-governance.js +4 -4
  127. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  128. package/dist/maintenance/rebuild-memory-projection-runner.js +6 -6
  129. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  130. package/dist/memory-browse-operations.js +28 -27
  131. package/dist/namespaces/migrate.js +5 -5
  132. package/dist/namespaces/storage.js +4 -4
  133. package/dist/operator-doctor-corpus.js +7 -7
  134. package/dist/operator-toolkit.js +13 -13
  135. package/dist/orchestration/maintenance.js +16 -15
  136. package/dist/orchestrator.js +86 -85
  137. package/dist/recall-explain-renderer.js +3 -3
  138. package/dist/recall-navigate-access.js +2 -2
  139. package/dist/recall-navigate-disclosure.js +2 -2
  140. package/dist/recall-navigate.js +2 -2
  141. package/dist/recall-navigation-operations.js +28 -27
  142. package/dist/recall-navigation.js +27 -26
  143. package/dist/recall-xray-cli.js +4 -4
  144. package/dist/recall-xray-renderer.js +3 -3
  145. package/dist/recall-xray.js +2 -2
  146. package/dist/resume-bundles.js +5 -5
  147. package/dist/semantic-consolidation.js +5 -5
  148. package/dist/semantic-rule-promotion.js +4 -4
  149. package/dist/semantic-rule-verifier.js +4 -4
  150. package/dist/source-agent-qualifier.js +4 -4
  151. package/dist/standup/build.js +4 -4
  152. package/dist/storage.js +3 -3
  153. package/dist/summarizer.js +3 -2
  154. package/dist/support-passport/index.js +30 -29
  155. package/dist/transfer/capsule-export.js +2 -2
  156. package/dist/transfer/capsule-import.js +2 -2
  157. package/dist/transfer/export-okf.js +4 -4
  158. package/dist/types.d.ts +8 -0
  159. package/dist/types.d.ts.map +1 -1
  160. package/dist/types.js +1 -1
  161. package/dist/verified-recall.js +4 -4
  162. package/dist/who-knows-cli.js +5 -5
  163. package/dist/who-knows.js +4 -4
  164. package/package.json +2 -2
  165. package/src/config.ts +9 -0
  166. package/src/local-llm-rate-limit.test.ts +406 -0
  167. package/src/local-llm-rate-limit.ts +418 -0
  168. package/src/local-llm.ts +35 -1
  169. package/src/types.ts +8 -0
  170. package/dist/chunk-466EGMFQ.js.map +0 -1
  171. package/dist/chunk-DYJHGJZ4.js.map +0 -1
  172. package/dist/chunk-VPXIKBPX.js.map +0 -1
  173. /package/dist/{chunk-ZUCNOTNF.js.map → chunk-2NQD4N42.js.map} +0 -0
  174. /package/dist/{chunk-O6ADSFGY.js.map → chunk-4B7JO3LG.js.map} +0 -0
  175. /package/dist/{chunk-D42L5SQF.js.map → chunk-5A4QMATS.js.map} +0 -0
  176. /package/dist/{chunk-SSUTJLCF.js.map → chunk-5ALVWXQG.js.map} +0 -0
  177. /package/dist/{chunk-N36PBG4A.js.map → chunk-5BNDPLGI.js.map} +0 -0
  178. /package/dist/{chunk-RTV3DB4I.js.map → chunk-7BFE2UD4.js.map} +0 -0
  179. /package/dist/{chunk-IVUCF3QO.js.map → chunk-7RTNJ6M2.js.map} +0 -0
  180. /package/dist/{chunk-VOEBHBWG.js.map → chunk-B2BAZPVQ.js.map} +0 -0
  181. /package/dist/{chunk-7UHCV5B3.js.map → chunk-B45WUQUU.js.map} +0 -0
  182. /package/dist/{chunk-2I3OREMR.js.map → chunk-BGG5BP3M.js.map} +0 -0
  183. /package/dist/{chunk-EML6HJE6.js.map → chunk-BO3F4X5U.js.map} +0 -0
  184. /package/dist/{chunk-5JXNXKKV.js.map → chunk-BWOLINYY.js.map} +0 -0
  185. /package/dist/{chunk-V4MB5ONM.js.map → chunk-BWXKJDDH.js.map} +0 -0
  186. /package/dist/{chunk-OPNSIFFU.js.map → chunk-CA35LZAE.js.map} +0 -0
  187. /package/dist/{chunk-PDFAX4PK.js.map → chunk-CT5YXLVB.js.map} +0 -0
  188. /package/dist/{chunk-VUBVNY3W.js.map → chunk-CZURNXGO.js.map} +0 -0
  189. /package/dist/{chunk-TXE55Z3X.js.map → chunk-D3I4P2SX.js.map} +0 -0
  190. /package/dist/{chunk-BI3CIDIH.js.map → chunk-DF2G4IXN.js.map} +0 -0
  191. /package/dist/{chunk-YRKKW4KT.js.map → chunk-DFL3MBL5.js.map} +0 -0
  192. /package/dist/{chunk-2C5YAVHA.js.map → chunk-DIHJ2QWP.js.map} +0 -0
  193. /package/dist/{chunk-IBGRIAE5.js.map → chunk-DVE7B2IR.js.map} +0 -0
  194. /package/dist/{chunk-33G67YZF.js.map → chunk-DWL6YIZH.js.map} +0 -0
  195. /package/dist/{chunk-YW4ZYASS.js.map → chunk-E2I5FVEU.js.map} +0 -0
  196. /package/dist/{chunk-ZDUMF4H6.js.map → chunk-ECBAENW7.js.map} +0 -0
  197. /package/dist/{chunk-QZ7WOCME.js.map → chunk-EQAIXC4Q.js.map} +0 -0
  198. /package/dist/{chunk-RYYVMZBR.js.map → chunk-ETPIYK4J.js.map} +0 -0
  199. /package/dist/{chunk-RQMSD4CN.js.map → chunk-FI6CUYMJ.js.map} +0 -0
  200. /package/dist/{chunk-GXVLEXIX.js.map → chunk-G7QUQILS.js.map} +0 -0
  201. /package/dist/{chunk-VYVQZRXF.js.map → chunk-H3AW6QZF.js.map} +0 -0
  202. /package/dist/{chunk-3K6J5GO5.js.map → chunk-H53F22SJ.js.map} +0 -0
  203. /package/dist/{chunk-NIS3FOMD.js.map → chunk-HGSERGFX.js.map} +0 -0
  204. /package/dist/{chunk-ZDDDWTC7.js.map → chunk-HJQXHLQQ.js.map} +0 -0
  205. /package/dist/{chunk-6VWCL2IZ.js.map → chunk-HNZTKT4J.js.map} +0 -0
  206. /package/dist/{chunk-W3GFSWKE.js.map → chunk-HWMK3QHK.js.map} +0 -0
  207. /package/dist/{chunk-VVSPHTSU.js.map → chunk-I75TTE7D.js.map} +0 -0
  208. /package/dist/{chunk-FBNL4BYD.js.map → chunk-IXUEFTK4.js.map} +0 -0
  209. /package/dist/{chunk-W7NVMERN.js.map → chunk-JT7GSF3T.js.map} +0 -0
  210. /package/dist/{chunk-LDZD3OQG.js.map → chunk-KD73Q7EN.js.map} +0 -0
  211. /package/dist/{chunk-HVIXRLXJ.js.map → chunk-KDMZZSL4.js.map} +0 -0
  212. /package/dist/{chunk-7LCGFUMB.js.map → chunk-KQAFEZQX.js.map} +0 -0
  213. /package/dist/{chunk-A2IJEV5L.js.map → chunk-M47LM554.js.map} +0 -0
  214. /package/dist/{chunk-5NO42IYF.js.map → chunk-MKP4W52S.js.map} +0 -0
  215. /package/dist/{chunk-LT4SMGE4.js.map → chunk-MRRLXXWA.js.map} +0 -0
  216. /package/dist/{chunk-2JGBX5XY.js.map → chunk-NJAFC244.js.map} +0 -0
  217. /package/dist/{chunk-OFQOOLQQ.js.map → chunk-NKGI7JWP.js.map} +0 -0
  218. /package/dist/{chunk-UHBZK3CK.js.map → chunk-OL7LAGBG.js.map} +0 -0
  219. /package/dist/{chunk-AAQ77A44.js.map → chunk-QNKEMFZY.js.map} +0 -0
  220. /package/dist/{chunk-XKPLERHV.js.map → chunk-QP5GYLMS.js.map} +0 -0
  221. /package/dist/{chunk-62CRBI5B.js.map → chunk-ROF3JORU.js.map} +0 -0
  222. /package/dist/{chunk-OMERTLB7.js.map → chunk-SA7NPDWJ.js.map} +0 -0
  223. /package/dist/{chunk-QC5TJUSP.js.map → chunk-TRG3A2AW.js.map} +0 -0
  224. /package/dist/{chunk-CA2R2AD4.js.map → chunk-UFUWKKCV.js.map} +0 -0
  225. /package/dist/{chunk-DJMGVSST.js.map → chunk-US4WUDQR.js.map} +0 -0
  226. /package/dist/{chunk-4OEBL5T3.js.map → chunk-V7KGDLBC.js.map} +0 -0
  227. /package/dist/{chunk-ML7ZJAKJ.js.map → chunk-VBINQDWP.js.map} +0 -0
  228. /package/dist/{chunk-5FNZ3G6X.js.map → chunk-VKQI47DJ.js.map} +0 -0
  229. /package/dist/{chunk-UXXLP2PD.js.map → chunk-X3ZLVGGX.js.map} +0 -0
  230. /package/dist/{chunk-32FVH5OQ.js.map → chunk-XE5QLP3P.js.map} +0 -0
  231. /package/dist/{chunk-PVHQSLNN.js.map → chunk-Y6IF6H2U.js.map} +0 -0
  232. /package/dist/{chunk-CFZ4SXXH.js.map → chunk-YEF5AAWN.js.map} +0 -0
  233. /package/dist/{chunk-NFB5QTOH.js.map → chunk-Z2XUE3VT.js.map} +0 -0
  234. /package/dist/{chunk-INBB7D3V.js.map → chunk-ZXVLOKN4.js.map} +0 -0
  235. /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/calibration.ts"],"sourcesContent":["/**\n * calibration.ts — Prediction-Error-Driven Model-User Calibration\n *\n * Analyzes patterns in user corrections to identify systematic miscalibration\n * between the model's predictions and the user's actual expectations.\n * During consolidation, replays chains of similar corrections through an LLM\n * to synthesize CalibrationRules that adjust model behavior for this specific user.\n *\n * Inspired by:\n * - Cerebellar motor calibration (prediction errors drive lasting adjustments)\n * - Temporal difference learning (dopamine signals prediction error)\n * - Tesla FSD shadow mode (divergence between prediction and reality = training signal)\n */\n\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { resolveLocalLlmCapabilities } from \"./capabilities.js\";\nimport { OUTPUT_LANGUAGE_POLICY } from \"./extraction-prompt.js\";\nimport { FallbackLlmClient } from \"./fallback-llm.js\";\nimport { listJsonFiles } from \"./json-store.js\";\nimport { isMacOsReplicationJunkBasename } from \"./utils/macos-replication-junk.js\";\nimport { LocalLlmClient } from \"./local-llm.js\";\nimport { log } from \"./logger.js\";\nimport { writeFileAtomically } from \"./maintenance/atomic-file.js\";\nimport type { AgentPersonaModelConfig, GatewayConfig, PluginConfig } from \"./types.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CalibrationRule {\n id: string;\n ruleType: \"model_tendency\" | \"user_expectation\" | \"scope_boundary\" | \"verification_required\";\n condition: string;\n modelTendency: string;\n userExpectation: string;\n calibration: string;\n confidence: number;\n evidenceCount: number;\n evidenceCorrectionIds: string[];\n createdAt: string;\n lastReinforcedAt: string;\n}\n\nexport interface CalibrationIndex {\n rules: CalibrationRule[];\n updatedAt: string;\n totalCorrectionsAnalyzed: number;\n /**\n * Weeks whose rule reinforcement is already committed. A retry of any of\n * these weeks must not increment `evidenceCount` again. Older indexes\n * persisted a single `appliedWeekId`; reads fold it into this list.\n */\n appliedWeekIds?: string[];\n}\n\n// ─── Storage ─────────────────────────────────────────────────────────────────\n\nfunction calibrationDir(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"calibration\");\n}\n\nfunction calibrationIndexPath(memoryDir: string): string {\n return path.join(calibrationDir(memoryDir), \"calibration-index.json\");\n}\n\n/** Week ids are short (`YYYY-Www`). Longer values are not used as stamps. */\nconst MAX_APPLIED_WEEK_ID_LENGTH = 32;\n\nexport function appliedCalibrationWeekId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0 || trimmed.length > MAX_APPLIED_WEEK_ID_LENGTH) return undefined;\n return trimmed;\n}\n\nexport async function readCalibrationIndex(memoryDir: string): Promise<CalibrationIndex> {\n try {\n const raw = JSON.parse(await readFile(calibrationIndexPath(memoryDir), \"utf8\"));\n const appliedWeekIds = normalizeAppliedWeekIds(raw.appliedWeekIds, raw.appliedWeekId);\n return {\n rules: Array.isArray(raw.rules) ? raw.rules : [],\n updatedAt: typeof raw.updatedAt === \"string\" ? raw.updatedAt : new Date().toISOString(),\n totalCorrectionsAnalyzed: typeof raw.totalCorrectionsAnalyzed === \"number\" ? raw.totalCorrectionsAnalyzed : 0,\n ...(appliedWeekIds.length > 0 ? { appliedWeekIds } : {}),\n };\n } catch {\n return { rules: [], updatedAt: new Date().toISOString(), totalCorrectionsAnalyzed: 0 };\n }\n}\n\nfunction normalizeAppliedWeekIds(list: unknown, additional: unknown): string[] {\n const weeks: string[] = [];\n if (Array.isArray(list)) {\n for (const entry of list) {\n const week = appliedCalibrationWeekId(entry);\n if (week && !weeks.includes(week)) weeks.push(week);\n }\n }\n const extra = appliedCalibrationWeekId(additional);\n if (extra && !weeks.includes(extra)) weeks.push(extra);\n return weeks;\n}\n\n/**\n * Attach the week stamp to the index that will be written with the reinforced\n * rules. The stamp and the evidence increment commit together. Every\n * previously applied week stays in the list so an out-of-order retry of an\n * older week is still recognized as already applied.\n */\nexport function withAppliedCalibrationWeek(\n index: CalibrationIndex,\n idempotencyKey: string | undefined,\n): CalibrationIndex {\n const appliedWeekIds = normalizeAppliedWeekIds(index.appliedWeekIds, idempotencyKey);\n return {\n rules: index.rules,\n updatedAt: index.updatedAt,\n totalCorrectionsAnalyzed: index.totalCorrectionsAnalyzed,\n ...(appliedWeekIds.length > 0 ? { appliedWeekIds } : {}),\n };\n}\n\nasync function writeCalibrationIndex(memoryDir: string, index: CalibrationIndex): Promise<void> {\n const dir = calibrationDir(memoryDir);\n await mkdir(dir, { recursive: true });\n const appliedWeekIds = normalizeAppliedWeekIds(index.appliedWeekIds, undefined);\n const stamped: CalibrationIndex = {\n rules: index.rules,\n updatedAt: new Date().toISOString(),\n totalCorrectionsAnalyzed: index.totalCorrectionsAnalyzed,\n ...(appliedWeekIds.length > 0 ? { appliedWeekIds } : {}),\n };\n await writeFileAtomically(calibrationIndexPath(memoryDir), JSON.stringify(stamped, null, 2));\n}\n\n// ─── Correction Reading ──────────────────────────────────────────────────────\n\nexport interface CorrectionMemory {\n id: string;\n content: string;\n created: string;\n confidence: number;\n entityRefs: string[];\n tags: string[];\n}\n\nconst CALIBRATION_RULE_TYPES = new Set([\n \"model_tendency\",\n \"user_expectation\",\n \"scope_boundary\",\n \"verification_required\",\n]);\n\nfunction parseConfidence(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction parseEvidenceIds(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\" && item.trim().length > 0);\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/**\n * Exported for entity-contamination R-11 regression coverage (#682\n * PR 2/3 — codex review). Tests can drive the real correction-reading\n * path instead of duplicating the regex inline, so calibration parser\n * regressions surface via the contamination suite.\n */\nexport async function readCalibrationCorrections(memoryDir: string): Promise<CorrectionMemory[]> {\n return readCorrectionsImpl(memoryDir);\n}\n\nasync function readCorrections(memoryDir: string): Promise<CorrectionMemory[]> {\n return readCorrectionsImpl(memoryDir);\n}\n\nasync function readCorrectionsImpl(memoryDir: string): Promise<CorrectionMemory[]> {\n const correctionsDir = path.join(memoryDir, \"corrections\");\n const files = await listJsonFiles(correctionsDir).catch(() => {\n // Corrections might be in facts/ directories too\n return [] as string[];\n });\n\n // Also scan facts directories for correction-category files\n const factsDir = path.join(memoryDir, \"facts\");\n try {\n const { readdir } = await import(\"node:fs/promises\");\n const dayDirs = (await readdir(factsDir)).filter((d: string) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d));\n for (const day of dayDirs) {\n const dayPath = path.join(factsDir, day);\n const dayFiles = (await readdir(dayPath))\n .filter((f: string) => !isMacOsReplicationJunkBasename(f) && f.startsWith(\"correction-\") && f.endsWith(\".md\"))\n .map((f: string) => path.join(dayPath, f));\n files.push(...dayFiles);\n }\n } catch {\n // facts dir might not exist\n }\n\n // Also check the dedicated corrections directory\n try {\n const { readdir } = await import(\"node:fs/promises\");\n const corrFiles = (await readdir(correctionsDir))\n .filter((f: string) => !isMacOsReplicationJunkBasename(f) && f.endsWith(\".md\"))\n .map((f: string) => path.join(correctionsDir, f));\n files.push(...corrFiles);\n } catch {\n // corrections dir might not exist\n }\n\n const corrections: CorrectionMemory[] = [];\n const seen = new Set<string>();\n\n for (const filePath of files) {\n try {\n const raw = await readFile(filePath, \"utf8\");\n\n // Parse frontmatter\n const fmMatch = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!fmMatch) continue;\n\n const content = fmMatch[2].trim();\n if (!content || content.length < 10) continue;\n\n // Extract id from frontmatter\n const idMatch = fmMatch[1].match(/^id:\\s*(.+)$/m);\n const id = idMatch?.[1]?.trim() ?? path.basename(filePath, \".md\");\n\n if (seen.has(id)) continue;\n seen.add(id);\n\n const confMatch = fmMatch[1].match(/^confidence:\\s*(.+)$/m);\n const confidence = confMatch ? parseFloat(confMatch[1]) : 0.9;\n\n const entityMatch = fmMatch[1].match(/^entityRef:\\s*(.+)$/m);\n const entityRefs = entityMatch ? [entityMatch[1].trim()] : [];\n\n corrections.push({ id, content, created: \"\", confidence, entityRefs, tags: [] });\n } catch {\n // skip unparseable files\n }\n }\n\n return corrections;\n}\n\n// ─── LLM-Assisted Clustering and Replay ──────────────────────────────────────\n\nexport const CLUSTER_PROMPT = `You are analyzing user corrections to an AI assistant. Each correction represents a moment where the assistant's prediction of what the user wanted was WRONG.\n\nYour job: Group these corrections into clusters where the SAME TYPE of misunderstanding is happening. Then for each cluster, synthesize a CalibrationRule.\n\nA CalibrationRule describes:\n- condition: When does this type of mistake happen?\n- modelTendency: What does the model tend to assume or do wrong?\n- userExpectation: What does the user actually want instead?\n- calibration: How should the model adjust its behavior?\n- ruleType: One of \"model_tendency\", \"user_expectation\", \"scope_boundary\", \"verification_required\"\n\nFocus on PATTERNS, not individual corrections. A cluster needs at least 2 corrections to be worth a rule.\n${OUTPUT_LANGUAGE_POLICY}\n\nOutput valid JSON only:\n{\n \"rules\": [\n {\n \"ruleType\": \"model_tendency\",\n \"condition\": \"When discussing project scope or task boundaries\",\n \"modelTendency\": \"The model tends to assume broader scope than the user intends\",\n \"userExpectation\": \"The user prefers narrow, specific task definitions and wants to be asked before scope expansion\",\n \"calibration\": \"When uncertain about scope, ask for clarification rather than assuming. Default to the narrower interpretation.\",\n \"confidence\": 0.85,\n \"evidenceIds\": [\"correction-id-1\", \"correction-id-2\"]\n }\n ]\n}`;\n\ninterface CalibrationChatClient {\n chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options?: {\n temperature?: number;\n maxTokens?: number;\n agentId?: string;\n modelChain?: AgentPersonaModelConfig;\n }\n ): Promise<{ content?: string | null } | null>;\n}\n\ninterface CalibrationLlmOptions {\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n /** Plugin config. Supplies `localLlm*` settings when the gateway chain is empty. */\n pluginConfig?: PluginConfig;\n}\n\nfunction resolveCalibrationChatClient(options: CalibrationLlmOptions): CalibrationChatClient | null {\n const gateway = new FallbackLlmClient(options.gatewayConfig, {\n workspaceDir: options.workspaceDir,\n });\n if (gateway.isAvailable({ agentId: options.gatewayAgentId, modelChain: options.modelChain })) {\n return gateway;\n }\n const pluginConfig = options.pluginConfig;\n if (pluginConfig && resolveLocalLlmCapabilities(pluginConfig).localLlm) {\n log.info(`[calibration] using configured local LLM (model=${pluginConfig.localLlmModel})`);\n const local = new LocalLlmClient(pluginConfig);\n return {\n chatCompletion(messages, chatOptions) {\n return local.chatCompletion(messages, {\n temperature: chatOptions?.temperature,\n maxTokens: chatOptions?.maxTokens,\n });\n },\n };\n }\n log.warn(\"[calibration] no LLM available — skipping consolidation\");\n return null;\n}\n\nexport async function synthesizeCalibrationRules(\n corrections: CorrectionMemory[],\n llm: CalibrationChatClient,\n existingRules: CalibrationRule[],\n agentId?: string,\n modelChain?: AgentPersonaModelConfig\n): Promise<CalibrationRule[]> {\n if (corrections.length < 2) return [];\n\n // Format corrections for the LLM\n const correctionText = corrections\n .slice(0, 50) // limit to avoid huge prompts\n .map((c, i) => `[${c.id}] ${c.content}`)\n .join(\"\\n\\n\");\n\n const existingRulesText =\n existingRules.length > 0\n ? `\\n\\nExisting calibration rules (avoid duplicating these):\\n${existingRules.map((r) => `- ${r.condition}: ${r.calibration}`).join(\"\\n\")}`\n : \"\";\n\n const response = await llm.chatCompletion(\n [\n { role: \"system\", content: CLUSTER_PROMPT },\n {\n role: \"user\",\n content: `Here are ${corrections.length} corrections from this user:\\n\\n${correctionText}${existingRulesText}`,\n },\n ],\n { temperature: 0.3, maxTokens: 3000, agentId, modelChain }\n );\n\n if (!response?.content) return [];\n\n try {\n let jsonStr = response.content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (fenceMatch) jsonStr = fenceMatch[1];\n\n const parsed = JSON.parse(jsonStr);\n if (!Array.isArray(parsed.rules)) return [];\n\n const now = new Date().toISOString();\n const rawRules: unknown[] = Array.isArray(parsed.rules) ? parsed.rules : [];\n return rawRules\n .map((r: unknown): CalibrationRule | undefined => {\n if (!r || typeof r !== \"object\" || Array.isArray(r)) return undefined;\n const raw = r as Record<string, unknown>;\n const ruleType = nonEmptyString(raw.ruleType);\n const condition = nonEmptyString(raw.condition);\n const modelTendency = nonEmptyString(raw.modelTendency);\n const calibration = nonEmptyString(raw.calibration);\n const confidence = parseConfidence(raw.confidence);\n if (\n !ruleType ||\n !CALIBRATION_RULE_TYPES.has(ruleType) ||\n !condition ||\n !modelTendency ||\n !calibration ||\n confidence === undefined\n ) {\n return undefined;\n }\n const evidenceCorrectionIds = parseEvidenceIds(raw.evidenceIds);\n return {\n id: `cal-${createHash(\"sha256\")\n .update(condition + calibration)\n .digest(\"hex\")\n .slice(0, 12)}`,\n ruleType: ruleType as CalibrationRule[\"ruleType\"],\n condition,\n modelTendency,\n userExpectation: nonEmptyString(raw.userExpectation) ?? \"\",\n calibration,\n confidence,\n evidenceCount: evidenceCorrectionIds.length,\n evidenceCorrectionIds,\n createdAt: now,\n lastReinforcedAt: now,\n };\n })\n .filter((rule): rule is CalibrationRule => !!rule);\n } catch {\n log.warn(\"[calibration] failed to parse LLM response\");\n return [];\n }\n}\n\n// ─── Recall Section ──────────────────────────────────────────────────────────\n\n/**\n * Build a recall section from calibration rules relevant to the current query.\n * Uses the LLM to select which rules apply to the current context.\n */\nexport function buildCalibrationRecallSection(\n rules: CalibrationRule[],\n query: string,\n maxChars: number = 1200,\n): string | null {\n if (rules.length === 0) return null;\n\n // Simple relevance: include all rules (they're already filtered to this user)\n // In production, could use embedding similarity to filter\n const lines: string[] = [\n \"## Model Calibration (learned from past corrections)\",\n \"\",\n \"Adjustments for this specific user, learned from patterns in their corrections:\",\n \"\",\n ];\n\n let totalChars = lines.join(\"\\n\").length;\n\n for (const rule of rules) {\n const line = `- **${rule.condition}**: ${rule.modelTendency} → Instead: ${rule.calibration}`;\n if (totalChars + line.length + 1 > maxChars) break;\n lines.push(line);\n totalChars += line.length + 1;\n }\n\n if (lines.length <= 4) return null;\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Options weekly compounding passes into calibration. Gateway agent and task\n * chain fields stay limited to `modelSource: \"gateway\"`. `pluginConfig` carries\n * the `localLlm*` settings used when that chain is empty.\n */\nexport function calibrationConsolidationInput(config: PluginConfig): {\n memoryDir: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig: PluginConfig;\n} {\n const gateway = config.modelSource === \"gateway\";\n return {\n memoryDir: config.memoryDir,\n gatewayConfig: config.gatewayConfig,\n gatewayAgentId: gateway ? (config.gatewayAgentId || undefined) : undefined,\n modelChain: gateway ? config.taskModelChain : undefined,\n workspaceDir: config.workspaceDir,\n pluginConfig: config,\n };\n}\n\n/**\n * Run the full calibration pipeline:\n * 1. Read all corrections\n * 2. Send to LLM for clustering and rule synthesis\n * 3. Merge with existing rules\n * 4. Write updated index\n */\nexport async function runCalibrationConsolidation(options: {\n memoryDir: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig?: PluginConfig;\n /**\n * When set, a week whose reinforcement is already in the index is returned\n * as-is. Weekly synthesis passes the ISO week id so a retry after a later\n * artifact failure does not count the same corrections twice.\n */\n idempotencyKey?: string;\n}): Promise<CalibrationRule[]> {\n try {\n const idempotencyKey = appliedCalibrationWeekId(options.idempotencyKey);\n const existingIndex = await readCalibrationIndex(options.memoryDir);\n if (idempotencyKey && (existingIndex.appliedWeekIds ?? []).includes(idempotencyKey)) {\n log.debug(`[calibration] week ${idempotencyKey} already applied — skipping`);\n return existingIndex.rules;\n }\n\n const llm = resolveCalibrationChatClient(options);\n if (!llm) return [];\n\n const corrections = await readCorrections(options.memoryDir);\n if (corrections.length < 3) {\n log.info(`[calibration] only ${corrections.length} corrections — need at least 3`);\n return [];\n }\n\n const newRules = await synthesizeCalibrationRules(\n corrections,\n llm,\n existingIndex.rules,\n options.gatewayAgentId,\n options.modelChain\n );\n if (newRules.length === 0) {\n log.info(\"[calibration] no new calibration rules synthesized\");\n return existingIndex.rules;\n }\n\n // Merge: keep existing rules, add new ones (deduplicate by id)\n const ruleMap = new Map(existingIndex.rules.map((r) => [r.id, r]));\n for (const rule of newRules) {\n if (ruleMap.has(rule.id)) {\n // Reinforce existing rule\n const existing = ruleMap.get(rule.id)!;\n existing.lastReinforcedAt = new Date().toISOString();\n existing.evidenceCount += rule.evidenceCount;\n existing.confidence = Math.min(1, existing.confidence + 0.05);\n } else {\n ruleMap.set(rule.id, rule);\n }\n }\n\n const allRules = [...ruleMap.values()];\n await writeCalibrationIndex(options.memoryDir, withAppliedCalibrationWeek({\n rules: allRules,\n updatedAt: existingIndex.updatedAt,\n totalCorrectionsAnalyzed: corrections.length,\n appliedWeekIds: existingIndex.appliedWeekIds,\n }, idempotencyKey));\n\n log.debug(`[calibration] synthesized ${newRules.length} new rule(s), ${allRules.length} total`);\n return allRules;\n } catch (error) {\n log.warn(\n `[calibration] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`\n );\n return [];\n }\n}\n\n/**\n * Standalone entry point for calibration consolidation that can be called\n * independently of weekly compounding. The compounding engine's\n * `synthesizeWeekly()` is one trigger, but orchestrators or periodic\n * maintenance jobs should call this directly so calibration is not gated\n * on weekly compounding being enabled.\n */\nexport async function runCalibrationIfEnabled(options: {\n memoryDir: string;\n calibrationEnabled: boolean;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig?: PluginConfig;\n}): Promise<CalibrationRule[]> {\n if (!options.calibrationEnabled) {\n return [];\n }\n return runCalibrationConsolidation({\n memoryDir: options.memoryDir,\n gatewayConfig: options.gatewayConfig,\n gatewayAgentId: options.gatewayAgentId,\n modelChain: options.modelChain,\n workspaceDir: options.workspaceDir,\n pluginConfig: options.pluginConfig,\n });\n}\n\n/**\n * Get calibration rules for recall injection.\n * Reads the pre-computed calibration index.\n */\nexport async function getCalibrationRulesForRecall(\n memoryDir: string,\n): Promise<CalibrationRule[]> {\n const index = await readCalibrationIndex(memoryDir);\n return index.rules;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/calibration.ts"],"sourcesContent":["/**\n * calibration.ts — Prediction-Error-Driven Model-User Calibration\n *\n * Analyzes patterns in user corrections to identify systematic miscalibration\n * between the model's predictions and the user's actual expectations.\n * During consolidation, replays chains of similar corrections through an LLM\n * to synthesize CalibrationRules that adjust model behavior for this specific user.\n *\n * Inspired by:\n * - Cerebellar motor calibration (prediction errors drive lasting adjustments)\n * - Temporal difference learning (dopamine signals prediction error)\n * - Tesla FSD shadow mode (divergence between prediction and reality = training signal)\n */\n\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { resolveLocalLlmCapabilities } from \"./capabilities.js\";\nimport { OUTPUT_LANGUAGE_POLICY } from \"./extraction-prompt.js\";\nimport { FallbackLlmClient } from \"./fallback-llm.js\";\nimport { listJsonFiles } from \"./json-store.js\";\nimport { isMacOsReplicationJunkBasename } from \"./utils/macos-replication-junk.js\";\nimport { LocalLlmClient } from \"./local-llm.js\";\nimport { log } from \"./logger.js\";\nimport { writeFileAtomically } from \"./maintenance/atomic-file.js\";\nimport type { AgentPersonaModelConfig, GatewayConfig, PluginConfig } from \"./types.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CalibrationRule {\n id: string;\n ruleType: \"model_tendency\" | \"user_expectation\" | \"scope_boundary\" | \"verification_required\";\n condition: string;\n modelTendency: string;\n userExpectation: string;\n calibration: string;\n confidence: number;\n evidenceCount: number;\n evidenceCorrectionIds: string[];\n createdAt: string;\n lastReinforcedAt: string;\n}\n\nexport interface CalibrationIndex {\n rules: CalibrationRule[];\n updatedAt: string;\n totalCorrectionsAnalyzed: number;\n /**\n * Weeks whose rule reinforcement is already committed. A retry of any of\n * these weeks must not increment `evidenceCount` again. Older indexes\n * persisted a single `appliedWeekId`; reads fold it into this list.\n */\n appliedWeekIds?: string[];\n}\n\n// ─── Storage ─────────────────────────────────────────────────────────────────\n\nfunction calibrationDir(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"calibration\");\n}\n\nfunction calibrationIndexPath(memoryDir: string): string {\n return path.join(calibrationDir(memoryDir), \"calibration-index.json\");\n}\n\n/** Week ids are short (`YYYY-Www`). Longer values are not used as stamps. */\nconst MAX_APPLIED_WEEK_ID_LENGTH = 32;\n\nexport function appliedCalibrationWeekId(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0 || trimmed.length > MAX_APPLIED_WEEK_ID_LENGTH) return undefined;\n return trimmed;\n}\n\nexport async function readCalibrationIndex(memoryDir: string): Promise<CalibrationIndex> {\n try {\n const raw = JSON.parse(await readFile(calibrationIndexPath(memoryDir), \"utf8\"));\n const appliedWeekIds = normalizeAppliedWeekIds(raw.appliedWeekIds, raw.appliedWeekId);\n return {\n rules: Array.isArray(raw.rules) ? raw.rules : [],\n updatedAt: typeof raw.updatedAt === \"string\" ? raw.updatedAt : new Date().toISOString(),\n totalCorrectionsAnalyzed: typeof raw.totalCorrectionsAnalyzed === \"number\" ? raw.totalCorrectionsAnalyzed : 0,\n ...(appliedWeekIds.length > 0 ? { appliedWeekIds } : {}),\n };\n } catch {\n return { rules: [], updatedAt: new Date().toISOString(), totalCorrectionsAnalyzed: 0 };\n }\n}\n\nfunction normalizeAppliedWeekIds(list: unknown, additional: unknown): string[] {\n const weeks: string[] = [];\n if (Array.isArray(list)) {\n for (const entry of list) {\n const week = appliedCalibrationWeekId(entry);\n if (week && !weeks.includes(week)) weeks.push(week);\n }\n }\n const extra = appliedCalibrationWeekId(additional);\n if (extra && !weeks.includes(extra)) weeks.push(extra);\n return weeks;\n}\n\n/**\n * Attach the week stamp to the index that will be written with the reinforced\n * rules. The stamp and the evidence increment commit together. Every\n * previously applied week stays in the list so an out-of-order retry of an\n * older week is still recognized as already applied.\n */\nexport function withAppliedCalibrationWeek(\n index: CalibrationIndex,\n idempotencyKey: string | undefined,\n): CalibrationIndex {\n const appliedWeekIds = normalizeAppliedWeekIds(index.appliedWeekIds, idempotencyKey);\n return {\n rules: index.rules,\n updatedAt: index.updatedAt,\n totalCorrectionsAnalyzed: index.totalCorrectionsAnalyzed,\n ...(appliedWeekIds.length > 0 ? { appliedWeekIds } : {}),\n };\n}\n\nasync function writeCalibrationIndex(memoryDir: string, index: CalibrationIndex): Promise<void> {\n const dir = calibrationDir(memoryDir);\n await mkdir(dir, { recursive: true });\n const appliedWeekIds = normalizeAppliedWeekIds(index.appliedWeekIds, undefined);\n const stamped: CalibrationIndex = {\n rules: index.rules,\n updatedAt: new Date().toISOString(),\n totalCorrectionsAnalyzed: index.totalCorrectionsAnalyzed,\n ...(appliedWeekIds.length > 0 ? { appliedWeekIds } : {}),\n };\n await writeFileAtomically(calibrationIndexPath(memoryDir), JSON.stringify(stamped, null, 2));\n}\n\n// ─── Correction Reading ──────────────────────────────────────────────────────\n\nexport interface CorrectionMemory {\n id: string;\n content: string;\n created: string;\n confidence: number;\n entityRefs: string[];\n tags: string[];\n}\n\nconst CALIBRATION_RULE_TYPES = new Set([\n \"model_tendency\",\n \"user_expectation\",\n \"scope_boundary\",\n \"verification_required\",\n]);\n\nfunction parseConfidence(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.max(0, Math.min(1, value));\n}\n\nfunction parseEvidenceIds(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\" && item.trim().length > 0);\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/**\n * Exported for entity-contamination R-11 regression coverage (#682\n * PR 2/3 — codex review). Tests can drive the real correction-reading\n * path instead of duplicating the regex inline, so calibration parser\n * regressions surface via the contamination suite.\n */\nexport async function readCalibrationCorrections(memoryDir: string): Promise<CorrectionMemory[]> {\n return readCorrectionsImpl(memoryDir);\n}\n\nasync function readCorrections(memoryDir: string): Promise<CorrectionMemory[]> {\n return readCorrectionsImpl(memoryDir);\n}\n\nasync function readCorrectionsImpl(memoryDir: string): Promise<CorrectionMemory[]> {\n const correctionsDir = path.join(memoryDir, \"corrections\");\n const files = await listJsonFiles(correctionsDir).catch(() => {\n // Corrections might be in facts/ directories too\n return [] as string[];\n });\n\n // Also scan facts directories for correction-category files\n const factsDir = path.join(memoryDir, \"facts\");\n try {\n const { readdir } = await import(\"node:fs/promises\");\n const dayDirs = (await readdir(factsDir)).filter((d: string) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d));\n for (const day of dayDirs) {\n const dayPath = path.join(factsDir, day);\n const dayFiles = (await readdir(dayPath))\n .filter((f: string) => !isMacOsReplicationJunkBasename(f) && f.startsWith(\"correction-\") && f.endsWith(\".md\"))\n .map((f: string) => path.join(dayPath, f));\n files.push(...dayFiles);\n }\n } catch {\n // facts dir might not exist\n }\n\n // Also check the dedicated corrections directory\n try {\n const { readdir } = await import(\"node:fs/promises\");\n const corrFiles = (await readdir(correctionsDir))\n .filter((f: string) => !isMacOsReplicationJunkBasename(f) && f.endsWith(\".md\"))\n .map((f: string) => path.join(correctionsDir, f));\n files.push(...corrFiles);\n } catch {\n // corrections dir might not exist\n }\n\n const corrections: CorrectionMemory[] = [];\n const seen = new Set<string>();\n\n for (const filePath of files) {\n try {\n const raw = await readFile(filePath, \"utf8\");\n\n // Parse frontmatter\n const fmMatch = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!fmMatch) continue;\n\n const content = fmMatch[2].trim();\n if (!content || content.length < 10) continue;\n\n // Extract id from frontmatter\n const idMatch = fmMatch[1].match(/^id:\\s*(.+)$/m);\n const id = idMatch?.[1]?.trim() ?? path.basename(filePath, \".md\");\n\n if (seen.has(id)) continue;\n seen.add(id);\n\n const confMatch = fmMatch[1].match(/^confidence:\\s*(.+)$/m);\n const confidence = confMatch ? parseFloat(confMatch[1]) : 0.9;\n\n const entityMatch = fmMatch[1].match(/^entityRef:\\s*(.+)$/m);\n const entityRefs = entityMatch ? [entityMatch[1].trim()] : [];\n\n corrections.push({ id, content, created: \"\", confidence, entityRefs, tags: [] });\n } catch {\n // skip unparseable files\n }\n }\n\n return corrections;\n}\n\n// ─── LLM-Assisted Clustering and Replay ──────────────────────────────────────\n\nexport const CLUSTER_PROMPT = `You are analyzing user corrections to an AI assistant. Each correction represents a moment where the assistant's prediction of what the user wanted was WRONG.\n\nYour job: Group these corrections into clusters where the SAME TYPE of misunderstanding is happening. Then for each cluster, synthesize a CalibrationRule.\n\nA CalibrationRule describes:\n- condition: When does this type of mistake happen?\n- modelTendency: What does the model tend to assume or do wrong?\n- userExpectation: What does the user actually want instead?\n- calibration: How should the model adjust its behavior?\n- ruleType: One of \"model_tendency\", \"user_expectation\", \"scope_boundary\", \"verification_required\"\n\nFocus on PATTERNS, not individual corrections. A cluster needs at least 2 corrections to be worth a rule.\n${OUTPUT_LANGUAGE_POLICY}\n\nOutput valid JSON only:\n{\n \"rules\": [\n {\n \"ruleType\": \"model_tendency\",\n \"condition\": \"When discussing project scope or task boundaries\",\n \"modelTendency\": \"The model tends to assume broader scope than the user intends\",\n \"userExpectation\": \"The user prefers narrow, specific task definitions and wants to be asked before scope expansion\",\n \"calibration\": \"When uncertain about scope, ask for clarification rather than assuming. Default to the narrower interpretation.\",\n \"confidence\": 0.85,\n \"evidenceIds\": [\"correction-id-1\", \"correction-id-2\"]\n }\n ]\n}`;\n\ninterface CalibrationChatClient {\n chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options?: {\n temperature?: number;\n maxTokens?: number;\n agentId?: string;\n modelChain?: AgentPersonaModelConfig;\n }\n ): Promise<{ content?: string | null } | null>;\n}\n\ninterface CalibrationLlmOptions {\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n /** Plugin config. Supplies `localLlm*` settings when the gateway chain is empty. */\n pluginConfig?: PluginConfig;\n}\n\nfunction resolveCalibrationChatClient(options: CalibrationLlmOptions): CalibrationChatClient | null {\n const gateway = new FallbackLlmClient(options.gatewayConfig, {\n workspaceDir: options.workspaceDir,\n });\n if (gateway.isAvailable({ agentId: options.gatewayAgentId, modelChain: options.modelChain })) {\n return gateway;\n }\n const pluginConfig = options.pluginConfig;\n if (pluginConfig && resolveLocalLlmCapabilities(pluginConfig).localLlm) {\n log.info(`[calibration] using configured local LLM (model=${pluginConfig.localLlmModel})`);\n const local = new LocalLlmClient(pluginConfig);\n return {\n chatCompletion(messages, chatOptions) {\n return local.chatCompletion(messages, {\n temperature: chatOptions?.temperature,\n maxTokens: chatOptions?.maxTokens,\n });\n },\n };\n }\n log.warn(\"[calibration] no LLM available — skipping consolidation\");\n return null;\n}\n\nexport async function synthesizeCalibrationRules(\n corrections: CorrectionMemory[],\n llm: CalibrationChatClient,\n existingRules: CalibrationRule[],\n agentId?: string,\n modelChain?: AgentPersonaModelConfig\n): Promise<CalibrationRule[]> {\n if (corrections.length < 2) return [];\n\n // Format corrections for the LLM\n const correctionText = corrections\n .slice(0, 50) // limit to avoid huge prompts\n .map((c, i) => `[${c.id}] ${c.content}`)\n .join(\"\\n\\n\");\n\n const existingRulesText =\n existingRules.length > 0\n ? `\\n\\nExisting calibration rules (avoid duplicating these):\\n${existingRules.map((r) => `- ${r.condition}: ${r.calibration}`).join(\"\\n\")}`\n : \"\";\n\n const response = await llm.chatCompletion(\n [\n { role: \"system\", content: CLUSTER_PROMPT },\n {\n role: \"user\",\n content: `Here are ${corrections.length} corrections from this user:\\n\\n${correctionText}${existingRulesText}`,\n },\n ],\n { temperature: 0.3, maxTokens: 3000, agentId, modelChain }\n );\n\n if (!response?.content) return [];\n\n try {\n let jsonStr = response.content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (fenceMatch) jsonStr = fenceMatch[1];\n\n const parsed = JSON.parse(jsonStr);\n if (!Array.isArray(parsed.rules)) return [];\n\n const now = new Date().toISOString();\n const rawRules: unknown[] = Array.isArray(parsed.rules) ? parsed.rules : [];\n return rawRules\n .map((r: unknown): CalibrationRule | undefined => {\n if (!r || typeof r !== \"object\" || Array.isArray(r)) return undefined;\n const raw = r as Record<string, unknown>;\n const ruleType = nonEmptyString(raw.ruleType);\n const condition = nonEmptyString(raw.condition);\n const modelTendency = nonEmptyString(raw.modelTendency);\n const calibration = nonEmptyString(raw.calibration);\n const confidence = parseConfidence(raw.confidence);\n if (\n !ruleType ||\n !CALIBRATION_RULE_TYPES.has(ruleType) ||\n !condition ||\n !modelTendency ||\n !calibration ||\n confidence === undefined\n ) {\n return undefined;\n }\n const evidenceCorrectionIds = parseEvidenceIds(raw.evidenceIds);\n return {\n id: `cal-${createHash(\"sha256\")\n .update(condition + calibration)\n .digest(\"hex\")\n .slice(0, 12)}`,\n ruleType: ruleType as CalibrationRule[\"ruleType\"],\n condition,\n modelTendency,\n userExpectation: nonEmptyString(raw.userExpectation) ?? \"\",\n calibration,\n confidence,\n evidenceCount: evidenceCorrectionIds.length,\n evidenceCorrectionIds,\n createdAt: now,\n lastReinforcedAt: now,\n };\n })\n .filter((rule): rule is CalibrationRule => !!rule);\n } catch {\n log.warn(\"[calibration] failed to parse LLM response\");\n return [];\n }\n}\n\n// ─── Recall Section ──────────────────────────────────────────────────────────\n\n/**\n * Build a recall section from calibration rules relevant to the current query.\n * Uses the LLM to select which rules apply to the current context.\n */\nexport function buildCalibrationRecallSection(\n rules: CalibrationRule[],\n query: string,\n maxChars: number = 1200,\n): string | null {\n if (rules.length === 0) return null;\n\n // Simple relevance: include all rules (they're already filtered to this user)\n // In production, could use embedding similarity to filter\n const lines: string[] = [\n \"## Model Calibration (learned from past corrections)\",\n \"\",\n \"Adjustments for this specific user, learned from patterns in their corrections:\",\n \"\",\n ];\n\n let totalChars = lines.join(\"\\n\").length;\n\n for (const rule of rules) {\n const line = `- **${rule.condition}**: ${rule.modelTendency} → Instead: ${rule.calibration}`;\n if (totalChars + line.length + 1 > maxChars) break;\n lines.push(line);\n totalChars += line.length + 1;\n }\n\n if (lines.length <= 4) return null;\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Options weekly compounding passes into calibration. Gateway agent and task\n * chain fields stay limited to `modelSource: \"gateway\"`. `pluginConfig` carries\n * the `localLlm*` settings used when that chain is empty.\n */\nexport function calibrationConsolidationInput(config: PluginConfig): {\n memoryDir: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig: PluginConfig;\n} {\n const gateway = config.modelSource === \"gateway\";\n return {\n memoryDir: config.memoryDir,\n gatewayConfig: config.gatewayConfig,\n gatewayAgentId: gateway ? (config.gatewayAgentId || undefined) : undefined,\n modelChain: gateway ? config.taskModelChain : undefined,\n workspaceDir: config.workspaceDir,\n pluginConfig: config,\n };\n}\n\n/**\n * Run the full calibration pipeline:\n * 1. Read all corrections\n * 2. Send to LLM for clustering and rule synthesis\n * 3. Merge with existing rules\n * 4. Write updated index\n */\nexport async function runCalibrationConsolidation(options: {\n memoryDir: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig?: PluginConfig;\n /**\n * When set, a week whose reinforcement is already in the index is returned\n * as-is. Weekly synthesis passes the ISO week id so a retry after a later\n * artifact failure does not count the same corrections twice.\n */\n idempotencyKey?: string;\n}): Promise<CalibrationRule[]> {\n try {\n const idempotencyKey = appliedCalibrationWeekId(options.idempotencyKey);\n const existingIndex = await readCalibrationIndex(options.memoryDir);\n if (idempotencyKey && (existingIndex.appliedWeekIds ?? []).includes(idempotencyKey)) {\n log.debug(`[calibration] week ${idempotencyKey} already applied — skipping`);\n return existingIndex.rules;\n }\n\n const llm = resolveCalibrationChatClient(options);\n if (!llm) return [];\n\n const corrections = await readCorrections(options.memoryDir);\n if (corrections.length < 3) {\n log.info(`[calibration] only ${corrections.length} corrections — need at least 3`);\n return [];\n }\n\n const newRules = await synthesizeCalibrationRules(\n corrections,\n llm,\n existingIndex.rules,\n options.gatewayAgentId,\n options.modelChain\n );\n if (newRules.length === 0) {\n log.info(\"[calibration] no new calibration rules synthesized\");\n return existingIndex.rules;\n }\n\n // Merge: keep existing rules, add new ones (deduplicate by id)\n const ruleMap = new Map(existingIndex.rules.map((r) => [r.id, r]));\n for (const rule of newRules) {\n if (ruleMap.has(rule.id)) {\n // Reinforce existing rule\n const existing = ruleMap.get(rule.id)!;\n existing.lastReinforcedAt = new Date().toISOString();\n existing.evidenceCount += rule.evidenceCount;\n existing.confidence = Math.min(1, existing.confidence + 0.05);\n } else {\n ruleMap.set(rule.id, rule);\n }\n }\n\n const allRules = [...ruleMap.values()];\n await writeCalibrationIndex(options.memoryDir, withAppliedCalibrationWeek({\n rules: allRules,\n updatedAt: existingIndex.updatedAt,\n totalCorrectionsAnalyzed: corrections.length,\n appliedWeekIds: existingIndex.appliedWeekIds,\n }, idempotencyKey));\n\n log.debug(`[calibration] synthesized ${newRules.length} new rule(s), ${allRules.length} total`);\n return allRules;\n } catch (error) {\n log.warn(\n `[calibration] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`\n );\n return [];\n }\n}\n\n/**\n * Standalone entry point for calibration consolidation that can be called\n * independently of weekly compounding. The compounding engine's\n * `synthesizeWeekly()` is one trigger, but orchestrators or periodic\n * maintenance jobs should call this directly so calibration is not gated\n * on weekly compounding being enabled.\n */\nexport async function runCalibrationIfEnabled(options: {\n memoryDir: string;\n calibrationEnabled: boolean;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n modelChain?: AgentPersonaModelConfig;\n workspaceDir?: string;\n pluginConfig?: PluginConfig;\n}): Promise<CalibrationRule[]> {\n if (!options.calibrationEnabled) {\n return [];\n }\n return runCalibrationConsolidation({\n memoryDir: options.memoryDir,\n gatewayConfig: options.gatewayConfig,\n gatewayAgentId: options.gatewayAgentId,\n modelChain: options.modelChain,\n workspaceDir: options.workspaceDir,\n pluginConfig: options.pluginConfig,\n });\n}\n\n/**\n * Get calibration rules for recall injection.\n * Reads the pre-computed calibration index.\n */\nexport async function getCalibrationRulesForRecall(\n memoryDir: string,\n): Promise<CalibrationRule[]> {\n const index = await readCalibrationIndex(memoryDir);\n return index.rules;\n}\n"],"mappings":"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","names":[]}
@@ -4,12 +4,12 @@ import {
4
4
  } from "./chunk-GZXXPCWJ.js";
5
5
  import {
6
6
  buildExtensionsBlockForConsolidation
7
- } from "./chunk-NFB5QTOH.js";
7
+ } from "./chunk-Z2XUE3VT.js";
8
8
  import "./chunk-5JMAGU26.js";
9
9
  import "./chunk-B7RWBZXW.js";
10
10
  import {
11
11
  runPostConsolidationMaterialize
12
- } from "./chunk-2C5YAVHA.js";
12
+ } from "./chunk-DIHJ2QWP.js";
13
13
  import "./chunk-3UXOZBHV.js";
14
14
  import "./chunk-PIWFNPQT.js";
15
15
  import "./chunk-TOTT6S2V.js";
@@ -19,7 +19,7 @@ import "./chunk-QDW3E4RD.js";
19
19
  import {
20
20
  resolveCausalTrajectoryStoreDir
21
21
  } from "./chunk-C2RW36YR.js";
22
- import "./chunk-D42L5SQF.js";
22
+ import "./chunk-5A4QMATS.js";
23
23
  import "./chunk-UHVXE24Y.js";
24
24
  import "./chunk-TRMJ6BYC.js";
25
25
  import "./chunk-V4JZCTMK.js";
@@ -40,8 +40,8 @@ import "./chunk-A7Q43EUZ.js";
40
40
  import "./chunk-S2V7RUCL.js";
41
41
  import "./chunk-PPUNC7UX.js";
42
42
  import "./chunk-X3724WL4.js";
43
- import "./chunk-MRPOZBJC.js";
44
43
  import "./chunk-S2HLHAZ4.js";
44
+ import "./chunk-MRPOZBJC.js";
45
45
  import "./chunk-DM2T26WE.js";
46
46
  import "./chunk-LDXUBPMO.js";
47
47
  import "./chunk-2DT7PIDZ.js";
@@ -89,7 +89,7 @@ import "./chunk-YCVB7X56.js";
89
89
  import "./chunk-PQVCH2AE.js";
90
90
  import "./chunk-PJKTFXE2.js";
91
91
  import "./chunk-W5BIQOAJ.js";
92
- import "./chunk-466EGMFQ.js";
92
+ import "./chunk-42KSEX6C.js";
93
93
  import "./chunk-YBA52OKG.js";
94
94
  import "./chunk-5LRV557D.js";
95
95
  import "./chunk-BT7NVCML.js";
@@ -1,9 +1,3 @@
1
- import {
2
- PROJECTION_REBUILD_RUNNER_FLAG
3
- } from "./chunk-SSUTJLCF.js";
4
- import {
5
- startLocationAutoSync
6
- } from "./chunk-PDFAX4PK.js";
7
1
  import {
8
2
  ensureContradictionScanCron,
9
3
  ensureDaySummaryCron,
@@ -13,22 +7,28 @@ import {
13
7
  ensureProceduralMiningCron,
14
8
  graphEdgeDecayCadenceToCronExpr
15
9
  } from "./chunk-666A3MOW.js";
10
+ import {
11
+ PROJECTION_REBUILD_RUNNER_FLAG
12
+ } from "./chunk-5ALVWXQG.js";
13
+ import {
14
+ startLocationAutoSync
15
+ } from "./chunk-CT5YXLVB.js";
16
16
  import {
17
17
  CompoundingEngine,
18
18
  isIsoWeekId,
19
19
  msUntilNextIsoWeek,
20
20
  previousIsoWeekId
21
- } from "./chunk-UXXLP2PD.js";
21
+ } from "./chunk-X3ZLVGGX.js";
22
22
  import {
23
23
  rebuildMemoryLifecycleLedger
24
- } from "./chunk-2JGBX5XY.js";
24
+ } from "./chunk-NJAFC244.js";
25
25
  import {
26
26
  planNamespaceMaintenance,
27
27
  runNamespaceMaintenanceBatchPlan
28
- } from "./chunk-YRKKW4KT.js";
28
+ } from "./chunk-DFL3MBL5.js";
29
29
  import {
30
30
  rebuildMemoryProjection
31
- } from "./chunk-6VWCL2IZ.js";
31
+ } from "./chunk-HNZTKT4J.js";
32
32
  import {
33
33
  DEFAULT_TIMELINE_CATEGORIES,
34
34
  TIMELINE_RESERVED_IDLE,
@@ -45,7 +45,7 @@ import {
45
45
  } from "./chunk-EWMRFW3F.js";
46
46
  import {
47
47
  LocalLlmClient
48
- } from "./chunk-DYJHGJZ4.js";
48
+ } from "./chunk-XSEVBFMD.js";
49
49
  import {
50
50
  FallbackLlmClient
51
51
  } from "./chunk-TKC5LZ43.js";
@@ -2793,4 +2793,4 @@ export {
2793
2793
  ActivitySyncScheduler,
2794
2794
  MaintenanceScheduler
2795
2795
  };
2796
- //# sourceMappingURL=chunk-ZUCNOTNF.js.map
2796
+ //# sourceMappingURL=chunk-2NQD4N42.js.map
@@ -0,0 +1,289 @@
1
+ // src/local-llm-rate-limit.ts
2
+ var RATE_LIMIT_STATUSES = /* @__PURE__ */ new Set([408, 429, 503]);
3
+ var RATE_LIMIT_BODY_PATTERN = /no deployments available|cooldown_list|all deployments.*cooldown/i;
4
+ var TRY_AGAIN_PATTERN = /try again in (\d+(?:\.\d+)?) ?(seconds?|secs?|s|milliseconds?|ms|minutes?|mins?)\b/i;
5
+ var MAX_RECORDED_BACKOFF_MS = 10 * 6e4;
6
+ var DEFAULT_RATE_LIMIT_RETRY_COUNT = 3;
7
+ var DEFAULT_RATE_LIMIT_BACKOFF_BASE_MS = 1e3;
8
+ var DEFAULT_RATE_LIMIT_BACKOFF_MAX_MS = 6e4;
9
+ var DEFAULT_MAX_CONCURRENCY = 4;
10
+ function nonNegativeInt(value, fallback) {
11
+ if (value === void 0 || !Number.isFinite(value) || value < 0) return fallback;
12
+ return Math.floor(value);
13
+ }
14
+ function resolveRateLimitSettings(config) {
15
+ const backoffBaseMs = Math.max(
16
+ 1,
17
+ nonNegativeInt(config.localLlmRateLimitBackoffBaseMs, DEFAULT_RATE_LIMIT_BACKOFF_BASE_MS)
18
+ );
19
+ const backoffMaxMs = Math.max(
20
+ backoffBaseMs,
21
+ nonNegativeInt(config.localLlmRateLimitBackoffMaxMs, DEFAULT_RATE_LIMIT_BACKOFF_MAX_MS)
22
+ );
23
+ return {
24
+ retryCount: nonNegativeInt(config.localLlmRateLimitRetryCount, DEFAULT_RATE_LIMIT_RETRY_COUNT),
25
+ backoffBaseMs,
26
+ backoffMaxMs,
27
+ maxConcurrency: nonNegativeInt(config.localLlmMaxConcurrency, DEFAULT_MAX_CONCURRENCY)
28
+ };
29
+ }
30
+ function isRateLimitedResponse(status, bodyText) {
31
+ if (RATE_LIMIT_STATUSES.has(status)) return true;
32
+ return status >= 500 && RATE_LIMIT_BODY_PATTERN.test(bodyText);
33
+ }
34
+ function parseRetryAfterMs(value, nowMs = Date.now()) {
35
+ if (value === null || value === void 0) return null;
36
+ const trimmed = value.trim();
37
+ if (!trimmed) return null;
38
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
39
+ return Math.min(MAX_RECORDED_BACKOFF_MS, Math.round(Number(trimmed) * 1e3));
40
+ }
41
+ const dateMs = Date.parse(trimmed);
42
+ if (Number.isNaN(dateMs)) return null;
43
+ return Math.min(MAX_RECORDED_BACKOFF_MS, Math.max(0, dateMs - nowMs));
44
+ }
45
+ function parseTryAgainHintMs(bodyText) {
46
+ const match = bodyText.match(TRY_AGAIN_PATTERN);
47
+ if (!match) return null;
48
+ const amount = Number(match[1]);
49
+ if (!Number.isFinite(amount)) return null;
50
+ const unit = match[2].toLowerCase();
51
+ let ms;
52
+ if (unit.startsWith("ms") || unit.startsWith("milli")) ms = amount;
53
+ else if (unit.startsWith("min")) ms = amount * 6e4;
54
+ else ms = amount * 1e3;
55
+ return Math.min(MAX_RECORDED_BACKOFF_MS, Math.round(ms));
56
+ }
57
+ function serverRetryHintMs(retryAfterHeader, bodyText, nowMs = Date.now()) {
58
+ return parseRetryAfterMs(retryAfterHeader, nowMs) ?? parseTryAgainHintMs(bodyText);
59
+ }
60
+ function computeFullJitterBackoffMs(attempt, baseMs, maxMs, random = Math.random) {
61
+ const exponent = Math.max(0, Math.min(30, attempt - 1));
62
+ const ceiling = Math.min(maxMs, baseMs * 2 ** exponent);
63
+ return Math.floor(random() * ceiling);
64
+ }
65
+ function computeRateLimitDelayMs(attempt, settings, serverHintMs, random = Math.random) {
66
+ const jittered = computeFullJitterBackoffMs(attempt, settings.backoffBaseMs, settings.backoffMaxMs, random);
67
+ return serverHintMs === null ? jittered : Math.max(serverHintMs, jittered);
68
+ }
69
+ var LocalLlmConcurrencyLimiter = class {
70
+ active = 0;
71
+ max;
72
+ waiters = [];
73
+ constructor(max) {
74
+ this.max = max;
75
+ }
76
+ setMax(max) {
77
+ this.max = max;
78
+ this.pump();
79
+ }
80
+ get inFlight() {
81
+ return this.active;
82
+ }
83
+ get queued() {
84
+ return this.waiters.length;
85
+ }
86
+ /**
87
+ * Wait for a slot. Resolves to a release function, or null when the
88
+ * signal aborts first. `max <= 0` means unlimited.
89
+ */
90
+ acquire(options = {}) {
91
+ const { signal, timeoutMs } = options;
92
+ if (signal?.aborted) return Promise.resolve(null);
93
+ if (this.max <= 0 || this.active < this.max && this.waiters.length === 0) {
94
+ this.active += 1;
95
+ return Promise.resolve(this.releaser());
96
+ }
97
+ if (timeoutMs !== void 0 && timeoutMs <= 0) return Promise.resolve(null);
98
+ return new Promise((resolve) => {
99
+ let timer;
100
+ const waiter = {
101
+ highPriority: options.highPriority === true,
102
+ grant: () => {
103
+ signal?.removeEventListener("abort", onAbort);
104
+ if (timer) clearTimeout(timer);
105
+ resolve(this.releaser());
106
+ }
107
+ };
108
+ const onAbort = () => {
109
+ const index = this.waiters.indexOf(waiter);
110
+ if (index >= 0) this.waiters.splice(index, 1);
111
+ signal?.removeEventListener("abort", onAbort);
112
+ if (timer) clearTimeout(timer);
113
+ resolve(null);
114
+ };
115
+ if (waiter.highPriority) {
116
+ const firstLow = this.waiters.findIndex((w) => !w.highPriority);
117
+ if (firstLow < 0) this.waiters.push(waiter);
118
+ else this.waiters.splice(firstLow, 0, waiter);
119
+ } else {
120
+ this.waiters.push(waiter);
121
+ }
122
+ signal?.addEventListener("abort", onAbort, { once: true });
123
+ if (timeoutMs !== void 0) timer = setTimeout(onAbort, timeoutMs);
124
+ });
125
+ }
126
+ releaser() {
127
+ let released = false;
128
+ return () => {
129
+ if (released) return;
130
+ released = true;
131
+ this.active -= 1;
132
+ this.pump();
133
+ };
134
+ }
135
+ pump() {
136
+ while (this.waiters.length > 0 && (this.max <= 0 || this.active < this.max)) {
137
+ const next = this.waiters.shift();
138
+ if (!next) break;
139
+ this.active += 1;
140
+ next.grant();
141
+ }
142
+ }
143
+ };
144
+ function globalState() {
145
+ globalThis.__remnicLocalLlmRateLimitState ??= {
146
+ limiter: new LocalLlmConcurrencyLimiter(DEFAULT_MAX_CONCURRENCY),
147
+ endpoints: /* @__PURE__ */ new Map()
148
+ };
149
+ return globalThis.__remnicLocalLlmRateLimitState;
150
+ }
151
+ function getLocalLlmConcurrencyLimiter(maxConcurrency) {
152
+ const { limiter } = globalState();
153
+ limiter.setMax(maxConcurrency);
154
+ return limiter;
155
+ }
156
+ function endpointBackoffRemainingMs(endpoint, nowMs = Date.now()) {
157
+ const entry = globalState().endpoints.get(endpoint);
158
+ return entry ? Math.max(0, entry.untilMs - nowMs) : 0;
159
+ }
160
+ function noteEndpointRateLimited(endpoint) {
161
+ const { endpoints } = globalState();
162
+ const entry = endpoints.get(endpoint) ?? { untilMs: 0, consecutiveRateLimits: 0 };
163
+ entry.consecutiveRateLimits += 1;
164
+ endpoints.set(endpoint, entry);
165
+ return entry.consecutiveRateLimits;
166
+ }
167
+ function extendEndpointBackoff(endpoint, delayMs, nowMs = Date.now()) {
168
+ const { endpoints } = globalState();
169
+ const entry = endpoints.get(endpoint) ?? { untilMs: 0, consecutiveRateLimits: 0 };
170
+ const bounded = Math.min(MAX_RECORDED_BACKOFF_MS, Math.max(0, delayMs));
171
+ entry.untilMs = Math.max(entry.untilMs, nowMs + bounded);
172
+ endpoints.set(endpoint, entry);
173
+ }
174
+ function noteEndpointRecovered(endpoint) {
175
+ const entry = globalState().endpoints.get(endpoint);
176
+ if (entry) entry.consecutiveRateLimits = 0;
177
+ }
178
+ function resetLocalLlmRateLimitStateForTests() {
179
+ const state = globalState();
180
+ state.endpoints.clear();
181
+ state.limiter = new LocalLlmConcurrencyLimiter(DEFAULT_MAX_CONCURRENCY);
182
+ }
183
+ var LocalLlmRateLimitSession = class {
184
+ /**
185
+ * @param waitBudgetMs the caller's per-attempt time budget. Waiting for the
186
+ * endpoint backoff or a concurrency slot never exceeds it, and a backoff
187
+ * longer than it skips the request instead (fail open to the caller).
188
+ */
189
+ constructor(endpoint, settings, highPriority, waitBudgetMs = settings.backoffMaxMs) {
190
+ this.endpoint = endpoint;
191
+ this.highPriority = highPriority;
192
+ this.waitBudgetMs = waitBudgetMs;
193
+ this.settings = settings;
194
+ this.limiter = getLocalLlmConcurrencyLimiter(settings.maxConcurrency);
195
+ }
196
+ endpoint;
197
+ highPriority;
198
+ waitBudgetMs;
199
+ retries = 0;
200
+ limiter;
201
+ settings;
202
+ get retryCount() {
203
+ return this.retries;
204
+ }
205
+ async beforeAttempt(signal) {
206
+ const deadlineMs = Date.now() + Math.max(0, this.waitBudgetMs);
207
+ for (; ; ) {
208
+ const remainingMs = endpointBackoffRemainingMs(this.endpoint);
209
+ if (remainingMs > 0) {
210
+ if (remainingMs > Math.min(this.settings.backoffMaxMs, deadlineMs - Date.now())) {
211
+ return { kind: "skip", reason: "backoff", remainingMs };
212
+ }
213
+ if (!await waitWithSignal(remainingMs, signal)) return { kind: "aborted" };
214
+ continue;
215
+ }
216
+ const release = await this.limiter.acquire({
217
+ highPriority: this.highPriority,
218
+ signal,
219
+ timeoutMs: deadlineMs - Date.now()
220
+ });
221
+ if (!release) return signal?.aborted ? { kind: "aborted" } : { kind: "skip", reason: "queue", remainingMs: 0 };
222
+ if (endpointBackoffRemainingMs(this.endpoint) > 0) {
223
+ release();
224
+ continue;
225
+ }
226
+ return { kind: "go", release };
227
+ }
228
+ }
229
+ onSuccess() {
230
+ noteEndpointRecovered(this.endpoint);
231
+ }
232
+ onResponse(status, retryAfterHeader, bodyText) {
233
+ if (!isRateLimitedResponse(status, bodyText)) {
234
+ noteEndpointRecovered(this.endpoint);
235
+ return { kind: "not-rate-limited" };
236
+ }
237
+ const consecutive = noteEndpointRateLimited(this.endpoint);
238
+ const hintMs = serverRetryHintMs(retryAfterHeader, bodyText);
239
+ const delayMs = computeRateLimitDelayMs(consecutive, this.settings, hintMs);
240
+ extendEndpointBackoff(this.endpoint, delayMs);
241
+ if (this.retries >= this.settings.retryCount || delayMs > Math.min(this.settings.backoffMaxMs, this.waitBudgetMs)) {
242
+ return { kind: "give-up", delayMs, retries: this.retries };
243
+ }
244
+ this.retries += 1;
245
+ return { kind: "retry", delayMs, hintMs, consecutive, retry: this.retries };
246
+ }
247
+ };
248
+ function waitWithSignal(ms, signal) {
249
+ return new Promise((resolve) => {
250
+ if (signal?.aborted) {
251
+ resolve(false);
252
+ return;
253
+ }
254
+ const onAbort = () => {
255
+ clearTimeout(timer);
256
+ resolve(false);
257
+ };
258
+ const timer = setTimeout(() => {
259
+ signal?.removeEventListener("abort", onAbort);
260
+ resolve(true);
261
+ }, ms);
262
+ signal?.addEventListener("abort", onAbort, { once: true });
263
+ });
264
+ }
265
+
266
+ export {
267
+ RATE_LIMIT_STATUSES,
268
+ MAX_RECORDED_BACKOFF_MS,
269
+ DEFAULT_RATE_LIMIT_RETRY_COUNT,
270
+ DEFAULT_RATE_LIMIT_BACKOFF_BASE_MS,
271
+ DEFAULT_RATE_LIMIT_BACKOFF_MAX_MS,
272
+ DEFAULT_MAX_CONCURRENCY,
273
+ resolveRateLimitSettings,
274
+ isRateLimitedResponse,
275
+ parseRetryAfterMs,
276
+ parseTryAgainHintMs,
277
+ serverRetryHintMs,
278
+ computeFullJitterBackoffMs,
279
+ computeRateLimitDelayMs,
280
+ LocalLlmConcurrencyLimiter,
281
+ getLocalLlmConcurrencyLimiter,
282
+ endpointBackoffRemainingMs,
283
+ noteEndpointRateLimited,
284
+ extendEndpointBackoff,
285
+ noteEndpointRecovered,
286
+ resetLocalLlmRateLimitStateForTests,
287
+ LocalLlmRateLimitSession
288
+ };
289
+ //# sourceMappingURL=chunk-3VRMUNWL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/local-llm-rate-limit.ts"],"sourcesContent":["/**\n * Process-wide rate-limit handling for the local LLM client (COS-447).\n *\n * A LiteLLM proxy in front of the local models answers `429 No deployments\n * available` (and `503`/`408`) when every deployment is busy or in cooldown.\n * Before this module the client returned on the first 429 and the caller\n * loop immediately issued the next request, so one daemon could hammer the\n * proxy with thousands of calls per hour while it was saturated.\n *\n * This module provides three pieces that the client composes:\n *\n * - classification of rate-limit responses and of the retry hint\n * (`Retry-After` header as delta-seconds or HTTP-date, falling back to\n * LiteLLM's `Try again in N seconds` body text);\n * - exponential backoff with full jitter;\n * - process-wide state shared by every `LocalLlmClient` instance: a\n * concurrency semaphore for in-flight HTTP attempts and a per-endpoint\n * \"do not call before\" deadline, so one client's 429 slows every client\n * pointed at the same endpoint.\n *\n * State lives on `globalThis` so duplicated module copies in bundled output\n * still share one limiter.\n */\n\nexport const RATE_LIMIT_STATUSES: ReadonlySet<number> = new Set([408, 429, 503]);\n\nconst RATE_LIMIT_BODY_PATTERN = /no deployments available|cooldown_list|all deployments.*cooldown/i;\nconst TRY_AGAIN_PATTERN = /try again in (\\d+(?:\\.\\d+)?) ?(seconds?|secs?|s|milliseconds?|ms|minutes?|mins?)\\b/i;\n\n/** Upper bound on any server-provided wait we will record (sanity clamp). */\nexport const MAX_RECORDED_BACKOFF_MS = 10 * 60_000;\n\nexport const DEFAULT_RATE_LIMIT_RETRY_COUNT = 3;\nexport const DEFAULT_RATE_LIMIT_BACKOFF_BASE_MS = 1_000;\nexport const DEFAULT_RATE_LIMIT_BACKOFF_MAX_MS = 60_000;\nexport const DEFAULT_MAX_CONCURRENCY = 4;\n\nexport interface LocalLlmRateLimitSettings {\n retryCount: number;\n backoffBaseMs: number;\n backoffMaxMs: number;\n /** 0 disables the process-wide concurrency cap. */\n maxConcurrency: number;\n}\n\nfunction nonNegativeInt(value: number | undefined, fallback: number): number {\n if (value === undefined || !Number.isFinite(value) || value < 0) return fallback;\n return Math.floor(value);\n}\n\nexport function resolveRateLimitSettings(config: {\n localLlmRateLimitRetryCount?: number;\n localLlmRateLimitBackoffBaseMs?: number;\n localLlmRateLimitBackoffMaxMs?: number;\n localLlmMaxConcurrency?: number;\n}): LocalLlmRateLimitSettings {\n const backoffBaseMs = Math.max(\n 1,\n nonNegativeInt(config.localLlmRateLimitBackoffBaseMs, DEFAULT_RATE_LIMIT_BACKOFF_BASE_MS)\n );\n const backoffMaxMs = Math.max(\n backoffBaseMs,\n nonNegativeInt(config.localLlmRateLimitBackoffMaxMs, DEFAULT_RATE_LIMIT_BACKOFF_MAX_MS)\n );\n return {\n retryCount: nonNegativeInt(config.localLlmRateLimitRetryCount, DEFAULT_RATE_LIMIT_RETRY_COUNT),\n backoffBaseMs,\n backoffMaxMs,\n maxConcurrency: nonNegativeInt(config.localLlmMaxConcurrency, DEFAULT_MAX_CONCURRENCY),\n };\n}\n\n/**\n * True for responses that mean \"the endpoint is saturated, slow down\":\n * 408/429/503, or a 5xx whose body carries LiteLLM's cooldown wording.\n * 400s are never rate limits (context-window errors mention fallbacks).\n */\nexport function isRateLimitedResponse(status: number, bodyText: string): boolean {\n if (RATE_LIMIT_STATUSES.has(status)) return true;\n return status >= 500 && RATE_LIMIT_BODY_PATTERN.test(bodyText);\n}\n\n/**\n * Parse an HTTP `Retry-After` value (RFC 9110: delta-seconds or HTTP-date).\n * Returns milliseconds to wait, or null when absent/invalid.\n */\nexport function parseRetryAfterMs(value: string | null | undefined, nowMs: number = Date.now()): number | null {\n if (value === null || value === undefined) return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n if (/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n return Math.min(MAX_RECORDED_BACKOFF_MS, Math.round(Number(trimmed) * 1000));\n }\n const dateMs = Date.parse(trimmed);\n if (Number.isNaN(dateMs)) return null;\n return Math.min(MAX_RECORDED_BACKOFF_MS, Math.max(0, dateMs - nowMs));\n}\n\n/** Parse LiteLLM's `Try again in N seconds` hint from an error body. */\nexport function parseTryAgainHintMs(bodyText: string): number | null {\n const match = bodyText.match(TRY_AGAIN_PATTERN);\n if (!match) return null;\n const amount = Number(match[1]);\n if (!Number.isFinite(amount)) return null;\n const unit = match[2].toLowerCase();\n let ms: number;\n if (unit.startsWith(\"ms\") || unit.startsWith(\"milli\")) ms = amount;\n else if (unit.startsWith(\"min\")) ms = amount * 60_000;\n else ms = amount * 1000;\n return Math.min(MAX_RECORDED_BACKOFF_MS, Math.round(ms));\n}\n\n/** Server-provided wait: `Retry-After` header first, then the body hint. */\nexport function serverRetryHintMs(\n retryAfterHeader: string | null | undefined,\n bodyText: string,\n nowMs: number = Date.now()\n): number | null {\n return parseRetryAfterMs(retryAfterHeader, nowMs) ?? parseTryAgainHintMs(bodyText);\n}\n\n/**\n * Exponential backoff with full jitter: uniform in [0, min(max, base * 2^(n-1))].\n * `attempt` is 1-based (the number of consecutive rate-limit responses).\n */\nexport function computeFullJitterBackoffMs(\n attempt: number,\n baseMs: number,\n maxMs: number,\n random: () => number = Math.random\n): number {\n const exponent = Math.max(0, Math.min(30, attempt - 1));\n const ceiling = Math.min(maxMs, baseMs * 2 ** exponent);\n return Math.floor(random() * ceiling);\n}\n\n/**\n * Delay before the next attempt after a rate-limit response. A server hint\n * is a floor (never call earlier than asked); jitter is added on top of the\n * exponential term so synchronized callers spread out.\n */\nexport function computeRateLimitDelayMs(\n attempt: number,\n settings: Pick<LocalLlmRateLimitSettings, \"backoffBaseMs\" | \"backoffMaxMs\">,\n serverHintMs: number | null,\n random: () => number = Math.random\n): number {\n const jittered = computeFullJitterBackoffMs(attempt, settings.backoffBaseMs, settings.backoffMaxMs, random);\n return serverHintMs === null ? jittered : Math.max(serverHintMs, jittered);\n}\n\ntype Waiter = { highPriority: boolean; grant: () => void };\n\n/** FIFO counting semaphore with a high-priority lane (recall-critical first). */\nexport class LocalLlmConcurrencyLimiter {\n private active = 0;\n private max: number;\n private readonly waiters: Waiter[] = [];\n\n constructor(max: number) {\n this.max = max;\n }\n\n setMax(max: number): void {\n this.max = max;\n this.pump();\n }\n\n get inFlight(): number {\n return this.active;\n }\n\n get queued(): number {\n return this.waiters.length;\n }\n\n /**\n * Wait for a slot. Resolves to a release function, or null when the\n * signal aborts first. `max <= 0` means unlimited.\n */\n acquire(\n options: { highPriority?: boolean; signal?: AbortSignal; timeoutMs?: number } = {}\n ): Promise<(() => void) | null> {\n const { signal, timeoutMs } = options;\n if (signal?.aborted) return Promise.resolve(null);\n if (this.max <= 0 || (this.active < this.max && this.waiters.length === 0)) {\n this.active += 1;\n return Promise.resolve(this.releaser());\n }\n if (timeoutMs !== undefined && timeoutMs <= 0) return Promise.resolve(null);\n return new Promise((resolve) => {\n let timer: NodeJS.Timeout | undefined;\n const waiter: Waiter = {\n highPriority: options.highPriority === true,\n grant: () => {\n signal?.removeEventListener(\"abort\", onAbort);\n if (timer) clearTimeout(timer);\n resolve(this.releaser());\n },\n };\n const onAbort = (): void => {\n const index = this.waiters.indexOf(waiter);\n if (index >= 0) this.waiters.splice(index, 1);\n signal?.removeEventListener(\"abort\", onAbort);\n if (timer) clearTimeout(timer);\n resolve(null);\n };\n if (waiter.highPriority) {\n const firstLow = this.waiters.findIndex((w) => !w.highPriority);\n if (firstLow < 0) this.waiters.push(waiter);\n else this.waiters.splice(firstLow, 0, waiter);\n } else {\n this.waiters.push(waiter);\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (timeoutMs !== undefined) timer = setTimeout(onAbort, timeoutMs);\n });\n }\n\n private releaser(): () => void {\n let released = false;\n return () => {\n if (released) return;\n released = true;\n this.active -= 1;\n this.pump();\n };\n }\n\n private pump(): void {\n while (this.waiters.length > 0 && (this.max <= 0 || this.active < this.max)) {\n const next = this.waiters.shift();\n if (!next) break;\n this.active += 1;\n next.grant();\n }\n }\n}\n\ninterface EndpointBackoffState {\n untilMs: number;\n consecutiveRateLimits: number;\n}\n\ninterface RateLimitGlobalState {\n limiter: LocalLlmConcurrencyLimiter;\n endpoints: Map<string, EndpointBackoffState>;\n}\n\ndeclare global {\n // Shared across duplicated module copies in bundled output.\n var __remnicLocalLlmRateLimitState: RateLimitGlobalState | undefined;\n}\n\nfunction globalState(): RateLimitGlobalState {\n globalThis.__remnicLocalLlmRateLimitState ??= {\n limiter: new LocalLlmConcurrencyLimiter(DEFAULT_MAX_CONCURRENCY),\n endpoints: new Map(),\n };\n return globalThis.__remnicLocalLlmRateLimitState;\n}\n\n/** Process-wide limiter, resized to the caller's configured cap. */\nexport function getLocalLlmConcurrencyLimiter(maxConcurrency: number): LocalLlmConcurrencyLimiter {\n const { limiter } = globalState();\n limiter.setMax(maxConcurrency);\n return limiter;\n}\n\n/** Milliseconds until `endpoint` may be called again (0 when clear). */\nexport function endpointBackoffRemainingMs(endpoint: string, nowMs: number = Date.now()): number {\n const entry = globalState().endpoints.get(endpoint);\n return entry ? Math.max(0, entry.untilMs - nowMs) : 0;\n}\n\n/**\n * Record a rate-limit response for `endpoint`. Returns the consecutive\n * rate-limit count (1-based) to feed the backoff exponent.\n */\nexport function noteEndpointRateLimited(endpoint: string): number {\n const { endpoints } = globalState();\n const entry = endpoints.get(endpoint) ?? { untilMs: 0, consecutiveRateLimits: 0 };\n entry.consecutiveRateLimits += 1;\n endpoints.set(endpoint, entry);\n return entry.consecutiveRateLimits;\n}\n\n/** Push the endpoint's \"do not call before\" deadline out to at least `nowMs + delayMs`. */\nexport function extendEndpointBackoff(endpoint: string, delayMs: number, nowMs: number = Date.now()): void {\n const { endpoints } = globalState();\n const entry = endpoints.get(endpoint) ?? { untilMs: 0, consecutiveRateLimits: 0 };\n const bounded = Math.min(MAX_RECORDED_BACKOFF_MS, Math.max(0, delayMs));\n entry.untilMs = Math.max(entry.untilMs, nowMs + bounded);\n endpoints.set(endpoint, entry);\n}\n\n/**\n * Reset the backoff exponent after a non-rate-limited response. An already\n * recorded deadline is left to expire so concurrent callers that saw the\n * 429 still wait it out.\n */\nexport function noteEndpointRecovered(endpoint: string): void {\n const entry = globalState().endpoints.get(endpoint);\n if (entry) entry.consecutiveRateLimits = 0;\n}\n\n/** Test seam: drop all process-wide state. */\nexport function resetLocalLlmRateLimitStateForTests(): void {\n const state = globalState();\n state.endpoints.clear();\n state.limiter = new LocalLlmConcurrencyLimiter(DEFAULT_MAX_CONCURRENCY);\n}\n\nexport type RateLimitAttemptGate =\n | { kind: \"go\"; release: () => void }\n | { kind: \"aborted\" }\n | { kind: \"skip\"; reason: \"backoff\" | \"queue\"; remainingMs: number };\n\nexport type RateLimitDecision =\n | { kind: \"not-rate-limited\" }\n | { kind: \"retry\"; delayMs: number; hintMs: number | null; consecutive: number; retry: number }\n | { kind: \"give-up\"; delayMs: number; retries: number };\n\n/**\n * Per-request driver for the rate-limit policy. The client calls\n * `beforeAttempt` ahead of every HTTP attempt (waits out the shared endpoint\n * deadline, then takes a concurrency slot) and `onResponse` after every\n * non-ok response.\n */\nexport class LocalLlmRateLimitSession {\n private retries = 0;\n private readonly limiter: LocalLlmConcurrencyLimiter;\n readonly settings: LocalLlmRateLimitSettings;\n\n /**\n * @param waitBudgetMs the caller's per-attempt time budget. Waiting for the\n * endpoint backoff or a concurrency slot never exceeds it, and a backoff\n * longer than it skips the request instead (fail open to the caller).\n */\n constructor(\n private readonly endpoint: string,\n settings: LocalLlmRateLimitSettings,\n private readonly highPriority: boolean,\n private readonly waitBudgetMs: number = settings.backoffMaxMs\n ) {\n this.settings = settings;\n this.limiter = getLocalLlmConcurrencyLimiter(settings.maxConcurrency);\n }\n\n get retryCount(): number {\n return this.retries;\n }\n\n async beforeAttempt(signal?: AbortSignal): Promise<RateLimitAttemptGate> {\n const deadlineMs = Date.now() + Math.max(0, this.waitBudgetMs);\n // Recheck the shared deadline after every async wait: another attempt may\n // have recorded or extended a cooldown while this one slept or queued.\n for (;;) {\n const remainingMs = endpointBackoffRemainingMs(this.endpoint);\n if (remainingMs > 0) {\n if (remainingMs > Math.min(this.settings.backoffMaxMs, deadlineMs - Date.now())) {\n return { kind: \"skip\", reason: \"backoff\", remainingMs };\n }\n if (!(await waitWithSignal(remainingMs, signal))) return { kind: \"aborted\" };\n continue;\n }\n const release = await this.limiter.acquire({\n highPriority: this.highPriority,\n signal,\n timeoutMs: deadlineMs - Date.now(),\n });\n if (!release) return signal?.aborted ? { kind: \"aborted\" } : { kind: \"skip\", reason: \"queue\", remainingMs: 0 };\n if (endpointBackoffRemainingMs(this.endpoint) > 0) {\n release();\n continue;\n }\n return { kind: \"go\", release };\n }\n }\n\n onSuccess(): void {\n noteEndpointRecovered(this.endpoint);\n }\n\n onResponse(status: number, retryAfterHeader: string | null | undefined, bodyText: string): RateLimitDecision {\n if (!isRateLimitedResponse(status, bodyText)) {\n noteEndpointRecovered(this.endpoint);\n return { kind: \"not-rate-limited\" };\n }\n const consecutive = noteEndpointRateLimited(this.endpoint);\n const hintMs = serverRetryHintMs(retryAfterHeader, bodyText);\n const delayMs = computeRateLimitDelayMs(consecutive, this.settings, hintMs);\n extendEndpointBackoff(this.endpoint, delayMs);\n if (this.retries >= this.settings.retryCount || delayMs > Math.min(this.settings.backoffMaxMs, this.waitBudgetMs)) {\n return { kind: \"give-up\", delayMs, retries: this.retries };\n }\n this.retries += 1;\n return { kind: \"retry\", delayMs, hintMs, consecutive, retry: this.retries };\n }\n}\n\nfunction waitWithSignal(ms: number, signal?: AbortSignal): Promise<boolean> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve(false);\n return;\n }\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve(false);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve(true);\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n"],"mappings":"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","names":[]}
@@ -19,4 +19,4 @@ export {
19
19
  confidenceTier,
20
20
  SPECULATIVE_TTL_DAYS
21
21
  };
22
- //# sourceMappingURL=chunk-466EGMFQ.js.map
22
+ //# sourceMappingURL=chunk-42KSEX6C.js.map