@graphrefly/graphrefly 0.47.1 → 0.48.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 (303) hide show
  1. package/dist/base/composition/index.cjs +28 -19
  2. package/dist/base/composition/index.cjs.map +1 -1
  3. package/dist/base/composition/index.d.cts +14 -5
  4. package/dist/base/composition/index.d.ts +14 -5
  5. package/dist/base/composition/index.js +9 -9
  6. package/dist/base/index.cjs +294 -164
  7. package/dist/base/index.cjs.map +1 -1
  8. package/dist/base/index.d.cts +2 -2
  9. package/dist/base/index.d.ts +2 -2
  10. package/dist/base/index.js +77 -72
  11. package/dist/base/io/index.cjs +145 -85
  12. package/dist/base/io/index.cjs.map +1 -1
  13. package/dist/base/io/index.d.cts +32 -5
  14. package/dist/base/io/index.d.ts +32 -5
  15. package/dist/base/io/index.js +5 -5
  16. package/dist/base/mutation/index.cjs +21 -0
  17. package/dist/base/mutation/index.cjs.map +1 -1
  18. package/dist/base/mutation/index.d.cts +23 -1
  19. package/dist/base/mutation/index.d.ts +23 -1
  20. package/dist/base/mutation/index.js +3 -1
  21. package/dist/base/sources/browser/index.cjs +18 -12
  22. package/dist/base/sources/browser/index.cjs.map +1 -1
  23. package/dist/base/sources/browser/index.d.cts +20 -2
  24. package/dist/base/sources/browser/index.d.ts +20 -2
  25. package/dist/base/sources/browser/index.js +18 -12
  26. package/dist/base/sources/browser/index.js.map +1 -1
  27. package/dist/base/sources/event/index.cjs +29 -1
  28. package/dist/base/sources/event/index.cjs.map +1 -1
  29. package/dist/base/sources/event/index.d.cts +67 -3
  30. package/dist/base/sources/event/index.d.ts +67 -3
  31. package/dist/base/sources/event/index.js +5 -2
  32. package/dist/base/sources/index.cjs +96 -50
  33. package/dist/base/sources/index.cjs.map +1 -1
  34. package/dist/base/sources/index.d.cts +1 -1
  35. package/dist/base/sources/index.d.ts +1 -1
  36. package/dist/base/sources/index.js +7 -4
  37. package/dist/base/sources/node/index.cjs +43 -37
  38. package/dist/base/sources/node/index.cjs.map +1 -1
  39. package/dist/base/sources/node/index.js +43 -37
  40. package/dist/base/sources/node/index.js.map +1 -1
  41. package/dist/{chunk-J5WFUEO4.js → chunk-23MAWVOJ.js} +3 -3
  42. package/dist/{chunk-YXCPV26R.js → chunk-3REMCHSS.js} +39 -27
  43. package/dist/chunk-3REMCHSS.js.map +1 -0
  44. package/dist/{chunk-CEVNQ74M.js → chunk-3YGXPUHW.js} +2 -2
  45. package/dist/{chunk-CEVNQ74M.js.map → chunk-3YGXPUHW.js.map} +1 -1
  46. package/dist/{chunk-EVYY4X5A.js → chunk-46X2EFQH.js} +16 -5
  47. package/dist/chunk-46X2EFQH.js.map +1 -0
  48. package/dist/{chunk-NY2PYHNC.js → chunk-5UY3PNFY.js} +12 -5
  49. package/dist/chunk-5UY3PNFY.js.map +1 -0
  50. package/dist/{chunk-RGMTUZCL.js → chunk-65OM4XLQ.js} +50 -4
  51. package/dist/chunk-65OM4XLQ.js.map +1 -0
  52. package/dist/{chunk-3PSLNJDU.js → chunk-6DQYBIHW.js} +314 -49
  53. package/dist/chunk-6DQYBIHW.js.map +1 -0
  54. package/dist/{chunk-LDCSZ72P.js → chunk-6YBER5UP.js} +3 -3
  55. package/dist/{chunk-LDCSZ72P.js.map → chunk-6YBER5UP.js.map} +1 -1
  56. package/dist/{chunk-7EGRP2VX.js → chunk-7BULJTL6.js} +2 -2
  57. package/dist/{chunk-7EGRP2VX.js.map → chunk-7BULJTL6.js.map} +1 -1
  58. package/dist/{chunk-VLAGJZSL.js → chunk-7T7WLEPM.js} +25 -4
  59. package/dist/chunk-7T7WLEPM.js.map +1 -0
  60. package/dist/{chunk-PKPO3JTZ.js → chunk-AQAKDE7F.js} +29 -11
  61. package/dist/chunk-AQAKDE7F.js.map +1 -0
  62. package/dist/{chunk-2OB3CEJS.js → chunk-B5Y5GPD5.js} +2 -2
  63. package/dist/{chunk-BXGZFGZ4.js → chunk-C5QD5DQX.js} +22 -1
  64. package/dist/chunk-C5QD5DQX.js.map +1 -0
  65. package/dist/{chunk-4XCHZRUJ.js → chunk-D5YGR4TP.js} +58 -7
  66. package/dist/chunk-D5YGR4TP.js.map +1 -0
  67. package/dist/{chunk-NPRP3MCV.js → chunk-DHDCOOJU.js} +2 -2
  68. package/dist/chunk-DHDCOOJU.js.map +1 -0
  69. package/dist/{chunk-MTTRCEJT.js → chunk-DVTDF5OI.js} +2 -2
  70. package/dist/{chunk-SOOKUYVM.js → chunk-F7EKHR32.js} +13 -9
  71. package/dist/chunk-F7EKHR32.js.map +1 -0
  72. package/dist/{chunk-A7KV5UK4.js → chunk-G7H6PN7P.js} +2 -2
  73. package/dist/{chunk-OCUDSN63.js → chunk-GGKHHG5Y.js} +110 -64
  74. package/dist/chunk-GGKHHG5Y.js.map +1 -0
  75. package/dist/{chunk-RAGGHLCV.js → chunk-GUNIRPEJ.js} +8 -6
  76. package/dist/{chunk-RAGGHLCV.js.map → chunk-GUNIRPEJ.js.map} +1 -1
  77. package/dist/{chunk-YJ4U2D2C.js → chunk-J5TBZFBD.js} +9 -7
  78. package/dist/chunk-J5TBZFBD.js.map +1 -0
  79. package/dist/{chunk-Y52CS6YA.js → chunk-JA67ZQG2.js} +2 -2
  80. package/dist/{chunk-Y52CS6YA.js.map → chunk-JA67ZQG2.js.map} +1 -1
  81. package/dist/{chunk-U225SKB4.js → chunk-K4ZYJ4EM.js} +569 -424
  82. package/dist/chunk-K4ZYJ4EM.js.map +1 -0
  83. package/dist/{chunk-Z4YXAUDN.js → chunk-KUFXLAEY.js} +11 -7
  84. package/dist/{chunk-Z4YXAUDN.js.map → chunk-KUFXLAEY.js.map} +1 -1
  85. package/dist/{chunk-IHTWQEDR.js → chunk-LTSI7ULC.js} +3 -3
  86. package/dist/{chunk-IHTWQEDR.js.map → chunk-LTSI7ULC.js.map} +1 -1
  87. package/dist/{chunk-DKNHAICT.js → chunk-MMHGYX44.js} +25 -9
  88. package/dist/chunk-MMHGYX44.js.map +1 -0
  89. package/dist/{chunk-K7PDZYQE.js → chunk-MQMTRKY3.js} +129 -50
  90. package/dist/chunk-MQMTRKY3.js.map +1 -0
  91. package/dist/{chunk-42FQ27MQ.js → chunk-MTODGQBR.js} +44 -179
  92. package/dist/chunk-MTODGQBR.js.map +1 -0
  93. package/dist/{chunk-O3MT7DYI.js → chunk-N6MNJNHB.js} +2 -2
  94. package/dist/{chunk-FVINAAKA.js → chunk-NBK6QQMG.js} +14 -13
  95. package/dist/{chunk-FVINAAKA.js.map → chunk-NBK6QQMG.js.map} +1 -1
  96. package/dist/{chunk-DM4OMPWK.js → chunk-NSA5K5G2.js} +2 -2
  97. package/dist/{chunk-MLTPJMH6.js → chunk-QQYULEZL.js} +2 -2
  98. package/dist/chunk-QSW4DFKE.js +31 -0
  99. package/dist/chunk-QSW4DFKE.js.map +1 -0
  100. package/dist/{chunk-PZWISPIQ.js → chunk-S7HN5FHL.js} +17 -11
  101. package/dist/chunk-S7HN5FHL.js.map +1 -0
  102. package/dist/{chunk-4S53H2KR.js → chunk-SUNCHMML.js} +2 -2
  103. package/dist/{chunk-4GYMCUDZ.js → chunk-T2U6N3FV.js} +7 -7
  104. package/dist/{chunk-RJOG4IJU.js → chunk-T5URUIIY.js} +50 -35
  105. package/dist/chunk-T5URUIIY.js.map +1 -0
  106. package/dist/{chunk-B4AKFXGE.js → chunk-TPTZZV25.js} +6 -6
  107. package/dist/chunk-TPTZZV25.js.map +1 -0
  108. package/dist/{chunk-BU3SEFA5.js → chunk-V46JWFGV.js} +7 -6
  109. package/dist/chunk-V46JWFGV.js.map +1 -0
  110. package/dist/{chunk-IJRR6YAI.js → chunk-VLDRAMP7.js} +18 -12
  111. package/dist/chunk-VLDRAMP7.js.map +1 -0
  112. package/dist/{chunk-6XZYT4SW.js → chunk-X6ESZDR6.js} +8 -9
  113. package/dist/chunk-X6ESZDR6.js.map +1 -0
  114. package/dist/{chunk-E5OZPDIW.js → chunk-X7BA5PWG.js} +7 -5
  115. package/dist/chunk-X7BA5PWG.js.map +1 -0
  116. package/dist/{chunk-CXANAIZU.js → chunk-XEWV254I.js} +3 -3
  117. package/dist/{chunk-CXANAIZU.js.map → chunk-XEWV254I.js.map} +1 -1
  118. package/dist/{chunk-V4Y3TM7U.js → chunk-YBJVKMTM.js} +38 -16
  119. package/dist/chunk-YBJVKMTM.js.map +1 -0
  120. package/dist/{chunk-7ADWWI2T.js → chunk-ZW32BPXV.js} +17 -6
  121. package/dist/chunk-ZW32BPXV.js.map +1 -0
  122. package/dist/compat/index.cjs +52 -5
  123. package/dist/compat/index.cjs.map +1 -1
  124. package/dist/compat/index.d.cts +1 -1
  125. package/dist/compat/index.d.ts +1 -1
  126. package/dist/compat/index.js +7 -7
  127. package/dist/compat/nestjs/index.cjs +52 -5
  128. package/dist/compat/nestjs/index.cjs.map +1 -1
  129. package/dist/compat/nestjs/index.d.cts +1 -1
  130. package/dist/compat/nestjs/index.d.ts +1 -1
  131. package/dist/compat/nestjs/index.js +4 -4
  132. package/dist/{fallback-Bx46zqky.d.cts → fallback-BROR6ZhO.d.cts} +1 -1
  133. package/dist/{fallback-pIWW8A2d.d.ts → fallback-DO80aM_3.d.ts} +1 -1
  134. package/dist/{index-B_p8tnvf.d.cts → index-D1z3XcF9.d.cts} +1 -0
  135. package/dist/{index-_HDSmPyp.d.ts → index-DZ6yua0Q.d.ts} +1 -0
  136. package/dist/index.cjs +2387 -1707
  137. package/dist/index.cjs.map +1 -1
  138. package/dist/index.d.cts +10 -10
  139. package/dist/index.d.ts +10 -10
  140. package/dist/index.js +173 -150
  141. package/dist/index.js.map +1 -1
  142. package/dist/presets/ai/index.cjs +88 -26
  143. package/dist/presets/ai/index.cjs.map +1 -1
  144. package/dist/presets/ai/index.js +14 -14
  145. package/dist/presets/harness/index.cjs +183 -51
  146. package/dist/presets/harness/index.cjs.map +1 -1
  147. package/dist/presets/harness/index.d.cts +15 -5
  148. package/dist/presets/harness/index.d.ts +15 -5
  149. package/dist/presets/harness/index.js +26 -26
  150. package/dist/presets/index.cjs +298 -101
  151. package/dist/presets/index.cjs.map +1 -1
  152. package/dist/presets/index.d.cts +2 -2
  153. package/dist/presets/index.d.ts +2 -2
  154. package/dist/presets/index.js +49 -49
  155. package/dist/presets/inspect/index.cjs +63 -14
  156. package/dist/presets/inspect/index.cjs.map +1 -1
  157. package/dist/presets/inspect/index.d.cts +1 -1
  158. package/dist/presets/inspect/index.d.ts +1 -1
  159. package/dist/presets/inspect/index.js +6 -6
  160. package/dist/presets/resilience/index.cjs +64 -44
  161. package/dist/presets/resilience/index.cjs.map +1 -1
  162. package/dist/presets/resilience/index.d.cts +12 -8
  163. package/dist/presets/resilience/index.d.ts +12 -8
  164. package/dist/presets/resilience/index.js +6 -6
  165. package/dist/{rate-limiter-DpVbSYdH.d.cts → rate-limiter-DC26FM8J.d.cts} +10 -1
  166. package/dist/{rate-limiter-CEALq4N1.d.ts → rate-limiter-DyWpwpQP.d.ts} +10 -1
  167. package/dist/{reactive-layout-fswlBUvX.d.ts → reactive-layout-BBBWH0V_.d.cts} +85 -4
  168. package/dist/{reactive-layout-fswlBUvX.d.cts → reactive-layout-BBBWH0V_.d.ts} +85 -4
  169. package/dist/solutions/index.cjs +239 -92
  170. package/dist/solutions/index.cjs.map +1 -1
  171. package/dist/solutions/index.d.cts +2 -2
  172. package/dist/solutions/index.d.ts +2 -2
  173. package/dist/solutions/index.js +32 -32
  174. package/dist/{spawnable-5mDY501F.d.cts → spawnable-B2IlW60f.d.cts} +23 -2
  175. package/dist/{spawnable-D3lR0oQu.d.ts → spawnable-tttFz2Nh.d.ts} +23 -2
  176. package/dist/testing/index.cjs +94 -0
  177. package/dist/testing/index.cjs.map +1 -0
  178. package/dist/testing/index.d.cts +59 -0
  179. package/dist/testing/index.d.ts +59 -0
  180. package/dist/testing/index.js +73 -0
  181. package/dist/testing/index.js.map +1 -0
  182. package/dist/{timeout-U5O4ESK3.js → timeout-BEABACRP.js} +2 -2
  183. package/dist/utils/ai/browser.cjs.map +1 -1
  184. package/dist/utils/ai/browser.d.cts +2 -2
  185. package/dist/utils/ai/browser.d.ts +2 -2
  186. package/dist/utils/ai/browser.js +10 -10
  187. package/dist/utils/ai/browser.js.map +1 -1
  188. package/dist/utils/ai/index.cjs +291 -191
  189. package/dist/utils/ai/index.cjs.map +1 -1
  190. package/dist/utils/ai/index.d.cts +108 -12
  191. package/dist/utils/ai/index.d.ts +108 -12
  192. package/dist/utils/ai/index.js +23 -21
  193. package/dist/utils/ai/node.cjs.map +1 -1
  194. package/dist/utils/ai/node.d.cts +5 -5
  195. package/dist/utils/ai/node.d.ts +5 -5
  196. package/dist/utils/ai/node.js +3 -3
  197. package/dist/utils/ai/node.js.map +1 -1
  198. package/dist/utils/cqrs/index.cjs +29 -3
  199. package/dist/utils/cqrs/index.cjs.map +1 -1
  200. package/dist/utils/cqrs/index.d.cts +12 -7
  201. package/dist/utils/cqrs/index.d.ts +12 -7
  202. package/dist/utils/cqrs/index.js +2 -2
  203. package/dist/utils/demo-shell/index.cjs +45 -19
  204. package/dist/utils/demo-shell/index.cjs.map +1 -1
  205. package/dist/utils/demo-shell/index.d.cts +1 -1
  206. package/dist/utils/demo-shell/index.d.ts +1 -1
  207. package/dist/utils/demo-shell/index.js +2 -2
  208. package/dist/utils/domain-templates/index.cjs +1 -1
  209. package/dist/utils/domain-templates/index.cjs.map +1 -1
  210. package/dist/utils/domain-templates/index.js +3 -3
  211. package/dist/utils/graphspec/index.cjs +1 -1
  212. package/dist/utils/graphspec/index.cjs.map +1 -1
  213. package/dist/utils/graphspec/index.js +3 -3
  214. package/dist/utils/harness/index.cjs +16 -10
  215. package/dist/utils/harness/index.cjs.map +1 -1
  216. package/dist/utils/harness/index.js +1 -1
  217. package/dist/utils/index.cjs +1692 -1192
  218. package/dist/utils/index.cjs.map +1 -1
  219. package/dist/utils/index.d.cts +7 -7
  220. package/dist/utils/index.d.ts +7 -7
  221. package/dist/utils/index.js +77 -59
  222. package/dist/utils/inspect/index.cjs +52 -4
  223. package/dist/utils/inspect/index.cjs.map +1 -1
  224. package/dist/utils/inspect/index.d.cts +32 -3
  225. package/dist/utils/inspect/index.d.ts +32 -3
  226. package/dist/utils/inspect/index.js +4 -4
  227. package/dist/utils/job-queue/index.cjs +46 -9
  228. package/dist/utils/job-queue/index.cjs.map +1 -1
  229. package/dist/utils/job-queue/index.d.cts +33 -3
  230. package/dist/utils/job-queue/index.d.ts +33 -3
  231. package/dist/utils/job-queue/index.js +2 -2
  232. package/dist/utils/memory/index.cjs +570 -425
  233. package/dist/utils/memory/index.cjs.map +1 -1
  234. package/dist/utils/memory/index.d.cts +261 -33
  235. package/dist/utils/memory/index.d.ts +261 -33
  236. package/dist/utils/memory/index.js +10 -2
  237. package/dist/utils/messaging/index.cjs.map +1 -1
  238. package/dist/utils/messaging/index.d.cts +4 -3
  239. package/dist/utils/messaging/index.d.ts +4 -3
  240. package/dist/utils/messaging/index.js +2 -2
  241. package/dist/utils/orchestration/index.cjs +14 -3
  242. package/dist/utils/orchestration/index.cjs.map +1 -1
  243. package/dist/utils/orchestration/index.js +3 -3
  244. package/dist/utils/process/index.cjs +32 -2
  245. package/dist/utils/process/index.cjs.map +1 -1
  246. package/dist/utils/process/index.d.cts +4 -3
  247. package/dist/utils/process/index.d.ts +4 -3
  248. package/dist/utils/process/index.js +3 -3
  249. package/dist/utils/reactive-layout/index.cjs +184 -55
  250. package/dist/utils/reactive-layout/index.cjs.map +1 -1
  251. package/dist/utils/reactive-layout/index.d.cts +128 -3
  252. package/dist/utils/reactive-layout/index.d.ts +128 -3
  253. package/dist/utils/reactive-layout/index.js +16 -8
  254. package/dist/utils/reduction/index.cjs +1 -1
  255. package/dist/utils/reduction/index.cjs.map +1 -1
  256. package/dist/utils/reduction/index.js +2 -2
  257. package/dist/utils/resilience/index.cjs +64 -43
  258. package/dist/utils/resilience/index.cjs.map +1 -1
  259. package/dist/utils/resilience/index.d.cts +1 -1
  260. package/dist/utils/resilience/index.d.ts +1 -1
  261. package/dist/utils/resilience/index.js +5 -5
  262. package/dist/utils/surface/index.cjs +1 -1
  263. package/dist/utils/surface/index.cjs.map +1 -1
  264. package/dist/utils/surface/index.js +4 -4
  265. package/package.json +15 -3
  266. package/dist/chunk-3PSLNJDU.js.map +0 -1
  267. package/dist/chunk-42FQ27MQ.js.map +0 -1
  268. package/dist/chunk-4XCHZRUJ.js.map +0 -1
  269. package/dist/chunk-6XZYT4SW.js.map +0 -1
  270. package/dist/chunk-7ADWWI2T.js.map +0 -1
  271. package/dist/chunk-B4AKFXGE.js.map +0 -1
  272. package/dist/chunk-BU3SEFA5.js.map +0 -1
  273. package/dist/chunk-BXGZFGZ4.js.map +0 -1
  274. package/dist/chunk-DKNHAICT.js.map +0 -1
  275. package/dist/chunk-E5OZPDIW.js.map +0 -1
  276. package/dist/chunk-EVYY4X5A.js.map +0 -1
  277. package/dist/chunk-IJRR6YAI.js.map +0 -1
  278. package/dist/chunk-K7PDZYQE.js.map +0 -1
  279. package/dist/chunk-NPRP3MCV.js.map +0 -1
  280. package/dist/chunk-NY2PYHNC.js.map +0 -1
  281. package/dist/chunk-OCUDSN63.js.map +0 -1
  282. package/dist/chunk-PKPO3JTZ.js.map +0 -1
  283. package/dist/chunk-PZWISPIQ.js.map +0 -1
  284. package/dist/chunk-RGMTUZCL.js.map +0 -1
  285. package/dist/chunk-RJOG4IJU.js.map +0 -1
  286. package/dist/chunk-SOOKUYVM.js.map +0 -1
  287. package/dist/chunk-U225SKB4.js.map +0 -1
  288. package/dist/chunk-V4Y3TM7U.js.map +0 -1
  289. package/dist/chunk-VLAGJZSL.js.map +0 -1
  290. package/dist/chunk-W2BOPXTI.js +0 -1
  291. package/dist/chunk-YJ4U2D2C.js.map +0 -1
  292. package/dist/chunk-YXCPV26R.js.map +0 -1
  293. package/dist/timeout-U5O4ESK3.js.map +0 -1
  294. /package/dist/{chunk-J5WFUEO4.js.map → chunk-23MAWVOJ.js.map} +0 -0
  295. /package/dist/{chunk-2OB3CEJS.js.map → chunk-B5Y5GPD5.js.map} +0 -0
  296. /package/dist/{chunk-MTTRCEJT.js.map → chunk-DVTDF5OI.js.map} +0 -0
  297. /package/dist/{chunk-A7KV5UK4.js.map → chunk-G7H6PN7P.js.map} +0 -0
  298. /package/dist/{chunk-O3MT7DYI.js.map → chunk-N6MNJNHB.js.map} +0 -0
  299. /package/dist/{chunk-DM4OMPWK.js.map → chunk-NSA5K5G2.js.map} +0 -0
  300. /package/dist/{chunk-MLTPJMH6.js.map → chunk-QQYULEZL.js.map} +0 -0
  301. /package/dist/{chunk-4S53H2KR.js.map → chunk-SUNCHMML.js.map} +0 -0
  302. /package/dist/{chunk-4GYMCUDZ.js.map → chunk-T2U6N3FV.js.map} +0 -0
  303. /package/dist/{chunk-W2BOPXTI.js.map → timeout-BEABACRP.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/base/composition/external-register.ts"],"sourcesContent":["/**\n * External-register helpers — the common `register({emit, error, complete})`\n * contract shared by webhook, MCP, syslog, StatsD, OTel and other callback-\n * based integrations. Absorbs the `active` flag that every such adapter needs\n * to guard against emits after teardown (§5.10 boundary pattern).\n *\n * Two shapes:\n *\n * - {@link externalProducer} — single channel. Lazy activation: the register\n * fn runs when the node gains its first subscriber; its returned cleanup\n * runs on deactivation.\n *\n * - {@link externalBundle} — multiple named channels. Eager activation: the\n * register fn runs at bundle construction time so externally-owned servers\n * (HTTP endpoints, UDP sockets) start accepting traffic immediately. A\n * shared refcount fires the returned cleanup once every channel has fully\n * torn down.\n */\n\nimport {\n\tbatch,\n\tCOMPLETE,\n\tDATA,\n\tERROR,\n\ttype Node,\n\ttype NodeOptions,\n\tnode,\n} from \"@graphrefly/pure-ts/core\";\n\ntype ExtraOpts = Omit<NodeOptions<unknown>, \"describeKind\">;\n\nfunction sourceOpts<T>(opts?: ExtraOpts): NodeOptions<T> {\n\treturn { describeKind: \"producer\", ...opts } as NodeOptions<T>;\n}\n\n/**\n * Standard emit-triad passed to a single-channel external registrar.\n *\n * Post-teardown calls on any of these are automatically no-ops — the\n * registrar does not need its own guard flag.\n *\n * @category extra\n */\nexport type EmitTriad<T> = {\n\t/** Emit a value as `DATA`. */\n\temit: (value: T) => void;\n\t/** Terminate with `ERROR`. Subsequent `emit` / `error` / `complete` are ignored. */\n\terror: (err: unknown) => void;\n\t/** Terminate with `COMPLETE`. Subsequent `emit` / `error` / `complete` are ignored. */\n\tcomplete: () => void;\n};\n\n/**\n * Multi-channel emit bundle. Each declared channel name maps to an emit fn;\n * `error` and `complete` terminate every channel atomically.\n *\n * @category extra\n */\nexport type BundleTriad<TChannels extends Record<string, unknown>> = {\n\t[K in keyof TChannels]: (value: TChannels[K]) => void;\n} & {\n\t/** Terminate every channel with `ERROR`. */\n\terror: (err: unknown) => void;\n\t/** Terminate every channel with `COMPLETE`. */\n\tcomplete: () => void;\n};\n\n/**\n * Generic external registrator contract. The caller installs handlers into a\n * third-party library / framework / server and optionally returns a cleanup\n * callback. Returning `undefined` / `void` is equivalent to a no-op cleanup.\n *\n * @category extra\n */\nexport type ExternalRegister<H> = (handlers: H) => (() => void) | undefined;\n\n/**\n * Wraps a callback-style external integration as a reactive source.\n *\n * The registrar installs the supplied `emit` / `error` / `complete` handlers\n * into the external SDK; post-teardown calls are silently dropped. Synchronous\n * exceptions thrown by the registrar surface as terminal `ERROR`.\n *\n * @param register - Installs handlers. Optionally returns a cleanup fn.\n * @param opts - Node options (name, equals, resubscribable, ...).\n *\n * @example\n * ```ts\n * import { externalProducer } from \"@graphrefly/graphrefly-ts\";\n *\n * const hook$ = externalProducer<Payload>(({ emit, error }) => {\n * const id = transport.onMessage((raw) => {\n * try { emit(parse(raw)); } catch (e) { error(e); }\n * });\n * return () => transport.off(id);\n * });\n * ```\n *\n * @category extra\n */\nexport function externalProducer<T = unknown>(\n\tregister: ExternalRegister<EmitTriad<T>>,\n\topts?: ExtraOpts,\n): Node<T> {\n\treturn node<T>((_data, a) => {\n\t\tlet active = true;\n\t\tconst triad: EmitTriad<T> = {\n\t\t\temit(value) {\n\t\t\t\tif (!active) return;\n\t\t\t\ta.emit(value);\n\t\t\t},\n\t\t\terror(err) {\n\t\t\t\tif (!active) return;\n\t\t\t\tactive = false;\n\t\t\t\ta.down([[ERROR, err]]);\n\t\t\t},\n\t\t\tcomplete() {\n\t\t\t\tif (!active) return;\n\t\t\t\tactive = false;\n\t\t\t\ta.down([[COMPLETE]]);\n\t\t\t},\n\t\t};\n\t\tlet cleanup: (() => void) | undefined;\n\t\ttry {\n\t\t\tconst ret = register(triad);\n\t\t\tcleanup = typeof ret === \"function\" ? ret : undefined;\n\t\t} catch (err) {\n\t\t\ttriad.error(err);\n\t\t\treturn {\n\t\t\t\tonDeactivation: () => {\n\t\t\t\t\tactive = false;\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tonDeactivation: () => {\n\t\t\t\tactive = false;\n\t\t\t\ttry {\n\t\t\t\t\tcleanup?.();\n\t\t\t\t} catch {\n\t\t\t\t\t/* registrar cleanup failure is not a reactive signal */\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}, sourceOpts(opts));\n}\n\n/**\n * Options for {@link externalBundle}.\n *\n * @category extra\n */\nexport type ExternalBundleOptions<TChannels extends Record<string, unknown>> = {\n\t/** Base name prefix for channel nodes; each node is named `${name}::${channel}`. */\n\tname?: string;\n\t/** Per-channel node options (equals, resubscribable, ...). */\n\tchannelOpts?: { [K in keyof TChannels]?: ExtraOpts };\n};\n\n/**\n * Multi-channel variant — one `Node<T>` per named channel, sharing a single\n * registrar. Activation is eager: the registrar runs at construction time so\n * externally-owned servers (HTTP, UDP, queue consumers) can start accepting\n * traffic immediately. The returned cleanup fires once every channel has been\n * subscribed and then fully deactivated (refcount-on-teardown).\n *\n * Any call to `error` or `complete` propagates to every channel atomically.\n *\n * @param register - Installs handlers for each channel plus shared error/complete.\n * @param channels - Ordered channel names; determines the returned object shape.\n * @param opts - Optional name prefix and per-channel node options.\n *\n * @example\n * ```ts\n * import { externalBundle } from \"@graphrefly/graphrefly-ts\";\n *\n * type OTelChannels = { traces: Span; metrics: Metric; logs: LogRec };\n * const otel = externalBundle<OTelChannels>(\n * ({ traces, metrics, logs, error }) => {\n * app.post(\"/v1/traces\", (req, res) => { traces(req.body); res.sendStatus(200); });\n * app.post(\"/v1/metrics\", (req, res) => { metrics(req.body); res.sendStatus(200); });\n * app.post(\"/v1/logs\", (req, res) => { logs(req.body); res.sendStatus(200); });\n * server.on(\"error\", error);\n * return () => server.close();\n * },\n * [\"traces\", \"metrics\", \"logs\"],\n * );\n * otel.traces.subscribe(...);\n * ```\n *\n * @category extra\n */\nexport function externalBundle<TChannels extends Record<string, unknown>>(\n\tregister: ExternalRegister<BundleTriad<TChannels>>,\n\tchannels: readonly (keyof TChannels & string)[],\n\topts?: ExternalBundleOptions<TChannels>,\n): { [K in keyof TChannels]: Node<TChannels[K]> } & { dispose(): void } {\n\tlet active = true;\n\tlet cleanup: (() => void) | undefined;\n\tlet activatedCount = 0;\n\tlet teardownCount = 0;\n\n\tconst nodes = {} as { [K in keyof TChannels]: Node<TChannels[K]> };\n\tconst channelNodes: Array<Node<unknown>> = [];\n\n\tconst finishCleanup = () => {\n\t\tconst fn = cleanup;\n\t\tcleanup = undefined;\n\t\ttry {\n\t\t\tfn?.();\n\t\t} catch {\n\t\t\t/* registrar cleanup failure is not a reactive signal */\n\t\t}\n\t};\n\n\tfor (const ch of channels) {\n\t\tconst name = opts?.name ? `${opts.name}::${ch}` : ch;\n\t\tconst chOpts = opts?.channelOpts?.[ch];\n\t\tconst n = node<TChannels[typeof ch]>(\n\t\t\t(_data, _a) => {\n\t\t\t\tactivatedCount++;\n\t\t\t\treturn {\n\t\t\t\t\tonDeactivation: () => {\n\t\t\t\t\t\tteardownCount++;\n\t\t\t\t\t\t// Cleanup fires once every channel has activated at least once\n\t\t\t\t\t\t// and then deactivated. Channels that never subscribe do not\n\t\t\t\t\t\t// gate cleanup — use the explicit `.dispose()` method for\n\t\t\t\t\t\t// unconditional teardown.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tactivatedCount > 0 &&\n\t\t\t\t\t\t\tteardownCount >= activatedCount &&\n\t\t\t\t\t\t\tteardownCount >= channels.length\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tfinishCleanup();\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\tsourceOpts({ ...chOpts, name }),\n\t\t);\n\t\tnodes[ch as keyof TChannels] = n as Node<TChannels[typeof ch]>;\n\t\tchannelNodes.push(n as Node<unknown>);\n\t}\n\n\tconst bundle = {} as BundleTriad<TChannels>;\n\tfor (const ch of channels) {\n\t\t(bundle as Record<string, unknown>)[ch] = (value: unknown) => {\n\t\t\tif (!active) return;\n\t\t\t(nodes[ch as keyof TChannels] as Node<unknown>).down([[DATA, value]]);\n\t\t};\n\t}\n\tbundle.error = (err: unknown) => {\n\t\tif (!active) return;\n\t\tactive = false;\n\t\tbatch(() => {\n\t\t\tfor (const n of channelNodes) n.down([[ERROR, err]]);\n\t\t});\n\t\tfinishCleanup();\n\t};\n\tbundle.complete = () => {\n\t\tif (!active) return;\n\t\tactive = false;\n\t\tbatch(() => {\n\t\t\tfor (const n of channelNodes) n.down([[COMPLETE]]);\n\t\t});\n\t\tfinishCleanup();\n\t};\n\n\t// Eager activation — register fires at construction time so externally-\n\t// owned servers can start accepting traffic immediately. Synchronous throws\n\t// propagate to the caller (no subscribers exist yet, so there is no\n\t// reactive ERROR path to deliver to). This matches the existing `fromOTel`\n\t// contract.\n\tconst ret = register(bundle);\n\tcleanup = typeof ret === \"function\" ? ret : undefined;\n\n\tconst dispose = () => {\n\t\tif (!active) return;\n\t\tactive = false;\n\t\t// Fire COMPLETE on every channel so downstream sees a clean terminal.\n\t\tbatch(() => {\n\t\t\tfor (const n of channelNodes) {\n\t\t\t\ttry {\n\t\t\t\t\tn.down([[COMPLETE]]);\n\t\t\t\t} catch {\n\t\t\t\t\t/* terminal filter / re-entrance — swallow */\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tfinishCleanup();\n\t};\n\n\treturn Object.assign(nodes, { dispose });\n}\n"],"mappings":"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","names":[]}
@@ -4,22 +4,22 @@ import {
4
4
  fallback,
5
5
  rateLimiter,
6
6
  withBreaker
7
- } from "./chunk-RJOG4IJU.js";
7
+ } from "./chunk-T5URUIIY.js";
8
8
  import {
9
9
  withTimeout
10
- } from "./chunk-RAGGHLCV.js";
11
- import {
12
- domainMeta
13
- } from "./chunk-FMPF42Q4.js";
10
+ } from "./chunk-GUNIRPEJ.js";
14
11
  import {
15
12
  withStatus
16
- } from "./chunk-Y52CS6YA.js";
13
+ } from "./chunk-JA67ZQG2.js";
17
14
  import {
18
15
  retry
19
- } from "./chunk-Z4YXAUDN.js";
16
+ } from "./chunk-KUFXLAEY.js";
20
17
  import {
21
18
  NS_PER_MS
22
19
  } from "./chunk-P5LBT622.js";
20
+ import {
21
+ domainMeta
22
+ } from "./chunk-FMPF42Q4.js";
23
23
 
24
24
  // src/presets/resilience/resilient-pipeline.ts
25
25
  import { ERROR, node, placeholderArgs } from "@graphrefly/pure-ts/core";
@@ -105,7 +105,6 @@ var ResilientPipelineGraph = class extends Graph {
105
105
  } else {
106
106
  const rateOpts = {
107
107
  ...opts.rateLimit,
108
- maxBuffer: opts.rateLimit.maxBuffer ?? Infinity,
109
108
  meta: domainMeta("resilient", "rate-limit")
110
109
  };
111
110
  const bundle = rateLimiter(current, rateOpts);
@@ -253,4 +252,4 @@ export {
253
252
  ResilientPipelineGraph,
254
253
  resilientPipeline
255
254
  };
256
- //# sourceMappingURL=chunk-6XZYT4SW.js.map
255
+ //# sourceMappingURL=chunk-X6ESZDR6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presets/resilience/resilient-pipeline.ts"],"sourcesContent":["/**\n * Resilience composition with correct nesting order (roadmap §9.0b — Tier 5.2 Wave-B rebuild).\n *\n * {@link resilientPipeline} composes the resilience primitives from\n * `extra/resilience/` in the canonical nesting order:\n *\n * ```text\n * rateLimit → budget → breaker → timeout → retry → fallback → status\n * ```\n *\n * Returns a {@link ResilientPipelineGraph} (Graph subclass) with mounted\n * intermediate nodes and per-layer status companions, replacing the prior\n * bundle return. Each intermediate is mounted under a stable name so\n * `pipeline.describe()` shows the resilience chain in topology snapshots,\n * mermaid renders, and `lens.health` aggregations.\n *\n * **Per-attempt timeout vs. retry ordering.** `timeout` is applied BEFORE\n * `retry` so each retry attempt resubscribes to a fresh deadline (per-attempt\n * semantics). If `timeout` wrapped `retry`, a single deadline would apply to\n * the entire retry chain — not what callers expect.\n *\n * **`breakerOnOpen` + `retry` interaction.** With `breakerOnOpen: \"error\"` AND\n * `retry`, retry sees `CircuitOpenError` and resubscribes; the next attempt\n * very likely also breaker-open → another error → retry burns its budget\n * against an open circuit. Either set retry's `backoff` long enough for the\n * breaker reset window OR keep the default `breakerOnOpen: \"skip\"` (emits\n * RESOLVED when open; downstream drops the beat without retry firing).\n *\n * **Reactive options (switchMap rebuild).** Every primitive option accepts a\n * `T | Node<T>` (precedent-aligned with `FallbackInput<T>`). When the caller\n * supplies a static value, the layer is built once at construction. When the\n * caller supplies a `Node<T>`, the pipeline subscribes via `switchMap` and\n * **rebuilds the layer on every option emission** — the chain stalls until\n * the option Node emits its first DATA. Each rebuild creates a fresh\n * primitive instance, so internal state is lost (rate-limiter pending buffer,\n * breaker failure count, retry attempt count, in-flight timeout). Per-layer\n * **companion Nodes** (`droppedCount`, `rateLimitState`, `breakerState`) are\n * therefore exposed ONLY for the static-options path. Primitive-side widening\n * (filed in `docs/optimizations.md` under \"Tier 5.2 follow-up — primitive-side\n * reactive-options widening\") will preserve internal state once it lands and\n * the pipeline will trivially forward Node-form options to the primitive.\n *\n * @module\n */\nimport { ERROR, type Node, node, placeholderArgs } from \"@graphrefly/pure-ts/core\";\nimport { switchMap } from \"@graphrefly/pure-ts/extra\";\nimport { Graph, type GraphOptions } from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport { NS_PER_MS } from \"../../base/resilience/backoff.js\";\nimport {\n\ttype BreakerState,\n\ttype BudgetConstraint,\n\tbudgetGate,\n\ttype CircuitBreakerOptions,\n\tcircuitBreaker,\n\ttype FallbackInput,\n\tfallback,\n\ttype NodeOrValue,\n\ttype RateLimiterOptions,\n\ttype RateLimiterState,\n\ttype RetryOptions,\n\ttype RetryState,\n\trateLimiter,\n\tretry,\n\ttype StatusValue,\n\ttype TimeoutOptions,\n\ttype TimeoutState,\n\twithBreaker,\n\twithStatus,\n\twithTimeout,\n} from \"../../utils/resilience/index.js\";\n\n// ---------------------------------------------------------------------------\n// Reactive-option helpers\n// ---------------------------------------------------------------------------\n\n/**\n * `T | Node<T>` for primitive options — precedent-aligned with\n * {@link FallbackInput} and `policyGate.policies`. When the caller supplies a\n * static value, the layer is built once at construction. When the caller\n * supplies a `Node<T>`, the pipeline subscribes via {@link switchMap}: the\n * layer is rebuilt on every option emission. **State-loss caveat:** each\n * rebuild creates a fresh primitive instance — `rateLimiter` loses its pending\n * buffer, `circuitBreaker` resets failure count, `retry` resets attempt\n * count, `timeout` cancels in-flight deadline. This is the documented\n * switchMap-pattern semantics; primitive-side widening (filed in\n * `docs/optimizations.md`) will preserve internal state once it lands and the\n * pipeline can forward Node-form options directly.\n *\n * Per-layer **companion Nodes** (`droppedCount`, `rateLimitState`,\n * `breakerState`) are exposed only for the static-options path — Node-form\n * leaves them as `undefined` because each rebuild creates new companion\n * instances and a switchMap-mirrored companion would track only the latest\n * bundle. Callers needing both reactive options AND companions wait for\n * primitive-side widening.\n */\n// NodeOrValue re-imported from utils/resilience (same type, avoid barrel duplicate).\n\nfunction isNode<T>(x: unknown): x is Node<T> {\n\treturn (\n\t\ttypeof x === \"object\" && x !== null && \"subscribe\" in (x as object) && \"down\" in (x as object)\n\t);\n}\n\n/**\n * Validation shared by the static and reactive timeout paths. The reactive\n * path runs this inside the `switchMap` projection so an emitted bad value\n * surfaces as a thrown error at projection time (the consuming subscribe\n * routes it through the reactive ERROR channel rather than crashing\n * construction).\n */\nfunction assertTimeoutMsValid(ms: number): void {\n\tif (ms <= 0) throw new RangeError(\"timeoutMs must be > 0\");\n\t// Guard against `timeoutMs * NS_PER_MS` overflowing\n\t// `Number.MAX_SAFE_INTEGER` (~9.007e15). 9_000_000 ms ≈ 2.5 hours is a\n\t// sane upper bound; callers needing longer deadlines should express them\n\t// at the primitive level.\n\tif (ms > 9_000_000) {\n\t\tthrow new RangeError(\n\t\t\t\"timeoutMs must be <= 9_000_000 (≈2.5h) to stay within safe ns arithmetic\",\n\t\t);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\n/**\n * Options for {@link resilientPipeline}. Every layer is optional — omit a\n * field and that layer is skipped.\n *\n * Reactive (`Node<T>`) forms are accepted everywhere a primitive value would\n * fit; the pipeline subscribes via `switchMap` and rebuilds the layer on each\n * emission. See module JSDoc for the rebuild semantics + state-loss caveat.\n */\nexport interface ResilientPipelineOptions<T> {\n\t/**\n\t * Admission control — at most `maxEvents` `DATA` per `windowNs`. See\n\t * {@link rateLimiter}.\n\t *\n\t * **`maxBuffer` is required in BOTH the value form and the Node form** —\n\t * forwarded directly to {@link rateLimiter}, which is fail-loud (D4): an\n\t * un-seeded opts Node, or a value/cached value omitting `maxBuffer`, throws\n\t * `RangeError` at construction. Pass a finite positive integer for a bounded\n\t * queue, or the literal `Infinity` to explicitly opt in to unbounded (the\n\t * silent bounded/unbounded lock is the most common rateLimiter\n\t * mis-configuration). There is **no value-vs-Node asymmetry and no legacy\n\t * `?? Infinity` convenience injection** — both forms match the primitive's\n\t * fail-loud contract (F-B-root, 2026-05-18 smell sweep).\n\t */\n\trateLimit?: NodeOrValue<RateLimiterOptions>;\n\t/** Cost/constraint gate. See {@link budgetGate}. */\n\tbudget?: NodeOrValue<ReadonlyArray<BudgetConstraint>>;\n\t/** Circuit breaker — fail-fast when the downstream resource is unhealthy. See {@link circuitBreaker}. */\n\tbreaker?: NodeOrValue<CircuitBreakerOptions>;\n\t/**\n\t * Behavior when the breaker is open:\n\t * - `\"skip\"` (default) — emit `RESOLVED` (lets downstream drop the beat).\n\t * - `\"error\"` — emit a `CircuitOpenError` so `retry` / `fallback` can react.\n\t * See module JSDoc for the retry-budget burn caveat.\n\t *\n\t * Static (configuration-only — no reactive form).\n\t */\n\tbreakerOnOpen?: \"skip\" | \"error\";\n\t/** Retry policy on terminal `ERROR`. See {@link retry}. */\n\tretry?: NodeOrValue<RetryOptions>;\n\t/**\n\t * Per-attempt deadline in milliseconds. Converted to ns internally. Omit\n\t * to skip the timeout wrap.\n\t *\n\t * Specified in ms (not ns) because callers consistently think in\n\t * millisecond deadlines; the underlying {@link timeout} primitive takes ns\n\t * internally.\n\t */\n\ttimeoutMs?: NodeOrValue<number>;\n\t/** Final fallback value emitted on terminal `ERROR` after retry exhausts. See {@link fallback}. */\n\tfallback?: FallbackInput<T>;\n\t/**\n\t * Initial status reported by the status node. Default `\"pending\"`. Static.\n\t */\n\tinitialStatus?: StatusValue;\n\t/** Wrapper graph name. Default `\"resilient_pipeline\"`. */\n\tname?: string;\n\t/** Wrapper graph options. */\n\tgraph?: GraphOptions;\n}\n\n// ---------------------------------------------------------------------------\n// ResilientPipelineGraph\n// ---------------------------------------------------------------------------\n\n/**\n * Graph subclass returned by {@link resilientPipeline}. Mounts each\n * configured intermediate under a stable name and exposes per-layer status\n * companions.\n *\n * @category patterns\n */\nexport class ResilientPipelineGraph<T> extends Graph {\n\t/**\n\t * Final resilient node — subscribe to this for `DATA` emissions.\n\t *\n\t * Named `output` (not `node`) because `Graph.node(name)` already names the\n\t * path-resolution method on the base class; a `readonly node` field would\n\t * shadow it.\n\t */\n\treadonly output: Node<T>;\n\t/** Live status: `\"pending\" | \"running\" | \"completed\" | \"errored\"`. */\n\treadonly status: Node<StatusValue>;\n\t/**\n\t * Last error payload, or `null` when not errored.\n\t *\n\t * Named `lastError` (not `error`) because `Graph.error(name, err)` already\n\t * names a method on the base class.\n\t */\n\treadonly lastError: Node<unknown | null>;\n\t/** Breaker state when `opts.breaker` is provided; `undefined` otherwise. */\n\treadonly breakerState: Node<BreakerState> | undefined;\n\t/**\n\t * Timeout state companion when `opts.timeoutMs` is supplied as a\n\t * `Node<Partial<TimeoutOptions>>`-like form; `undefined` otherwise\n\t * (DS-13.5.B forwarding contract — Node-form opts skip the switchMap\n\t * rebuild and lift the primitive's lifecycle companion onto the\n\t * pipeline bundle).\n\t */\n\treadonly timeoutState: Node<TimeoutState> | undefined;\n\t/**\n\t * Retry state companion when `opts.retry` is supplied as a\n\t * `Node<RetryOptions>`-like form; `undefined` otherwise\n\t * (DS-13.5.B forwarding contract).\n\t */\n\treadonly retryState: Node<RetryState> | undefined;\n\t/**\n\t * Drop-counter when `opts.rateLimit` is provided; `undefined` otherwise.\n\t *\n\t * **Lifetime note:** `droppedCount` retains its final value through\n\t * terminal (`COMPLETE` / `ERROR` / `TEARDOWN`); the underlying counter\n\t * resets to `0` only at the next subscription cycle.\n\t */\n\treadonly droppedCount: Node<number> | undefined;\n\t/**\n\t * Combined rate-limit state when `opts.rateLimit` is provided; `undefined`\n\t * otherwise. Same lifecycle as {@link droppedCount} but exposes\n\t * `pendingCount` and `paused` alongside the drop counter for richer\n\t * backpressure observability (Tier 5.2 D7).\n\t */\n\treadonly rateLimitState: Node<RateLimiterState> | undefined;\n\n\tconstructor(source: Node<T>, opts: ResilientPipelineOptions<T> = {}) {\n\t\tsuper(opts.name ?? \"resilient_pipeline\", opts.graph);\n\n\t\tlet current: Node<T> = source;\n\t\tlet droppedCount: Node<number> | undefined;\n\t\tlet rateLimitState: Node<RateLimiterState> | undefined;\n\t\tlet breakerState: Node<BreakerState> | undefined;\n\t\tlet timeoutState: Node<TimeoutState> | undefined;\n\t\tlet retryState: Node<RetryState> | undefined;\n\n\t\t// 1. Admission control — cheapest to drop / queue before any other work.\n\t\tif (opts.rateLimit != null) {\n\t\t\tif (isNode<RateLimiterOptions>(opts.rateLimit)) {\n\t\t\t\t// DS-13.5.B forwarding: rateLimiter primitive is widened to\n\t\t\t\t// accept `NodeOrValue<RateLimiterOptions>` directly. Forward\n\t\t\t\t// the Node form to preserve internal state (pending buffer,\n\t\t\t\t// dropped counter) across opts swaps. Companion nodes\n\t\t\t\t// (droppedCount / rateLimitState) lift onto the pipeline\n\t\t\t\t// bundle. The pre-DS-13.5.B switchMap-rebuild path is gone.\n\t\t\t\tconst bundle = rateLimiter(current, opts.rateLimit);\n\t\t\t\tcurrent = bundle.node;\n\t\t\t\tdroppedCount = bundle.droppedCount;\n\t\t\t\trateLimitState = bundle.rateLimitState;\n\t\t\t\tthis.add(current, { name: \"rateLimited\" });\n\t\t\t\tthis.add(droppedCount, { name: \"droppedCount\" });\n\t\t\t\tthis.add(rateLimitState, { name: \"rateLimitState\" });\n\t\t\t} else {\n\t\t\t\t// F-B-root (2026-05-18 smell sweep): no `?? Infinity` injection.\n\t\t\t\t// `maxBuffer` is required by the type and validated fail-loud by\n\t\t\t\t// `rateLimiter` (D4) — `Infinity` is an explicit unbounded opt-in.\n\t\t\t\tconst rateOpts: RateLimiterOptions = {\n\t\t\t\t\t...opts.rateLimit,\n\t\t\t\t\tmeta: domainMeta(\"resilient\", \"rate-limit\"),\n\t\t\t\t};\n\t\t\t\tconst bundle = rateLimiter(current, rateOpts);\n\t\t\t\tcurrent = bundle.node;\n\t\t\t\tdroppedCount = bundle.droppedCount;\n\t\t\t\trateLimitState = bundle.rateLimitState;\n\t\t\t\tthis.add(current, { name: \"rateLimited\" });\n\t\t\t\tthis.add(droppedCount, { name: \"droppedCount\" });\n\t\t\t\tthis.add(rateLimitState, { name: \"rateLimitState\" });\n\t\t\t}\n\t\t}\n\n\t\t// 2. Budget — block when constraints are exhausted. Also cheap (no I/O).\n\t\tif (opts.budget != null) {\n\t\t\tif (isNode<ReadonlyArray<BudgetConstraint>>(opts.budget)) {\n\t\t\t\tconst inputForLayer = current;\n\t\t\t\tconst reactiveBudget = opts.budget;\n\t\t\t\tcurrent = switchMap(reactiveBudget, (constraints) =>\n\t\t\t\t\tconstraints.length > 0\n\t\t\t\t\t\t? budgetGate(inputForLayer, constraints, {\n\t\t\t\t\t\t\t\tmeta: domainMeta(\"resilient\", \"budget\"),\n\t\t\t\t\t\t\t}).node\n\t\t\t\t\t\t: inputForLayer,\n\t\t\t\t);\n\t\t\t\tthis.add(current, { name: \"budgetGated\" });\n\t\t\t} else if (opts.budget.length > 0) {\n\t\t\t\tcurrent = budgetGate(current, opts.budget, {\n\t\t\t\t\tmeta: domainMeta(\"resilient\", \"budget\"),\n\t\t\t\t}).node;\n\t\t\t\tthis.add(current, { name: \"budgetGated\" });\n\t\t\t}\n\t\t}\n\n\t\t// 3. Breaker — skip the resource when unhealthy (fail-fast before retry wastes time).\n\t\tif (opts.breaker != null) {\n\t\t\t// DS-13.5.B forwarding: circuitBreaker primitive accepts\n\t\t\t// `NodeOrValue<CircuitBreakerOptions>` directly. Pass the Node\n\t\t\t// form straight through so internal state (`_state`,\n\t\t\t// `_failureCount`, `_openCycle`, …) is preserved across opts\n\t\t\t// swaps. Companion `breakerState` lifts onto the pipeline\n\t\t\t// bundle in both static and Node-form paths.\n\t\t\tconst breaker = circuitBreaker(opts.breaker as NodeOrValue<CircuitBreakerOptions>);\n\t\t\tconst onOpen = opts.breakerOnOpen ?? \"skip\";\n\t\t\tconst wrapped = withBreaker<T>(breaker, {\n\t\t\t\tonOpen,\n\t\t\t\tmeta: domainMeta(\"resilient\", \"breaker\"),\n\t\t\t})(current);\n\t\t\tcurrent = wrapped.node;\n\t\t\tbreakerState = wrapped.breakerState;\n\t\t\tthis.add(current, { name: \"breakerWrapped\" });\n\t\t\tthis.add(breakerState, { name: \"breakerState\" });\n\t\t}\n\n\t\t// 4. Timeout — per-attempt deadline. Applied BEFORE retry so each retry\n\t\t// resubscribes to a fresh timeout. Swapping the order (timeout\n\t\t// OUTSIDE retry) would apply one global deadline to the entire\n\t\t// retry chain — not what callers expect for \"per-attempt timeout.\"\n\t\tif (opts.timeoutMs != null) {\n\t\t\tif (isNode<number>(opts.timeoutMs)) {\n\t\t\t\t// DS-13.5.B forwarding: build a derived `Node<{ns}>` from\n\t\t\t\t// the caller's `Node<number>` (ms) and pass directly to the\n\t\t\t\t// widened timeout primitive. State preservation (in-flight\n\t\t\t\t// deadline) is handled inside timeout's reactive opts path.\n\t\t\t\t// Companion `timeoutState` lifts onto the pipeline bundle.\n\t\t\t\tconst reactiveTimeoutMs = opts.timeoutMs;\n\t\t\t\tconst initialMs = reactiveTimeoutMs.cache as number | undefined;\n\t\t\t\t// QA A5 (2026-05-03): assert validity of the cached initial\n\t\t\t\t// value at construction so a bad cache fails loud at wire\n\t\t\t\t// time, not silently at first emit. Reactive emits with\n\t\t\t\t// invalid values flow through the producer body's ERROR\n\t\t\t\t// channel rather than throwing into the host scheduler.\n\t\t\t\tif (initialMs !== undefined) assertTimeoutMsValid(initialMs);\n\t\t\t\tconst optsBridge = node<Partial<TimeoutOptions>>(\n\t\t\t\t\t[reactiveTimeoutMs as Node<unknown>],\n\t\t\t\t\t(batchData, actions, ctx) => {\n\t\t\t\t\t\tconst data = batchData.map((b, i) =>\n\t\t\t\t\t\t\tb != null && b.length > 0 ? b.at(-1) : ctx.prevData[i],\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst ms = data[0] as number | undefined;\n\t\t\t\t\t\tif (ms === undefined) return;\n\t\t\t\t\t\t// QA A5: route validation failures through the\n\t\t\t\t\t\t// reactive ERROR channel — sync `throw` inside a\n\t\t\t\t\t\t// producer body corrupts the host scheduler's\n\t\t\t\t\t\t// wave dispatch (mirrors timeout primitive's\n\t\t\t\t\t\t// \"sync throw would corrupt the host scheduler\"\n\t\t\t\t\t\t// rationale).\n\t\t\t\t\t\tif (typeof ms !== \"number\" || !Number.isFinite(ms) || ms <= 0 || ms > 9_000_000) {\n\t\t\t\t\t\t\tactions.down([\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\tERROR,\n\t\t\t\t\t\t\t\t\tnew RangeError(\n\t\t\t\t\t\t\t\t\t\t`resilientPipeline: timeoutMs reactive emit invalid (${ms}); must be > 0 and <= 9_000_000.`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tactions.emit({ ns: ms * NS_PER_MS });\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\t\t\tname: \"timeoutOptsBridge\",\n\t\t\t\t\t\t...(initialMs !== undefined\n\t\t\t\t\t\t\t? { initial: { ns: initialMs * NS_PER_MS } as Partial<TimeoutOptions> }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// QA A5: register the bridge on the pipeline graph so\n\t\t\t\t// describe() walks see the full topology (dry-run /\n\t\t\t\t// real-run equivalence per CLAUDE.md).\n\t\t\t\tthis.add(optsBridge, { name: \"timeoutOptsBridge\" });\n\t\t\t\tconst bundle = withTimeout(current, optsBridge, {\n\t\t\t\t\tmeta: domainMeta(\"resilient\", \"timeout\"),\n\t\t\t\t});\n\t\t\t\tcurrent = bundle.node;\n\t\t\t\ttimeoutState = bundle.timeoutState;\n\t\t\t\tthis.add(current, { name: \"timeoutWrapped\" });\n\t\t\t\tthis.add(timeoutState, { name: \"timeoutState\" });\n\t\t\t} else {\n\t\t\t\tassertTimeoutMsValid(opts.timeoutMs);\n\t\t\t\tconst bundle = withTimeout(\n\t\t\t\t\tcurrent,\n\t\t\t\t\t{ ns: opts.timeoutMs * NS_PER_MS },\n\t\t\t\t\t{\n\t\t\t\t\t\tmeta: domainMeta(\"resilient\", \"timeout\"),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tcurrent = bundle.node;\n\t\t\t\ttimeoutState = bundle.timeoutState;\n\t\t\t\tthis.add(current, { name: \"timeoutWrapped\" });\n\t\t\t\tthis.add(timeoutState, { name: \"timeoutState\" });\n\t\t\t}\n\t\t}\n\n\t\t// 5. Retry — resubscribe on `ERROR` up to `count` times. Wraps timeout\n\t\t// so each retry gets its own fresh deadline.\n\t\tif (opts.retry != null) {\n\t\t\t// DS-13.5.B forwarding: retry primitive accepts\n\t\t\t// `NodeOrValue<RetryOptions>` directly. Forward Node form so\n\t\t\t// `attempt` / `prevDelay` / in-flight timer survive opts swaps.\n\t\t\t// Companion `retryState` lifts onto the pipeline bundle.\n\t\t\tif (isNode<RetryOptions>(opts.retry)) {\n\t\t\t\tconst bundle = retry(current, opts.retry as NodeOrValue<RetryOptions>);\n\t\t\t\tcurrent = bundle.node;\n\t\t\t\tretryState = bundle.retryState;\n\t\t\t\tthis.add(current, { name: \"retryWrapped\" });\n\t\t\t\tthis.add(retryState, { name: \"retryState\" });\n\t\t\t} else {\n\t\t\t\tconst bundle = retry(current, {\n\t\t\t\t\t...opts.retry,\n\t\t\t\t\tmeta: domainMeta(\"resilient\", \"retry\"),\n\t\t\t\t});\n\t\t\t\tcurrent = bundle.node;\n\t\t\t\tretryState = bundle.retryState;\n\t\t\t\tthis.add(current, { name: \"retryWrapped\" });\n\t\t\t\tthis.add(retryState, { name: \"retryState\" });\n\t\t\t}\n\t\t}\n\n\t\t// 6. Fallback — last resort after retry+timeout exhaust. Guard\n\t\t// `opts.fallback !== undefined` so `null` is a valid fallback.\n\t\tif (opts.fallback !== undefined) {\n\t\t\tcurrent = fallback(current, opts.fallback, {\n\t\t\t\tmeta: domainMeta(\"resilient\", \"fallback\"),\n\t\t\t});\n\t\t\tthis.add(current, { name: \"fallbackWrapped\" });\n\t\t}\n\n\t\t// 7. Status wrapping — observability. Always last so it sees the final shape.\n\t\tconst statusBundle = withStatus(current, {\n\t\t\tinitialStatus: opts.initialStatus ?? \"pending\",\n\t\t\tmeta: domainMeta(\"resilient\", \"status\"),\n\t\t});\n\n\t\tthis.output = statusBundle.node;\n\t\tthis.status = statusBundle.status;\n\t\tthis.lastError = statusBundle.error;\n\t\tthis.breakerState = breakerState;\n\t\tthis.droppedCount = droppedCount;\n\t\tthis.rateLimitState = rateLimitState;\n\t\tthis.timeoutState = timeoutState;\n\t\tthis.retryState = retryState;\n\n\t\t// Mount the externally-visible top-level entries by name. Each carries\n\t\t// its own factoryTag meta from the underlying primitive (`withStatus`\n\t\t// for `output`/`status`/`lastError`); domain-level provenance lives on\n\t\t// the Graph itself via the `tagFactory(\"resilientPipeline\", ...)` call\n\t\t// in the public factory below. The mount names use `output` /\n\t\t// `lastError` to match the property names — the previous `node` /\n\t\t// `error` clashed with `Graph.node(name)` / `Graph.error(name, err)`.\n\t\tthis.add(this.output, { name: \"output\" });\n\t\tthis.add(this.status, { name: \"status\" });\n\t\tthis.add(this.lastError, { name: \"lastError\" });\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Compose a resilient pipeline around `source` in the canonical nesting\n * order — `rateLimit → budget → breaker → timeout → retry → fallback → status`.\n * Omit any option to skip that layer.\n *\n * Returns a {@link ResilientPipelineGraph} (Graph subclass) —\n * `pipeline.output` is the externally visible final node; `pipeline.status`\n * / `pipeline.lastError` / `pipeline.breakerState` / `pipeline.droppedCount`\n * are the per-layer companions. Call `pipeline.describe()` to see the\n * mounted intermediates; compose with {@link graphLens}'s `health` for\n * aggregate status.\n *\n * **Naming note:** `output` and `lastError` (not `node` / `error`) avoid\n * clashes with `Graph.node(name)` and `Graph.error(name, err)` on the base\n * class.\n *\n * @param source - Upstream node to wrap.\n * @param opts - See {@link ResilientPipelineOptions}. All fields optional.\n *\n * @example\n * ```ts\n * const safeFetch = resilientPipeline(fetchNode, {\n * rateLimit: { maxEvents: 10, windowNs: NS_PER_SEC, maxBuffer: 100 },\n * breaker: { failureThreshold: 5 },\n * retry: { count: 3, backoff: \"exponential\" },\n * timeoutMs: 10_000,\n * fallback: null,\n * });\n * safeFetch.output.subscribe(msgs => console.log(msgs));\n * safeFetch.status.subscribe(msgs => console.log(msgs));\n * graphSpecToAscii(safeFetch.describe()); // visualize the chain\n * ```\n *\n * @category patterns\n */\nexport function resilientPipeline<T>(\n\tsource: Node<T>,\n\topts: ResilientPipelineOptions<T> = {},\n): ResilientPipelineGraph<T> {\n\tconst g = new ResilientPipelineGraph<T>(source, opts);\n\t// Self-tag for `graph.describe()` factory provenance (Phase 2.5 DG1=B).\n\t// `placeholderArgs` substitutes Node-typed and function-typed fields with\n\t// `\"<Node>\"` / `\"<function>\"` so `factoryArgs` stays JSON-serializable.\n\tg.tagFactory(\"resilientPipeline\", placeholderArgs(opts as unknown as Record<string, unknown>));\n\treturn g;\n}\n\n// Tag the underlying status / error / breaker / dropped companions with a\n// best-effort factoryTag too via the wrapper class's meta — already covered\n// by `domainMeta(\"resilient\", kind)` on the mounted nodes.\n\n// Tier 9.1 γ-form: this module now lives inside `extra/resilience/`, so the\n// underlying primitive option types are already exported from the same barrel\n// (`./index.js`). The previous re-exports of `factoryTag` / `placeholderArgs` /\n// `NS_PER_MS` / `NS_PER_SEC` / option types were a workaround for the prior\n// `patterns/resilient-pipeline/` folder location and are now redundant.\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  awaitSettled,
3
3
  firstValueFrom
4
- } from "./chunk-O3MT7DYI.js";
4
+ } from "./chunk-N6MNJNHB.js";
5
5
 
6
6
  // src/presets/ai/debate/index.ts
7
7
  import {
@@ -92,9 +92,11 @@ function heterogeneousDebate(parent, opts) {
92
92
  if (!cancelled) actions.down([[ERROR, err]]);
93
93
  }
94
94
  })();
95
- return () => {
96
- cancelled = true;
97
- ac.abort();
95
+ return {
96
+ onDeactivation: () => {
97
+ cancelled = true;
98
+ ac.abort();
99
+ }
98
100
  };
99
101
  },
100
102
  { describeKind: "producer", name: `${name}.round-work` }
@@ -226,4 +228,4 @@ export {
226
228
  DebateGraph,
227
229
  heterogeneousDebate
228
230
  };
229
- //# sourceMappingURL=chunk-E5OZPDIW.js.map
231
+ //# sourceMappingURL=chunk-X7BA5PWG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presets/ai/debate/index.ts"],"sourcesContent":["/**\n * DS-14.6.A U-C — `heterogeneousDebate()` (Phase 14.5).\n *\n * Stanford-MAD heterogeneity thesis (SESSION-DS-14.6-A L9): participants get\n * **different model adapters + different role prompts**. Closed reasoning\n * loop — no tools / side effects / persistent state beyond the transcript.\n *\n * **Reactive topology (QA M1→b, 2026-05-15).** The round loop is NOT a\n * top-level `async while` driver. It mirrors `refineLoop`'s §7 feedback\n * shape so `describe()` / `explain()` / dry-run see the structure:\n *\n * ```\n * roundTrigger(state) ──▶ roundWork(switchMap→producer, per-round\n * sequential adapter.invoke) ──▶ transcript(scan)\n * └─▶ converged(derived) │\n * ▲ │\n * └──────────── decideEffect (feedback + trigger) ◀─────┘\n * ```\n *\n * The per-round participant calls are an async **source boundary** inside\n * the switchMap producer (spec §5.10 — async belongs in sources), with an\n * abort-on-deactivate `AbortController` (COMPOSITION-GUIDE §45) so a\n * superseded / torn-down round cancels its in-flight LLM calls. `decideEffect`\n * is the sole feedback authority (computes termination inline from the round\n * envelope + closure history, §28); `converged` is a parallel derived purely\n * for external `status` observability. Termination: `fixedRounds |\n * \"until-converge\" | { until: Node<boolean> }` — `until` is a real reactive\n * dep, not a `.cache` poll. `\"until-converge\"` uses a pluggable `converge?`\n * fn, default = no participant changed stance across the last 2 rounds\n * (D-C1, zero extra LLM cost). `output:\"synthesizer-final\"` with no\n * synthesizer-role participant throws at construction (D-C2).\n *\n * @module\n */\n\nimport {\n\tbatch,\n\tnode as createNode,\n\tERROR,\n\tINVALIDATE,\n\ttype Node,\n\tRESOLVED,\n} from \"@graphrefly/pure-ts/core\";\nimport { fromAny, switchMap } from \"@graphrefly/pure-ts/extra\";\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\nimport { awaitSettled, firstValueFrom } from \"../../../base/sources/settled.js\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMResponse,\n\tNodeInput,\n} from \"../../../utils/ai/adapters/core/types.js\";\n\nexport interface Turn {\n\treadonly round: number;\n\treadonly role: string;\n\treadonly content: string;\n}\n\nexport interface DebateParticipant {\n\treadonly adapter: LLMAdapter;\n\t/** e.g. \"advocate\" | \"skeptic\" | \"synthesizer\" | custom. */\n\treadonly role: string;\n\treadonly systemPrompt: string;\n}\n\nexport type DebateTermination = number | \"until-converge\" | { readonly until: Node<boolean> };\n\nexport type DebateOutput =\n\t| \"transcript\"\n\t| \"synthesizer-final\"\n\t| { readonly project: (transcript: readonly Turn[]) => unknown };\n\nexport type DebateStatus = \"running\" | \"converged\" | \"max-rounds\" | \"error\";\n\nexport interface HeterogeneousDebateOptions {\n\treadonly question: string;\n\treadonly participants: readonly DebateParticipant[];\n\t/** Default `3`. */\n\treadonly rounds?: DebateTermination;\n\t/** Default `\"transcript\"`. */\n\treadonly output?: DebateOutput;\n\t/**\n\t * D-C1 — `\"until-converge\"` detector. Default: no participant's latest\n\t * content changed vs the previous round (structural compare, no LLM).\n\t */\n\treadonly converge?: (transcript: readonly Turn[]) => boolean;\n\treadonly name?: string;\n\t/** Hard ceiling for `\"until-converge\"` / `{ until }` (default 12). */\n\treadonly maxRounds?: number;\n}\n\nexport interface HeterogeneousDebateBundle {\n\treadonly transcript: Node<readonly Turn[]>;\n\treadonly result: Node<unknown>;\n\treadonly status: Node<DebateStatus>;\n\treadonly graph: DebateGraph;\n\t/**\n\t * Thin `awaitSettled` bridge (mirrors `agentLoop.run()`): kicks the\n\t * reactive round loop and resolves the final `result`. Throws\n\t * `RangeError` if a prior `run()` is still pending (QA P4 re-entrancy).\n\t */\n\trun(): Promise<unknown>;\n}\n\nexport class DebateGraph extends Graph {}\n\nconst SYNTH_RE = /synth/i;\n\n/** Default converge: every role's latest two rounds are identical. */\nfunction defaultConverge(transcript: readonly Turn[]): boolean {\n\tconst rounds = transcript.reduce((m, t) => Math.max(m, t.round), 0);\n\tif (rounds < 2) return false;\n\tconst at = (r: number): Map<string, string> => {\n\t\tconst m = new Map<string, string>();\n\t\tfor (const t of transcript) if (t.round === r) m.set(t.role, t.content);\n\t\treturn m;\n\t};\n\tconst prev = at(rounds - 1);\n\tconst cur = at(rounds);\n\tif (prev.size === 0 || prev.size !== cur.size) return false;\n\tfor (const [role, content] of cur) if (prev.get(role) !== content) return false;\n\treturn true;\n}\n\n/** Process-wide sequence for collision-safe default mount names (QA P6). */\nlet _debateSeq = 0;\n\ninterface RoundEnvelope {\n\treadonly round: number;\n\treadonly turns: readonly Turn[];\n}\n\nexport function heterogeneousDebate(\n\tparent: Graph,\n\topts: HeterogeneousDebateOptions,\n): HeterogeneousDebateBundle {\n\tconst output = opts.output ?? \"transcript\";\n\tif (output === \"synthesizer-final\" && !opts.participants.some((p) => SYNTH_RE.test(p.role))) {\n\t\tthrow new Error(\n\t\t\t\"heterogeneousDebate: output 'synthesizer-final' requires a participant whose role matches /synth/i (DS-14.6.A D-C2).\",\n\t\t);\n\t}\n\tif (opts.participants.length === 0) {\n\t\tthrow new Error(\"heterogeneousDebate: at least one participant is required\");\n\t}\n\n\tconst name = opts.name ?? `debate-${++_debateSeq}`;\n\tconst graph = new DebateGraph(name);\n\tparent.mount(name, graph);\n\n\tconst term = opts.rounds ?? 3;\n\tconst maxRounds = opts.maxRounds ?? 12;\n\tconst converge = opts.converge ?? defaultConverge;\n\t// `{ until }` becomes a real reactive dep (QA P10 — no `.cache` poll);\n\t// non-until modes use a constant-false node for stable dep arity.\n\tconst untilNode: Node<boolean> =\n\t\ttypeof term === \"object\" ? term.until : createNode<boolean>([], { initial: false });\n\n\t// --- state / output nodes -------------------------------------------------\n\tconst roundTrigger = createNode<number>([], { name: `${name}.round`, initial: 0 });\n\tconst statusState = createNode<DebateStatus>([], { name: `${name}.status`, initial: \"running\" });\n\tconst resultState = createNode<unknown>([], { name: `${name}.result`, initial: undefined });\n\n\t// Closure history (§28 factory-time mirror) — the feedback authority's\n\t// view of the transcript without a declared dep cycle.\n\tlet history: Turn[] = [];\n\n\tfunction buildMessages(p: DebateParticipant): ChatMessage[] {\n\t\tconst msgs: ChatMessage[] = [\n\t\t\t{ role: \"system\", content: p.systemPrompt },\n\t\t\t{ role: \"user\", content: opts.question },\n\t\t];\n\t\tfor (const t of history) {\n\t\t\tmsgs.push({ role: \"assistant\", content: `[${t.role} r${t.round}] ${t.content}` });\n\t\t}\n\t\treturn msgs;\n\t}\n\n\t// --- GENERATE: roundTrigger → per-round sequential participant calls -------\n\t// switchMap producer = async source boundary (spec §5.10). The producer's\n\t// deactivate cleanup aborts in-flight calls (COMPOSITION-GUIDE §45 + the\n\t// switchMap supersede path), so a torn-down round burns no extra tokens.\n\tconst roundWork = switchMap<number, RoundEnvelope>(\n\t\troundTrigger,\n\t\t(r) =>\n\t\t\tcreateNode<RoundEnvelope>(\n\t\t\t\t[],\n\t\t\t\t(_data, actions) => {\n\t\t\t\t\tif (r < 1) {\n\t\t\t\t\t\tactions.down([[RESOLVED]]);\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t\tconst ac = new AbortController();\n\t\t\t\t\tlet cancelled = false;\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\tconst turns: Turn[] = [];\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tfor (const p of opts.participants) {\n\t\t\t\t\t\t\t\tconst res = await firstValueFrom(\n\t\t\t\t\t\t\t\t\tfromAny<LLMResponse>(\n\t\t\t\t\t\t\t\t\t\tp.adapter.invoke(buildMessages(p), {\n\t\t\t\t\t\t\t\t\t\t\tsignal: ac.signal,\n\t\t\t\t\t\t\t\t\t\t}) as NodeInput<LLMResponse>,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif (cancelled) return;\n\t\t\t\t\t\t\t\tturns.push({ round: r, role: p.role, content: res.content });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!cancelled) actions.emit({ round: r, turns });\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tif (!cancelled) actions.down([[ERROR, err]]);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tonDeactivation: () => {\n\t\t\t\t\t\t\tcancelled = true;\n\t\t\t\t\t\t\tac.abort();\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\t{ describeKind: \"producer\", name: `${name}.round-work` },\n\t\t\t),\n\t\t{ name: `${name}.rounds` },\n\t);\n\tgraph.add(roundWork, { name: \"rounds\" });\n\n\t// --- transcript: scan-accumulate (external observability) -----------------\n\tconst transcript = createNode<readonly Turn[]>(\n\t\t[roundWork],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst env = (\n\t\t\t\tbatchData[0] != null && batchData[0].length > 0 ? batchData[0].at(-1) : ctx.prevData[0]\n\t\t\t) as RoundEnvelope | undefined;\n\t\t\tif (env === undefined) {\n\t\t\t\tactions.down([[RESOLVED]]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tactions.emit(history.slice());\n\t\t},\n\t\t{ name: `${name}.transcript`, describeKind: \"derived\" },\n\t);\n\tgraph.add(transcript, { name: \"transcript\" });\n\n\t// --- converged: parallel derived for external `status` (NOT authority) ----\n\tconst converged = createNode<{ done: boolean; reason: DebateStatus }>(\n\t\t[transcript as Node, untilNode as Node],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst tr = (\n\t\t\t\tbatchData[0] != null && batchData[0].length > 0 ? batchData[0].at(-1) : ctx.prevData[0]\n\t\t\t) as readonly Turn[] | undefined;\n\t\t\tconst untilV = (\n\t\t\t\tbatchData[1] != null && batchData[1].length > 0 ? batchData[1].at(-1) : ctx.prevData[1]\n\t\t\t) as boolean | undefined;\n\t\t\tconst rounds = (tr ?? []).reduce((m, t) => Math.max(m, t.round), 0);\n\t\t\tif (typeof term === \"number\") {\n\t\t\t\tactions.emit({ done: rounds >= term, reason: \"max-rounds\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (term === \"until-converge\") {\n\t\t\t\tif (converge(tr ?? [])) actions.emit({ done: true, reason: \"converged\" });\n\t\t\t\telse actions.emit({ done: rounds >= maxRounds, reason: \"max-rounds\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tactions.emit({ done: untilV === true || rounds >= maxRounds, reason: \"max-rounds\" });\n\t\t},\n\t\t{ name: `${name}.converged`, describeKind: \"derived\" },\n\t);\n\tgraph.add(converged, { name: \"converged\" });\n\n\t// --- decideEffect: SOLE feedback authority (§7 single-trigger) ------------\n\t// Computes termination INLINE (round envelope + closure history + the\n\t// reactive `untilNode` dep) — does NOT read `converged.cache` (avoids the\n\t// same-wave drain-order hazard refineLoop documents). `converged` above is\n\t// purely for external observation.\n\tconst decideEffect = createNode(\n\t\t[roundWork as Node, untilNode as Node],\n\t\t(batchData, _actions, ctx) => {\n\t\t\tconst env = (\n\t\t\t\tbatchData[0] != null && batchData[0].length > 0 ? batchData[0].at(-1) : undefined\n\t\t\t) as RoundEnvelope | undefined;\n\t\t\tconst untilV = (\n\t\t\t\tbatchData[1] != null && batchData[1].length > 0 ? batchData[1].at(-1) : ctx.prevData[1]\n\t\t\t) as boolean | undefined;\n\t\t\tif (env === undefined) {\n\t\t\t\t// `until` flipped with no new round — finalize if it went true.\n\t\t\t\tif (typeof term === \"object\" && untilV === true && statusState.cache === \"running\") {\n\t\t\t\t\tfinalize(\"max-rounds\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// New round settled — fold into the closure history first.\n\t\t\thistory = [...history, ...env.turns];\n\t\t\tconst r = env.round;\n\t\t\tlet done = false;\n\t\t\tlet reason: DebateStatus = \"max-rounds\";\n\t\t\tif (typeof term === \"number\") done = r >= term;\n\t\t\telse if (term === \"until-converge\") {\n\t\t\t\tif (converge(history)) {\n\t\t\t\t\tdone = true;\n\t\t\t\t\treason = \"converged\";\n\t\t\t\t} else done = r >= maxRounds;\n\t\t\t} else done = untilV === true || r >= maxRounds;\n\n\t\t\tif (done) finalize(reason);\n\t\t\telse roundTrigger.emit(r + 1); // §7 feedback edge\n\t\t},\n\t\t{ name: `${name}.decide`, describeKind: \"effect\", errorWhenDepsError: false },\n\t);\n\tgraph.add(decideEffect, { name: \"decide\" });\n\n\tfunction finalize(reason: DebateStatus): void {\n\t\tlet out: unknown;\n\t\tif (output === \"transcript\") out = history.slice();\n\t\telse if (output === \"synthesizer-final\") {\n\t\t\tconst synth = [...history].reverse().find((t) => SYNTH_RE.test(t.role));\n\t\t\tout = synth?.content ?? null;\n\t\t} else out = output.project(history.slice());\n\t\tbatch(() => {\n\t\t\tstatusState.emit(reason);\n\t\t\tresultState.emit(out);\n\t\t});\n\t}\n\n\t// Error watcher — adapter throw surfaces as ERROR on roundWork.\n\tconst errorWatcher = createNode(\n\t\t[roundWork as Node],\n\t\t(_b, _a, ec) => {\n\t\t\tconst t = ec.terminalDeps[0];\n\t\t\tif (t !== undefined && t !== true && statusState.cache === \"running\") {\n\t\t\t\tbatch(() => {\n\t\t\t\t\tstatusState.emit(\"error\");\n\t\t\t\t\tresultState.emit(new Error(\"heterogeneousDebate: a participant adapter errored\"));\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\t{ name: `${name}.error-watcher`, describeKind: \"effect\", errorWhenDepsError: false },\n\t);\n\tgraph.add(errorWatcher, { name: \"error-watcher\" });\n\n\t// Keepalive: activate the feedback + observability nodes so the loop runs\n\t// and `.cache` stays warm without an external subscriber. These live for\n\t// the graph's lifetime (torn down on parent-graph destroy).\n\tdecideEffect.subscribe(() => undefined);\n\terrorWatcher.subscribe(() => undefined);\n\ttranscript.subscribe(() => undefined);\n\tconverged.subscribe(() => undefined);\n\n\tlet running = false;\n\n\treturn {\n\t\ttranscript,\n\t\tresult: resultState,\n\t\tstatus: statusState,\n\t\tgraph,\n\t\tasync run(): Promise<unknown> {\n\t\t\tif (running) {\n\t\t\t\tthrow new RangeError(\n\t\t\t\t\t`heterogeneousDebate \"${name}\": run() called while a previous run() is still pending`,\n\t\t\t\t);\n\t\t\t}\n\t\t\trunning = true;\n\t\t\ttry {\n\t\t\t\thistory = [];\n\t\t\t\tbatch(() => {\n\t\t\t\t\tstatusState.emit(\"running\");\n\t\t\t\t\tresultState.down([[INVALIDATE]]);\n\t\t\t\t});\n\t\t\t\t// Subscribe BEFORE the kick (sync adapters would otherwise drain\n\t\t\t\t// the terminal before awaitSettled subscribes).\n\t\t\t\tconst settled = awaitSettled(resultState, { skipCurrent: true });\n\t\t\t\troundTrigger.emit(1); // kick\n\t\t\t\treturn await settled;\n\t\t\t} finally {\n\t\t\t\trunning = false;\n\t\t\t}\n\t\t},\n\t};\n}\n"],"mappings":"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","names":[]}
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  singleFromAny
3
- } from "./chunk-BU3SEFA5.js";
3
+ } from "./chunk-V46JWFGV.js";
4
4
  import {
5
5
  firstValueFrom
6
- } from "./chunk-O3MT7DYI.js";
6
+ } from "./chunk-N6MNJNHB.js";
7
7
 
8
8
  // src/utils/ai/adapters/providers/dry-run.ts
9
9
  import { ResettableTimer } from "@graphrefly/pure-ts/core";
@@ -527,4 +527,4 @@ export {
527
527
  FallbackMissError,
528
528
  fallbackAdapter
529
529
  };
530
- //# sourceMappingURL=chunk-CXANAIZU.js.map
530
+ //# sourceMappingURL=chunk-XEWV254I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/ai/adapters/providers/dry-run.ts","../src/utils/ai/adapters/middleware/replay-cache.ts","../src/utils/ai/adapters/_internal/content-addressed-cache.ts","../src/utils/ai/adapters/_internal/wrappers.ts","../src/utils/ai/adapters/providers/fallback.ts"],"sourcesContent":["/**\n * DryRunAdapter — zero-cost mock provider.\n *\n * Returns a deterministic fake response (plus a configurable hook for\n * customization). Useful for: pipeline smoke tests, CI without API keys,\n * local development, and as the leaf of a `cascadingLlmAdapter` when every\n * real tier fails.\n *\n * The library ships a minimal implementation only — richer scenario-\n * scripted mocks (per-stage responses, call recording) belong at the test\n * harness layer, not in the shipped library.\n *\n * Uses `ResettableTimer` for simulated latency (spec §5.10 escape hatch\n * documented on the class), and throws an `AbortError`-named Error on\n * abort so retry/timeout middleware can classify it.\n */\n\nimport { ResettableTimer } from \"@graphrefly/pure-ts/core\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tStreamDelta,\n\tTokenUsage,\n} from \"../core/types.js\";\n\nexport interface DryRunAdapterOptions {\n\tprovider?: string;\n\tmodel?: string;\n\t/** Generate the fake response. Defaults to echoing the last user message. */\n\trespond?: (messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string;\n\t/**\n\t * Generate a fake usage object. Defaults to a simple character-count\n\t * heuristic (`input = sum(messages) / 4`, `output = content / 4`).\n\t */\n\tusage?: (messages: readonly ChatMessage[], content: string) => TokenUsage;\n\t/** Simulated latency in milliseconds (applied to both invoke and stream). */\n\tlatencyMs?: number;\n\t/** Stream chunk size in characters. Default 16. */\n\tstreamChunkSize?: number;\n}\n\nfunction makeAbortError(): Error {\n\tconst err = new Error(\"aborted\") as Error & { name: string };\n\terr.name = \"AbortError\";\n\treturn err;\n}\n\n/**\n * Abort-aware sleep using `ResettableTimer`. Spec §5.10 escape hatch.\n * No-op if `ms <= 0`; rejects with `AbortError` if the signal aborts.\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\tif (ms <= 0) return Promise.resolve();\n\tif (signal?.aborted) return Promise.reject(makeAbortError());\n\treturn new Promise((resolve, reject) => {\n\t\tconst timer = new ResettableTimer();\n\t\tlet onAbort: (() => void) | undefined;\n\t\tconst cleanup = (): void => {\n\t\t\ttimer.cancel();\n\t\t\tif (signal && onAbort) signal.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\ttimer.start(ms, () => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t});\n\t\tif (signal) {\n\t\t\tonAbort = (): void => {\n\t\t\t\tcleanup();\n\t\t\t\treject(makeAbortError());\n\t\t\t};\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t});\n}\n\n/**\n * Create a DryRun adapter.\n *\n * @example\n * ```ts\n * const adapter = dryRunAdapter({ respond: (msgs) => \"hello from dry-run\" });\n * const resp = await Promise.resolve(adapter.invoke([{ role: \"user\", content: \"hi\" }]));\n * ```\n */\nexport function dryRunAdapter(opts: DryRunAdapterOptions = {}): LLMAdapter {\n\tconst provider = opts.provider ?? \"dry-run\";\n\tconst model = opts.model ?? \"dry-run-v1\";\n\tconst latencyMs = opts.latencyMs ?? 0;\n\tconst streamChunkSize = Math.max(1, opts.streamChunkSize ?? 16);\n\n\tconst respondFn =\n\t\topts.respond ??\n\t\t((msgs: readonly ChatMessage[]): string => {\n\t\t\tconst lastUser = [...msgs].reverse().find((m) => m.role === \"user\");\n\t\t\treturn lastUser ? `echo: ${lastUser.content}` : \"dry-run: no user message\";\n\t\t});\n\n\tconst usageFn =\n\t\topts.usage ??\n\t\t((msgs: readonly ChatMessage[], content: string): TokenUsage => {\n\t\t\tconst totalInput = msgs.reduce((s, m) => s + m.content.length, 0);\n\t\t\treturn {\n\t\t\t\tinput: { regular: Math.ceil(totalInput / 4) },\n\t\t\t\toutput: { regular: Math.ceil(content.length / 4) },\n\t\t\t};\n\t\t});\n\n\treturn {\n\t\tprovider,\n\t\tmodel,\n\n\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\tawait sleep(latencyMs, invokeOpts?.signal);\n\t\t\tif (invokeOpts?.signal?.aborted) throw makeAbortError();\n\t\t\tconst content = respondFn(messages, invokeOpts);\n\t\t\tconst usage = usageFn(messages, content);\n\t\t\treturn {\n\t\t\t\tcontent,\n\t\t\t\tusage,\n\t\t\t\tfinishReason: \"stop\",\n\t\t\t\tmodel: invokeOpts?.model ?? model,\n\t\t\t\tprovider,\n\t\t\t\ttier: invokeOpts?.tier,\n\t\t\t\tmetadata: { dryRun: true },\n\t\t\t};\n\t\t},\n\n\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\tconst content = respondFn(messages, invokeOpts);\n\t\t\tconst usage = usageFn(messages, content);\n\t\t\tconst chunkCount = Math.ceil(content.length / streamChunkSize) || 1;\n\t\t\tconst perChunkMs = latencyMs > 0 ? latencyMs / chunkCount : 0;\n\t\t\tfor (let i = 0; i < content.length; i += streamChunkSize) {\n\t\t\t\tif (invokeOpts?.signal?.aborted) throw makeAbortError();\n\t\t\t\tawait sleep(perChunkMs, invokeOpts?.signal);\n\t\t\t\tyield { type: \"token\", delta: content.slice(i, i + streamChunkSize) };\n\t\t\t}\n\t\t\tyield { type: \"usage\", usage };\n\t\t\tyield { type: \"finish\", reason: \"stop\" };\n\t\t},\n\t};\n}\n","/**\n * `withReplayCache` — content-addressed response cache over `KvStorageTier`.\n *\n * - Key: sha256 of canonicalized (messages + invoke options minus `signal`).\n * - `\"read-write\"` (default): returns cached response if present; on miss,\n * passes through and stores the result.\n * - `\"write-only\"`: never reads; populates the cache for later runs.\n * - `\"read\"`: reads only; on miss, passes through without writing.\n * - `\"read-strict\"`: reads only; on miss, **throws `ReplayCacheMissError`**\n * instead of passing through. Use for fixture-driven tests or offline\n * fallback adapters where any cache miss is a test failure or a signal to\n * degrade.\n *\n * Reuses the library's existing `KvStorageTier` abstraction — any kv tier\n * (memoryKv / fileKv / sqliteKv / indexedDbKv / custom).\n *\n * **Concurrent cache-miss dedup:** uses `singleFromAny` so two concurrent\n * calls with the same key share one upstream request. Second caller sees the\n * same response that the first caller fetched; no duplicate provider spend.\n *\n * **Circular-ref safe:** `canonicalJson` uses a seen-set replacer so\n * user-supplied `ToolDefinition.parameters` with `$ref` cycles don't stack-\n * overflow the key computation.\n *\n * **Stream cadence capture:** when `cacheStreaming: true` AND\n * `captureStreamCadence: true`, per-chunk delays (ms since previous chunk)\n * are recorded alongside the content. Replay honors the recorded cadence\n * unless `replaySpeed` is set, which multiplies the effective per-chunk\n * delay (`replaySpeed: 2` → 2× faster; `replaySpeed: 0` → instant).\n * Without `captureStreamCadence`, replay is instant regardless.\n */\n\nimport { monotonicNs, ResettableTimer, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport type { KvStorageTier } from \"@graphrefly/pure-ts/extra\";\nimport { fromAny } from \"@graphrefly/pure-ts/extra\";\nimport { singleFromAny } from \"../../../../base/composition/single-from-any.js\";\nimport { firstValueFrom } from \"../../../../base/sources/settled.js\";\nimport { contentAddressedCache } from \"../_internal/content-addressed-cache.js\";\nimport { adapterWrapper, withLayer } from \"../_internal/wrappers.js\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tStreamDelta,\n} from \"../core/types.js\";\n\nexport type ReplayCacheMode = \"read\" | \"read-strict\" | \"write-only\" | \"read-write\";\n\nexport class ReplayCacheMissError extends Error {\n\toverride name = \"ReplayCacheMissError\";\n\tconstructor(\n\t\tpublic readonly key: string,\n\t\tpublic readonly method: \"invoke\" | \"stream\",\n\t) {\n\t\tsuper(`withReplayCache: no cached response for ${method} (key=${key}, mode=read-strict)`);\n\t}\n}\n\n/**\n * Context object passed to {@link WithReplayCacheOptions.keyFn}. Extending\n * with additional fields is forward-compatible — current implementations\n * ignoring unknown fields continue to work.\n */\nexport interface ReplayCacheKeyContext {\n\treadonly messages: readonly ChatMessage[];\n\treadonly opts: LLMInvokeOptions | undefined;\n\t/** Shortcut to `opts?.keyContext` — avoids an extra guard in callers. */\n\treadonly context: unknown;\n}\n\nexport interface WithReplayCacheOptions {\n\tstorage: KvStorageTier;\n\tmode?: ReplayCacheMode;\n\t/**\n\t * Custom key function. Receives a {@link ReplayCacheKeyContext} with the\n\t * chat messages, the invoke options, and the caller-supplied\n\t * `opts.keyContext`. Defaults to sha256 of canonical JSON over\n\t * `(messages, opts without signal/keyContext)`.\n\t *\n\t * Legacy 2-arg form `(messages, opts?)` also accepted — the adapter\n\t * detects arity and dispatches. Prefer the object form for new code.\n\t */\n\tkeyFn?:\n\t\t| ((ctx: ReplayCacheKeyContext) => string | Promise<string>)\n\t\t| ((messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string | Promise<string>);\n\t/** Prefix for cached keys (useful when sharing a tier across domains). */\n\tkeyPrefix?: string;\n\t/**\n\t * Whether to cache streaming responses (by consuming the full stream\n\t * and replaying it as one synthetic token chunk). Default `false`.\n\t */\n\tcacheStreaming?: boolean;\n\t/**\n\t * When `cacheStreaming: true`, also record per-chunk delays (ms since\n\t * previous chunk) so replay can honor the original streaming cadence.\n\t * Default `false` (chunks replay instantly).\n\t */\n\tcaptureStreamCadence?: boolean;\n\t/**\n\t * Stream replay speed multiplier. `1` = original cadence (requires\n\t * `captureStreamCadence`). `2` = 2× faster. `0` = instant. Default `1`.\n\t */\n\treplaySpeed?: number;\n}\n\ninterface CachedEntry {\n\tresponse: LLMResponse;\n\tstoredAtNs: number;\n\t/**\n\t * Per-chunk deltas in milliseconds — populated only when `captureStreamCadence`\n\t * is `true` during the write. Replayed via `ResettableTimer` in stream().\n\t */\n\tstreamCadenceMs?: readonly number[];\n\t/**\n\t * Per-chunk bodies in order (tokens only — `usage`/`finish` are reconstructed\n\t * from `response.usage` / `response.finishReason`). Populated only when\n\t * `captureStreamCadence` is `true`, used for cadence-faithful replay.\n\t */\n\tstreamChunks?: ReadonlyArray<{ delta: string }>;\n}\n\ntype ResolveArgs = {\n\tmessages: readonly ChatMessage[];\n\tinvokeOpts: LLMInvokeOptions | undefined;\n};\ntype ResolveArgsWithKey = ResolveArgs & { _precomputedKey: string };\n\n/** Wrap an adapter with a replay cache. */\nexport function withReplayCache(inner: LLMAdapter, opts: WithReplayCacheOptions): LLMAdapter {\n\tconst mode = opts.mode ?? \"read-write\";\n\tconst cacheStreaming = opts.cacheStreaming ?? false;\n\tconst captureStreamCadence = opts.captureStreamCadence ?? false;\n\tconst replaySpeed = opts.replaySpeed ?? 1;\n\tconst keyPrefix = opts.keyPrefix ?? \"llm-replay\";\n\tconst isReadOnly = mode === \"read\" || mode === \"read-strict\";\n\n\t// Content-addressed substrate — keys via canonicalJson + sha256 over\n\t// (messages, opts minus signal/keyContext) or the caller's custom keyFn.\n\t// Value type is `CachedEntry` so we can persist stream cadence alongside\n\t// the response. Uses the shared substrate in `src/extra/content-addressed-\n\t// storage.ts` via the LLM-specific wrapper in `_internal/`.\n\t//\n\t// Mode translation: `ReplayCacheMode` uses `\"write-only\"` (legacy name);\n\t// the substrate uses `\"write\"`. All other modes map 1:1.\n\tconst cache = contentAddressedCache<CachedEntry>({\n\t\tstorage: opts.storage,\n\t\tmode: mode === \"write-only\" ? \"write\" : mode,\n\t\tkeyFn: opts.keyFn,\n\t\tkeyPrefix,\n\t});\n\n\tconst sleepMs = (ms: number): Promise<void> =>\n\t\tms <= 0\n\t\t\t? Promise.resolve()\n\t\t\t: new Promise<void>((resolve) => {\n\t\t\t\t\tconst t = new ResettableTimer();\n\t\t\t\t\tt.start(ms, () => resolve());\n\t\t\t\t});\n\n\t// Singleflight — concurrent cache-miss requests with the same key share one\n\t// upstream call. `keyFn` must be synchronous (singleflight needs the key\n\t// before dispatching), so we compute the key eagerly in `invoke`/`stream`\n\t// and thread it through as `_precomputedKey`. The passed `keyFn` reads\n\t// that precomputed value instead of re-hashing.\n\tconst upstreamInFlight = singleFromAny<ResolveArgsWithKey, LLMResponse>(\n\t\tasync ({ messages, invokeOpts }) => {\n\t\t\treturn await firstValueFrom(fromAny(inner.invoke(messages, invokeOpts)));\n\t\t},\n\t\t{ keyFn: ({ _precomputedKey }) => _precomputedKey },\n\t);\n\n\tconst wrap = adapterWrapper(inner, {\n\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\tconst key = await cache.keyFor(messages, invokeOpts);\n\t\t\tconst entry = await cache.lookup(messages, invokeOpts);\n\t\t\tif (entry?.response) {\n\t\t\t\tconst cached = entry.response;\n\t\t\t\treturn { ...cached, metadata: { ...(cached.metadata ?? {}), replayCache: \"hit\" } };\n\t\t\t}\n\n\t\t\tif (mode === \"read-strict\") throw new ReplayCacheMissError(key, \"invoke\");\n\t\t\tconst resp = await upstreamInFlight({ messages, invokeOpts, _precomputedKey: key });\n\t\t\tif (!isReadOnly) {\n\t\t\t\tawait cache.store(messages, invokeOpts, { response: resp, storedAtNs: wallClockNs() });\n\t\t\t}\n\t\t\treturn resp;\n\t\t},\n\n\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\tif (!cacheStreaming) {\n\t\t\t\t// `read-strict` only applies to cache-checked paths. When\n\t\t\t\t// `cacheStreaming: false` the cache isn't consulted for\n\t\t\t\t// streams at all, so passthrough is correct — throwing would\n\t\t\t\t// make the adapter's stream() permanently unusable.\n\t\t\t\tfor await (const delta of inner.stream(messages, invokeOpts)) yield delta;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst key = await cache.keyFor(messages, invokeOpts);\n\t\t\tconst entry = await cache.lookup(messages, invokeOpts);\n\t\t\tif (entry) {\n\t\t\t\tconst cached = entry.response;\n\t\t\t\t// Cadence-faithful replay when both recorded chunks + delays are present.\n\t\t\t\tif (entry.streamChunks && entry.streamCadenceMs) {\n\t\t\t\t\tfor (let i = 0; i < entry.streamChunks.length; i++) {\n\t\t\t\t\t\tconst delay = entry.streamCadenceMs[i] ?? 0;\n\t\t\t\t\t\tconst effective = replaySpeed > 0 ? delay / replaySpeed : 0;\n\t\t\t\t\t\tif (effective > 0) await sleepMs(effective);\n\t\t\t\t\t\tyield { type: \"token\", delta: entry.streamChunks[i]?.delta ?? \"\" };\n\t\t\t\t\t}\n\t\t\t\t} else if (cached.content) {\n\t\t\t\t\tyield { type: \"token\", delta: cached.content };\n\t\t\t\t}\n\t\t\t\tif (cached.usage) yield { type: \"usage\", usage: cached.usage };\n\t\t\t\tyield { type: \"finish\", reason: cached.finishReason ?? \"stop\" };\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (mode === \"read-strict\") throw new ReplayCacheMissError(key, \"stream\");\n\t\t\t// Miss: accumulate, store, re-yield.\n\t\t\tlet content = \"\";\n\t\t\tlet usage: LLMResponse[\"usage\"] | undefined;\n\t\t\tlet finishReason: string | undefined;\n\t\t\tconst chunks: { delta: string }[] = [];\n\t\t\tconst delaysMs: number[] = [];\n\t\t\t// Time-to-first-token (TTFT — provider latency / network / queue\n\t\t\t// warmup) is NOT cadence; setting lastNs inside the loop on first\n\t\t\t// chunk means chunk-0's delay is 0 (boundary-clean) and subsequent\n\t\t\t// delays measure only inter-chunk gaps. Use the central clock so\n\t\t\t// the scale matches every other cadence measurement in the library\n\t\t\t// and the module stays browser-safe (spec §5.11).\n\t\t\tlet lastNs: number | undefined;\n\t\t\tfor await (const delta of inner.stream(messages, invokeOpts)) {\n\t\t\t\tif (delta.type === \"token\") {\n\t\t\t\t\tcontent += delta.delta;\n\t\t\t\t\tif (captureStreamCadence) {\n\t\t\t\t\t\tconst now = monotonicNs();\n\t\t\t\t\t\tconst gap = lastNs === undefined ? 0 : (now - lastNs) / 1e6;\n\t\t\t\t\t\tdelaysMs.push(gap);\n\t\t\t\t\t\tlastNs = now;\n\t\t\t\t\t\tchunks.push({ delta: delta.delta });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (delta.type === \"usage\") usage = delta.usage;\n\t\t\t\tif (delta.type === \"finish\") finishReason = delta.reason;\n\t\t\t\tyield delta;\n\t\t\t}\n\t\t\tif ((content || usage) && !isReadOnly) {\n\t\t\t\tconst resp: LLMResponse = {\n\t\t\t\t\tcontent,\n\t\t\t\t\tusage: usage ?? { input: { regular: 0 }, output: { regular: 0 } },\n\t\t\t\t\tfinishReason,\n\t\t\t\t\tmodel: inner.model ?? invokeOpts?.model ?? \"\",\n\t\t\t\t\tprovider: inner.provider,\n\t\t\t\t};\n\t\t\t\tconst entryToStore: CachedEntry = {\n\t\t\t\t\tresponse: resp,\n\t\t\t\t\tstoredAtNs: wallClockNs(),\n\t\t\t\t\t...(captureStreamCadence ? { streamChunks: chunks, streamCadenceMs: delaysMs } : {}),\n\t\t\t\t};\n\t\t\t\tawait cache.store(messages, invokeOpts, entryToStore);\n\t\t\t}\n\t\t},\n\t});\n\twithLayer(wrap, \"withReplayCache\", inner);\n\treturn wrap;\n}\n\n// canonicalJson is no longer re-exported here — consumers import directly from\n// @graphrefly/pure-ts/extra. The presentation-layer re-export caused a\n// duplicate-export conflict at the root barrel level (A3 build gate).\n","/**\n * LLM-specific content-addressed cache wrapper over the generic\n * {@link contentAddressedStorage} substrate in `src/extra/`. Handles the\n * ChatMessage + LLMInvokeOptions → stable key shape that both\n * `withReplayCache` and `fallbackAdapter` need, so the two middleware files\n * don't drift on key construction.\n *\n * The generic substrate does the sha256 + canonicalJson + tier IO; this\n * wrapper owns:\n * - Stripping non-serializable `signal` (AbortSignal) and `keyContext`\n * (caller-opaque context) from the default key.\n * - Arity-dispatched legacy `keyFn` support (1-arg ctx-object form vs 2-arg\n * `(messages, opts)` form).\n * - LLM-conventional `llm-replay` default key prefix (override via `keyPrefix`).\n *\n * @module\n */\n\nimport type { KvStorageTier } from \"@graphrefly/pure-ts/extra\";\nimport {\n\ttype ContentAddressedMode,\n\ttype ContentAddressedStorage,\n\tcontentAddressedStorage,\n} from \"@graphrefly/pure-ts/extra\";\nimport type { ChatMessage, LLMInvokeOptions } from \"../core/types.js\";\n\n/**\n * Context object passed to the 1-arg {@link ContentAddressedCacheOptions.keyFn}\n * form. Adding fields here is forward-compatible — existing 1-arg consumers\n * that ignore unknowns keep working.\n */\nexport interface LLMCacheKeyContext {\n\treadonly messages: readonly ChatMessage[];\n\treadonly opts: LLMInvokeOptions | undefined;\n\t/** Shortcut to `opts?.keyContext` — no extra guard in callers. */\n\treadonly context: unknown;\n}\n\nexport interface ContentAddressedCacheOptions<V> {\n\tstorage: KvStorageTier;\n\tmode?: ContentAddressedMode;\n\t/**\n\t * Custom key function. Receives either {@link LLMCacheKeyContext} (1-arg\n\t * form) or `(messages, opts?)` (legacy 2-arg form) — the wrapper dispatches\n\t * on `Function.length`. Return `string` or `Promise<string>`.\n\t */\n\tkeyFn?:\n\t\t| ((ctx: LLMCacheKeyContext) => string | Promise<string>)\n\t\t| ((messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string | Promise<string>);\n\t/** Prefix applied as `${keyPrefix}:${hash}`. Default `\"llm-replay\"`. */\n\tkeyPrefix?: string;\n\t/**\n\t * Optional value type hint — callers may store anything JSON-serializable.\n\t * `withReplayCache` stores `CachedEntry` (response + stream cadence);\n\t * `fallbackAdapter` stores the raw `LLMResponse`.\n\t */\n\t_valueType?: V;\n}\n\n/** Handle returned by {@link contentAddressedCache}. */\nexport interface ContentAddressedCache<V> {\n\tkeyFor(messages: readonly ChatMessage[], invokeOpts?: LLMInvokeOptions): Promise<string>;\n\tlookup(messages: readonly ChatMessage[], invokeOpts?: LLMInvokeOptions): Promise<V | undefined>;\n\tstore(\n\t\tmessages: readonly ChatMessage[],\n\t\tinvokeOpts: LLMInvokeOptions | undefined,\n\t\tvalue: V,\n\t): Promise<void>;\n\tforget(messages: readonly ChatMessage[], invokeOpts?: LLMInvokeOptions): Promise<void>;\n}\n\n/**\n * Builds an LLM-shaped content-addressed cache over `storage`. Default keying\n * hashes `(messages, opts minus signal/keyContext)` via\n * {@link contentAddressedStorage} — the generic extra-tier substrate.\n *\n * Used by `withReplayCache` (full response cache) and `fallbackAdapter`\n * (fixture lookup). Both consumers wrap this to add their divergent features\n * (singleflight / stream cadence / canned miss response) around the shared\n * substrate.\n *\n * @category internal\n */\nexport function contentAddressedCache<V>(\n\topts: ContentAddressedCacheOptions<V>,\n): ContentAddressedCache<V> {\n\tconst { storage, mode = \"read-write\", keyFn, keyPrefix = \"llm-replay\" } = opts;\n\n\t// Substrate mode: translate `\"read-strict\"` to `\"read\"` so the substrate\n\t// returns `undefined` on miss. The LLM-middleware callers (`withReplayCache`,\n\t// `fallbackAdapter`) own the strict-mode throw because they emit\n\t// domain-specific error types (`ReplayCacheMissError` / `FallbackMissError`)\n\t// that carry context the substrate shouldn't know about.\n\tconst substrateMode = mode === \"read-strict\" ? \"read\" : mode;\n\n\t// When `keyFn` is supplied, we wire it through the substrate's raw-key API\n\t// (via a synthesized keyContext passthrough). When omitted, we let the\n\t// substrate hash the default shape (messages + opts minus non-serializable\n\t// fields) with the standard prefix.\n\tconst substrate: ContentAddressedStorage<\n\t\t{ messages: readonly ChatMessage[]; opts: LLMInvokeOptions | undefined },\n\t\tV\n\t> = contentAddressedStorage({\n\t\tstorage,\n\t\tkeyPrefix,\n\t\tmode: substrateMode,\n\t\tkeyContext: ({ messages, opts: invokeOpts }) => {\n\t\t\t// Drop `signal` (not serializable) and `keyContext` (caller-opaque\n\t\t\t// context should only participate when user opts in via keyFn).\n\t\t\tconst { signal: _signal, keyContext: _keyContext, ...rest } = invokeOpts ?? {};\n\t\t\treturn { messages, opts: rest };\n\t\t},\n\t});\n\n\tasync function keyFor(\n\t\tmessages: readonly ChatMessage[],\n\t\tinvokeOpts?: LLMInvokeOptions,\n\t): Promise<string> {\n\t\tif (keyFn) {\n\t\t\t// Arity-dispatch: Function.length === 1 → ctx-object form;\n\t\t\t// otherwise → legacy 2-arg (messages, opts). Both may return\n\t\t\t// sync or async strings.\n\t\t\tif (keyFn.length <= 1) {\n\t\t\t\tconst ctx: LLMCacheKeyContext = {\n\t\t\t\t\tmessages,\n\t\t\t\t\topts: invokeOpts,\n\t\t\t\t\tcontext: invokeOpts?.keyContext,\n\t\t\t\t};\n\t\t\t\tconst raw = await (keyFn as (c: LLMCacheKeyContext) => string | Promise<string>)(ctx);\n\t\t\t\treturn `${keyPrefix}:${raw}`;\n\t\t\t}\n\t\t\tconst raw = await (\n\t\t\t\tkeyFn as (m: readonly ChatMessage[], o?: LLMInvokeOptions) => string | Promise<string>\n\t\t\t)(messages, invokeOpts);\n\t\t\treturn `${keyPrefix}:${raw}`;\n\t\t}\n\t\treturn substrate.keyFor({ messages, opts: invokeOpts });\n\t}\n\n\treturn {\n\t\tkeyFor,\n\n\t\tasync lookup(messages, invokeOpts) {\n\t\t\tif (mode === \"write\") return undefined;\n\t\t\tif (keyFn) {\n\t\t\t\t// Custom keyFn path: we can't use substrate.lookup (it hashes\n\t\t\t\t// from its own keyContext). Build the key ourselves and load.\n\t\t\t\t// Strict-mode throw is the CALLER'S responsibility — we return\n\t\t\t\t// undefined on miss so callers can wrap the throw with their\n\t\t\t\t// domain-specific error type.\n\t\t\t\tconst key = await keyFor(messages, invokeOpts);\n\t\t\t\tconst raw = await storage.load(key);\n\t\t\t\tif (raw === undefined) return undefined;\n\t\t\t\treturn raw as V;\n\t\t\t}\n\t\t\treturn substrate.lookup({ messages, opts: invokeOpts });\n\t\t},\n\n\t\tasync store(messages, invokeOpts, value) {\n\t\t\tif (mode === \"read\") return;\n\t\t\tif (keyFn) {\n\t\t\t\tconst key = await keyFor(messages, invokeOpts);\n\t\t\t\tawait storage.save(key, value as unknown);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait substrate.store({ messages, opts: invokeOpts }, value);\n\t\t},\n\n\t\tasync forget(messages, invokeOpts) {\n\t\t\tif (mode === \"read\" || mode === \"write\") return;\n\t\t\tif (!storage.delete) return;\n\t\t\tif (keyFn) {\n\t\t\t\tconst key = await keyFor(messages, invokeOpts);\n\t\t\t\tawait storage.delete(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait substrate.forget({ messages, opts: invokeOpts });\n\t\t},\n\t};\n}\n","/**\n * Shared shell + shape-dispatch helpers for the LLM adapter layer.\n *\n * Wave A Unit 11 decision: the 9 middleware files and the observable adapter\n * all repeated the same three boilerplate patterns:\n *\n * 1. Building the returned `LLMAdapter` shell (provider/model/capabilities\n * pass-through). → `adapterWrapper(inner, {invoke, stream})`.\n * 2. Dispatching the adapter's `NodeInput<LLMResponse>` result across its\n * three possible shapes (Promise / Node / plain value) + a `recordedOnce`\n * double-record guard on the reactive path. → `adaptInvokeResult`.\n * 3. Constructing a `CallStatsEvent` from provider / model / tier / usage /\n * latency. → `buildCallStats`.\n *\n * Two small additions:\n * - `withLayer(adapter, layerName)` stamps a `meta.middlewareLayer` tag on\n * the returned adapter via a non-enumerable property, enabling\n * `describeAdapterStack(adapter)` to walk the wrap chain bottom-up.\n * - `describeAdapterStack(adapter)` returns the list of layer names from\n * innermost to outermost so users can inspect the resilient stack the same\n * way they inspect graph topology.\n */\n\nimport { ERROR, monotonicNs, type Node, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport { fromAny, onFirstData } from \"@graphrefly/pure-ts/extra\";\nimport type { CallStatsEvent } from \"../core/observable.js\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tNodeInput,\n\tStreamDelta,\n\tTokenUsage,\n} from \"../core/types.js\";\n\n// ---------------------------------------------------------------------------\n// adapterWrapper — LLMAdapter shell with provider/model/capabilities pass-through\n// ---------------------------------------------------------------------------\n\n/** Callable shape for the invoke / stream bodies that `adapterWrapper` composes. */\nexport type AdapterInvokeFn = (\n\tmessages: readonly ChatMessage[],\n\topts?: LLMInvokeOptions,\n) => NodeInput<LLMResponse>;\n\nexport type AdapterStreamFn = (\n\tmessages: readonly ChatMessage[],\n\topts?: LLMInvokeOptions,\n) => AsyncIterable<StreamDelta>;\n\n/**\n * Builds an `LLMAdapter` shell around the given `invoke` / `stream`\n * implementations. Pass-through fields — `provider`, `model`, `capabilities`\n * — come from `inner` unless the caller overrides (`override.provider` etc.).\n *\n * Middleware files used to repeat `provider: inner.provider, model: inner.model,\n * capabilities: inner.capabilities?.bind(inner)` verbatim; this helper\n * captures that once.\n *\n * @category internal\n */\nexport function adapterWrapper(\n\tinner: LLMAdapter,\n\timpl: { invoke: AdapterInvokeFn; stream: AdapterStreamFn },\n\toverride?: Partial<Pick<LLMAdapter, \"provider\" | \"model\" | \"capabilities\">>,\n): LLMAdapter {\n\treturn {\n\t\tprovider: override?.provider ?? inner.provider,\n\t\tmodel: override?.model ?? inner.model,\n\t\tcapabilities: override?.capabilities ?? inner.capabilities?.bind(inner),\n\t\tinvoke: impl.invoke,\n\t\tstream: impl.stream,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// adaptInvokeResult — shape-dispatch for NodeInput<LLMResponse>\n// ---------------------------------------------------------------------------\n\n/**\n * Shape-dispatch helper for `inner.invoke(...)` return values. Converts any of\n * the three `NodeInput<LLMResponse>` shapes (Promise, plain value, Node /\n * iterable) into the caller-requested output, applying `onResp` exactly once\n * on the first DATA emission.\n *\n * **Paths.**\n * - `Promise<LLMResponse>` → `Promise<R>` (chains `onResp` via `.then`; if\n * `onError` is provided, wires `.catch` so rejected Promises flow to the\n * error path just like the stream body does).\n * - Plain `LLMResponse` (object with `content` field) → `R` (calls `onResp`\n * synchronously and returns the mapped value).\n * - Anything else → reactive `Node<R>` via `fromAny` + `onFirstData(onResp)`\n * so late subscribers don't re-fire the side effect.\n *\n * @category internal\n */\nexport function adaptInvokeResult<R>(\n\tinput: NodeInput<LLMResponse>,\n\topts: {\n\t\tonResp: (resp: LLMResponse) => R;\n\t\t/**\n\t\t * If provided, rejected Promises are piped through this handler (for\n\t\t * stats / budget recording) before re-throwing. Signature mirrors the\n\t\t * stream try/catch shape so callers can share a single error-recording\n\t\t * closure across both paths.\n\t\t */\n\t\tonError?: (err: unknown) => void;\n\t\t/** Optional node name for the reactive-path derived (describe-friendly). */\n\t\tname?: string;\n\t},\n): Promise<R> | R | Node<R> {\n\tconst { onResp, onError, name } = opts;\n\n\t// Promise / thenable: chain .then, optionally .catch.\n\tif (input != null && typeof (input as PromiseLike<LLMResponse>).then === \"function\") {\n\t\tconst p = input as Promise<LLMResponse>;\n\t\tif (onError) {\n\t\t\treturn p.then(onResp).catch((err: unknown) => {\n\t\t\t\tonError(err);\n\t\t\t\tthrow err;\n\t\t\t});\n\t\t}\n\t\treturn p.then(onResp);\n\t}\n\t// Plain LLMResponse (synchronous).\n\tif (input != null && typeof input === \"object\" && \"content\" in (input as object)) {\n\t\treturn onResp(input as LLMResponse);\n\t}\n\t// Reactive / iterable — map inside a `derived` guarded by `onFirstData`\n\t// so the side-effect fires exactly once per node lifetime (push-on-\n\t// subscribe replay on late subscribers is silent).\n\tconst bridged = fromAny(input);\n\t// Wire `onError` via a side-subscription on the bridged node. `onFirstData`\n\t// only handles DATA — ERROR messages need their own hook so stats /\n\t// budget recording fires symmetrically with the Promise path. The\n\t// subscription runs for the lifetime of the derived below (keepalive via\n\t// the downstream derived's activation), and fires at most once per ERROR\n\t// frame. Without this, ERRORs on Node-shaped adapter returns silently\n\t// bypass budget/observable recording.\n\tif (onError) {\n\t\tlet errored = false;\n\t\tbridged.subscribe((msgs) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (errored) return;\n\t\t\t\tif ((m as readonly [symbol, unknown])[0] === ERROR) {\n\t\t\t\t\terrored = true;\n\t\t\t\t\tonError((m as readonly [symbol, unknown])[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\t// `captured` holds the mapped value from the first DATA so re-subscribers\n\t// and re-emissions see a stable mapped value without re-firing `onResp`.\n\t// Use a distinct `mapped` sentinel (not nullish) because `onResp` may\n\t// legitimately return `null` / `undefined` / `0` — the prior\n\t// `captured ?? onResp(v)` guard re-fired `onResp` on falsy captured, which\n\t// broke the \"exactly once per node lifetime\" contract.\n\tlet captured: R;\n\tlet mapped = false;\n\tconst tapped = onFirstData(bridged, (v) => {\n\t\tcaptured = onResp(v);\n\t\tmapped = true;\n\t});\n\treturn node<R>(\n\t\t[tapped],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst v = data[0];\n\t\t\tif (v == null) {\n\t\t\t\tactions.emit(null as R);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (mapped) {\n\t\t\t\tactions.emit(captured);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tactions.emit(onResp(v as LLMResponse));\n\t\t},\n\t\t{ describeKind: \"derived\", name: name ?? \"adapt/invokeTap\" },\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// buildCallStats — CallStatsEvent constructor\n// ---------------------------------------------------------------------------\n\nexport interface BuildCallStatsArgs {\n\tprovider: string;\n\tmodel: string;\n\ttier?: string;\n\tusage: TokenUsage;\n\tstartNs: number;\n\tmethod: \"invoke\" | \"stream\";\n\terror?: { readonly type: string; readonly message: string };\n\t/** Override the wall-clock stamp (useful for tests / replay). */\n\tstartWallClockNs?: number;\n\t/** Override the monotonic end stamp (useful for tests). */\n\tendNs?: number;\n}\n\n/**\n * Constructs a {@link CallStatsEvent} from the arguments observable.ts and\n * budget-gate.ts used to assemble inline. The `timestamp` / `latencyMs` are\n * computed from `startNs` + (optional) `endNs`; `wallClock` is snapshot at\n * call-start via `wallClockNs()` unless overridden.\n *\n * @category internal\n */\nexport function buildCallStats(args: BuildCallStatsArgs): CallStatsEvent {\n\tconst end = args.endNs ?? monotonicNs();\n\treturn {\n\t\ttimestamp: end,\n\t\twallClock: args.startWallClockNs ?? wallClockNs(),\n\t\tprovider: args.provider,\n\t\tmodel: args.model,\n\t\ttier: args.tier,\n\t\tusage: args.usage,\n\t\tlatencyMs: Math.max(0, (end - args.startNs) / 1e6),\n\t\tmethod: args.method,\n\t\t...(args.error ? { error: args.error } : {}),\n\t};\n}\n\n/** Convenience — empty disaggregated usage stub used by every middleware. */\nexport function emptyUsageStub(): TokenUsage {\n\treturn { input: { regular: 0 }, output: { regular: 0 } };\n}\n\n// ---------------------------------------------------------------------------\n// meta.middlewareLayer + describeAdapterStack (Unit 11 Q1 user directive)\n// ---------------------------------------------------------------------------\n\n/** Symbol key carrying the wrap chain on the returned adapter. Non-enumerable. */\nconst MIDDLEWARE_LAYERS = Symbol.for(\"graphrefly.adapter.middlewareLayers\");\n\n/**\n * Stamp `adapter` with a middleware-layer name and return it. The stamp is a\n * non-enumerable property keyed by `Symbol.for(\"graphrefly.adapter.middlewareLayers\")`\n * — opaque to users, visible via {@link describeAdapterStack}.\n *\n * Each wrap prepends its layer to `inner`'s chain so the stack can be walked\n * bottom-up (innermost first). Providers have no layer stamp — they show up\n * as the bottom of the chain via their `provider` / `model` identity.\n *\n * @category internal\n */\nexport function withLayer<A extends LLMAdapter>(\n\tadapter: A,\n\tlayerName: string,\n\tinner?: LLMAdapter,\n): A {\n\tconst innerLayers = inner ? readLayers(inner) : [];\n\tconst chain = [...innerLayers, layerName];\n\tObject.defineProperty(adapter, MIDDLEWARE_LAYERS, {\n\t\tvalue: Object.freeze(chain),\n\t\tenumerable: false,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t});\n\treturn adapter;\n}\n\n/**\n * Returns the middleware-layer names stamped on `adapter`, innermost first.\n * An adapter that has never been wrapped returns `[]` — callers combine the\n * result with `adapter.provider` / `adapter.model` for a full stack render.\n *\n * @example\n * ```ts\n * const stack = describeAdapterStack(resilientAdapter(anthropicAdapter(), opts).adapter);\n * // → [\"withLLMTimeout\", \"withRetry\", \"withLLMBreaker\", \"withBudgetGate\", \"withRateLimiter\", \"cascade\"]\n * ```\n *\n * @category extra\n */\nexport function describeAdapterStack(adapter: LLMAdapter): readonly string[] {\n\treturn readLayers(adapter);\n}\n\nfunction readLayers(adapter: LLMAdapter): readonly string[] {\n\tconst v = (adapter as unknown as Record<symbol, unknown>)[MIDDLEWARE_LAYERS];\n\treturn Array.isArray(v) ? (v as readonly string[]) : [];\n}\n","/**\n * `fallbackAdapter` — fixture-backed {@link LLMAdapter} for offline demos,\n * deterministic tests, and graceful degradation in production.\n *\n * A peer of `anthropicAdapter` / `openAICompatAdapter` / `googleAdapter` /\n * `ollamaAdapter` / `dryRunAdapter`, but whose role is to serve pre-recorded\n * or canned responses when real providers aren't reachable. Install it as a\n * tier via the existing routing primitives (no new composer needed):\n *\n * ```ts\n * // Graceful offline fallback for a user app:\n * resilientAdapter(anthropicAdapter({ ... }), {\n * fallback: fallbackAdapter({ fixturesDir: \"./fixtures\" }),\n * });\n *\n * // Or the general N-tier shape:\n * cascadingLlmAdapter([\n * { name: \"primary\", adapter: anthropicAdapter({ ... }) },\n * { name: \"fallback\", adapter: fallbackAdapter({ fixturesDir: \"./fixtures\" }) },\n * ]);\n * ```\n *\n * The `provider` field is `\"fallback\"` so its role is self-documenting in\n * logs, stats, cost tables, and audit trails.\n *\n * ## Three fixture sources (mutually exclusive)\n *\n * Pick exactly one of:\n *\n * 1. **`fixtures: FallbackFixture[]`** — inline, hand-authored. Supports\n * both hash-keyed and messages-keyed shapes; the adapter computes the\n * canonical hash for messages-keyed entries at init time. Ideal when you\n * want full control in code (tests, small demos).\n *\n * 2. **`fixturesStorage: KvStorageTier`** — the escape hatch for any backend.\n * Pass a `memoryKv()`, `indexedDbKv(...)`, `sqliteKv(...)`,\n * `cascadingCache(...)`, or a custom tier. You own the layout — no\n * auto-namespacing.\n *\n * **Filesystem directories (Node only):** the core `fallbackAdapter`\n * does NOT import `node:fs` / `node:path` — it's safe to bundle for\n * browsers. For a directory convenience, import `fallbackAdapter` from\n * `@graphrefly/graphrefly/patterns/ai/node` (node subpath);\n * that variant adds `fixturesDir: string` (auto-namespaced to\n * `join(dir, keyPrefix)`, cache-format validated at init).\n *\n * ## Record mode\n *\n * `record: { adapter: real, storage }` proxies every call to `real` AND\n * persists the response through the provided tier. Use the node subpath's\n * `fallbackAdapter` for `record.dir` (auto-namespaced + `record.dir` defaults\n * to `fixturesDir` when both are file-backed).\n *\n * ## Three use cases, one implementation\n *\n * | Use case | Config |\n * |---|---|\n * | **User apps** — degrade when the cloud provider errors or network is down | `fallbackAdapter({ fixturesStorage: ... })` installed as a fallback tier |\n * | **Tests** — deterministic replays, fail loudly on miss | `fallbackAdapter({ fixturesStorage: ..., onMiss: \"throw\" })` |\n * | **Eval offline replay** — zero-spend repeat runs | `fallbackAdapter({ fixturesStorage: ... })` as the only adapter |\n *\n * ## Implementation\n *\n * Thin sugar over {@link withReplayCache}. Key shape comes from its\n * `canonicalJson` — fixtures written by either tool are interchangeable.\n *\n * @module\n */\n\nimport { sha256Hex, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport { canonicalJson, type KvStorageTier, memoryKv } from \"@graphrefly/pure-ts/extra\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tStreamDelta,\n} from \"../core/types.js\";\nimport {\n\ttype ReplayCacheKeyContext,\n\tReplayCacheMissError,\n\twithReplayCache,\n} from \"../middleware/replay-cache.js\";\nimport { dryRunAdapter } from \"./dry-run.js\";\n\nexport type FallbackMissPolicy = \"throw\" | \"respond\";\n\n/**\n * Thrown when `fallbackAdapter({ onMiss: \"throw\" })` receives a request that\n * has no matching fixture. Alias of `ReplayCacheMissError` for now — the\n * adapter is a thin sugar over `withReplayCache` and shares its miss-error.\n */\nexport const FallbackMissError = ReplayCacheMissError;\nexport type FallbackMissError = ReplayCacheMissError;\n\n/**\n * One recorded fixture. Two authoring shapes:\n * - **Hash-keyed** — `{ key, response, stream? }`. Key is `sha256(canonicalJson({messages, opts}))`\n * with `fallback:` prefix. This is what `record` mode writes.\n * - **Messages-keyed** — `{ messages, invokeOpts?, response }`. The adapter\n * computes the key at init time. Ergonomic for hand-authored fixtures.\n */\nexport type FallbackFixture =\n\t| {\n\t\t\treadonly key: string;\n\t\t\treadonly response: LLMResponse;\n\t\t\treadonly stream?: {\n\t\t\t\treadonly chunks: readonly StreamDelta[];\n\t\t\t\treadonly delaysMs?: readonly number[];\n\t\t\t};\n\t }\n\t| {\n\t\t\treadonly messages: readonly ChatMessage[];\n\t\t\treadonly invokeOpts?: Omit<LLMInvokeOptions, \"signal\">;\n\t\t\treadonly response: LLMResponse;\n\t };\n\nexport interface FallbackAdapterOptions {\n\t/** Adapter provider label. Default `\"fallback\"`. */\n\treadonly provider?: string;\n\t/** Adapter model label. Default `\"fallback\"`. */\n\treadonly model?: string;\n\t/**\n\t * Inline hand-authored fixtures. Supports both hash-keyed (`{key, response, stream?}`)\n\t * and messages-keyed (`{messages, invokeOpts?, response}`) shapes — the adapter\n\t * computes the canonical hash for messages-keyed entries at init time. Held in\n\t * an internal `memoryKv`. Mutually exclusive with `fixturesDir` and\n\t * `fixturesStorage`.\n\t */\n\treadonly fixtures?: readonly FallbackFixture[];\n\t/**\n\t * Bring-your-own `KvStorageTier` (`memoryKv`, `sqliteKv`,\n\t * `indexedDbKv`, `cascadingCache`, or a custom tier). You own the\n\t * layout — no auto-namespacing. Mutually exclusive with `fixtures`.\n\t *\n\t * For filesystem directories, use the node subpath's `fallbackAdapter`\n\t * with its `fixturesDir` option (auto-namespaced + validated).\n\t */\n\treadonly fixturesStorage?: KvStorageTier;\n\t/**\n\t * Called on fixture miss when `onMiss === \"respond\"`. If not provided and\n\t * `onMiss === \"respond\"`, a canned \"service unavailable\" response is\n\t * returned (marked with `metadata.degraded: true`).\n\t */\n\treadonly respond?: (\n\t\tmessages: readonly ChatMessage[],\n\t\topts?: LLMInvokeOptions,\n\t) => string | LLMResponse;\n\t/** Miss policy. Default `\"respond\"`. */\n\treadonly onMiss?: FallbackMissPolicy;\n\t/**\n\t * Record mode. Proxies every call to `record.adapter` AND persists the\n\t * result through `record.storage`. For filesystem `record.dir` convenience,\n\t * use the node subpath's `fallbackAdapter`.\n\t */\n\treadonly record?: {\n\t\treadonly adapter: LLMAdapter;\n\t\treadonly storage?: KvStorageTier;\n\t};\n\t/** Stream replay speed multiplier. See {@link withReplayCache}. Default `1`. */\n\treadonly replaySpeed?: number;\n\t/** Key prefix. Kept compatible with `withReplayCache` defaults. Default `\"fallback\"`. */\n\treadonly keyPrefix?: string;\n\t/**\n\t * Custom key function — forwarded directly to the underlying\n\t * {@link withReplayCache}. Use to shard fixtures by `invokeOpts.keyContext`\n\t * (tenant, session, feature flag). Accepts either the new\n\t * {@link ReplayCacheKeyContext} object form or the legacy 2-arg\n\t * `(messages, opts?)` form.\n\t */\n\treadonly keyFn?:\n\t\t| ((ctx: ReplayCacheKeyContext) => string)\n\t\t| ((messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string);\n}\n\n// ---------------------------------------------------------------------------\n// Canned degraded response\n// ---------------------------------------------------------------------------\n\nfunction degradedResponse(provider: string, model: string): LLMResponse {\n\treturn {\n\t\tcontent: \"[fallback: no cached response available for this request]\",\n\t\tusage: { input: { regular: 0 }, output: { regular: 0 } },\n\t\tfinishReason: \"stop\",\n\t\tmodel,\n\t\tprovider,\n\t\tmetadata: { degraded: true, reason: \"no-fixture\" },\n\t};\n}\n\nfunction normalizeRespondResult(\n\traw: string | LLMResponse,\n\tprovider: string,\n\tmodel: string,\n): LLMResponse {\n\tif (typeof raw === \"string\") {\n\t\treturn {\n\t\t\tcontent: raw,\n\t\t\tusage: { input: { regular: 0 }, output: { regular: 0 } },\n\t\t\tfinishReason: \"stop\",\n\t\t\tmodel,\n\t\t\tprovider,\n\t\t\tmetadata: { degraded: true, reason: \"respond\" },\n\t\t};\n\t}\n\treturn raw;\n}\n\n// ---------------------------------------------------------------------------\n// Fixture → storage tier conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the key a `FallbackFixture` would hash to. Messages-keyed fixtures\n * get their key derived on the spot; hash-keyed fixtures pass through. Async\n * because `sha256Hex` uses `globalThis.crypto.subtle` (universal, no\n * `node:crypto` leak) — see {@link sha256Hex}.\n */\nasync function fixtureKey(fixture: FallbackFixture, keyPrefix: string): Promise<string> {\n\tif (\"key\" in fixture) return fixture.key;\n\tconst canonical = canonicalJson({ messages: fixture.messages, opts: fixture.invokeOpts ?? {} });\n\tconst hex = await sha256Hex(canonical);\n\treturn `${keyPrefix}:${hex}`;\n}\n\n/**\n * `withReplayCache` stores values as `CachedEntry = { response, storedAtNs, streamChunks?, streamCadenceMs? }`.\n * Convert a `FallbackFixture` into that shape so inline fixtures can be\n * seeded into a memory tier alongside file-authored ones.\n */\nfunction toCachedEntry(fixture: FallbackFixture): unknown {\n\tconst base: { response: LLMResponse; storedAtNs: number } = {\n\t\tresponse: fixture.response,\n\t\t// Real timestamp so future TTL-aware tiers don't treat inline fixtures\n\t\t// as Epoch-old and evict them on first pass. Matches the wall-clock\n\t\t// write `withReplayCache` uses for disk-written entries.\n\t\tstoredAtNs: wallClockNs(),\n\t};\n\tif (\"key\" in fixture && fixture.stream) {\n\t\tconst tokenChunks = fixture.stream.chunks.filter(\n\t\t\t(c): c is Extract<StreamDelta, { type: \"token\" }> => c.type === \"token\",\n\t\t);\n\t\treturn {\n\t\t\t...base,\n\t\t\tstreamChunks: tokenChunks.map((c) => ({ delta: c.delta })),\n\t\t\tstreamCadenceMs: fixture.stream.delaysMs ?? tokenChunks.map(() => 0),\n\t\t};\n\t}\n\treturn base;\n}\n\n/**\n * Resolve the fixture source to a `KvStorageTier`. Enforces mutual exclusion\n * between `fixtures` and `fixturesStorage`. When `fixtures` is provided, the\n * seeded memory tier is returned synchronously but keys are populated\n * asynchronously via the returned seeding promise (await before first use).\n */\nfunction resolveFixtureStorage(\n\topts: FallbackAdapterOptions,\n\tkeyPrefix: string,\n): { tier: KvStorageTier | undefined; seedReady: Promise<void> } {\n\tconst sources: string[] = [];\n\tif (opts.fixtures != null) sources.push(\"fixtures\");\n\tif (opts.fixturesStorage != null) sources.push(\"fixturesStorage\");\n\tif (sources.length > 1) {\n\t\tthrow new TypeError(\n\t\t\t`fallbackAdapter: \\`fixtures\\` and \\`fixturesStorage\\` are mutually ` +\n\t\t\t\t`exclusive; got both ${sources.join(\" and \")}. Pick one source. ` +\n\t\t\t\t`For filesystem directories use the node subpath's \\`fallbackAdapter\\`.`,\n\t\t);\n\t}\n\tif (opts.fixtures) {\n\t\tconst tier = memoryKv();\n\t\tconst fixtures = opts.fixtures;\n\t\tconst seedReady = (async () => {\n\t\t\tfor (const fixture of fixtures) {\n\t\t\t\tconst key = await fixtureKey(fixture, keyPrefix);\n\t\t\t\tawait tier.save(key, toCachedEntry(fixture));\n\t\t\t}\n\t\t})();\n\t\t// Attach a no-op catch so a failure inside the IIFE (e.g. `sha256Hex`\n\t\t// throws because `globalThis.crypto.subtle` is unavailable) does NOT\n\t\t// become an unhandled rejection when the adapter is constructed but\n\t\t// never invoked. V8 records the handler attachment via this branch\n\t\t// of the promise graph, so the Node `unhandledRejection` hook stays\n\t\t// silent — yet subsequent `await seedReady` inside `invoke`/`stream`\n\t\t// still throws the original error for the caller to see.\n\t\tseedReady.catch(() => {});\n\t\treturn { tier, seedReady };\n\t}\n\tif (opts.fixturesStorage) return { tier: opts.fixturesStorage, seedReady: Promise.resolve() };\n\treturn { tier: undefined, seedReady: Promise.resolve() };\n}\n\n// ---------------------------------------------------------------------------\n// fallbackAdapter\n// ---------------------------------------------------------------------------\n\n/**\n * Build a fixture-backed {@link LLMAdapter}. See module docs for use cases\n * (offline demo, tests, degraded-mode) and recipe snippets.\n */\nexport function fallbackAdapter(opts: FallbackAdapterOptions = {}): LLMAdapter {\n\tconst provider = opts.provider ?? \"fallback\";\n\tconst model = opts.model ?? \"fallback\";\n\tconst onMiss: FallbackMissPolicy = opts.onMiss ?? \"respond\";\n\tconst keyPrefix = opts.keyPrefix ?? \"fallback\";\n\n\t// Pick the inner leaf adapter based on mode.\n\tconst leaf: LLMAdapter = (() => {\n\t\tif (opts.record) return opts.record.adapter;\n\t\tif (onMiss === \"throw\") {\n\t\t\t// `withReplayCache({ mode: \"read-strict\" })` throws on miss before\n\t\t\t// ever calling the inner. Supply a no-op to satisfy the type.\n\t\t\treturn dryRunAdapter({\n\t\t\t\tprovider,\n\t\t\t\tmodel,\n\t\t\t\trespond: () => \"[unreachable: read-strict mode throws on miss]\",\n\t\t\t});\n\t\t}\n\t\t// Custom leaf (not `dryRunAdapter`) so `metadata.degraded` is preserved\n\t\t// — `dryRunAdapter` returns a string and constructs its own response,\n\t\t// discarding our metadata. For the respond-on-miss path we want full\n\t\t// `LLMResponse` control.\n\t\treturn {\n\t\t\tprovider,\n\t\t\tmodel,\n\t\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\t\tconst raw = opts.respond\n\t\t\t\t\t? opts.respond(messages, invokeOpts)\n\t\t\t\t\t: degradedResponse(provider, model);\n\t\t\t\treturn normalizeRespondResult(raw, provider, model);\n\t\t\t},\n\t\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\t\tconst raw = opts.respond\n\t\t\t\t\t? opts.respond(messages, invokeOpts)\n\t\t\t\t\t: degradedResponse(provider, model);\n\t\t\t\tconst r = normalizeRespondResult(raw, provider, model);\n\t\t\t\tyield { type: \"token\", delta: r.content };\n\t\t\t\tif (r.usage) yield { type: \"usage\", usage: r.usage };\n\t\t\t\tyield { type: \"finish\", reason: r.finishReason ?? \"stop\" };\n\t\t\t},\n\t\t} satisfies LLMAdapter;\n\t})();\n\n\t// Resolve the storage tier.\n\t// - `record` mode: require `record.storage`.\n\t// - Replay-only: `fixtures` seeds an in-memory tier (async) OR\n\t// `fixturesStorage` passes through.\n\tlet storage: KvStorageTier;\n\tlet seedReady: Promise<void> = Promise.resolve();\n\tif (opts.record) {\n\t\tif (!opts.record.storage) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"fallbackAdapter: `record.storage` is required in record mode. For filesystem \" +\n\t\t\t\t\t\"`record.dir` convenience, use the node subpath's `fallbackAdapter`.\",\n\t\t\t);\n\t\t}\n\t\tstorage = opts.record.storage;\n\t} else {\n\t\tconst resolved = resolveFixtureStorage(opts, keyPrefix);\n\t\tstorage = resolved.tier ?? memoryKv();\n\t\tseedReady = resolved.seedReady;\n\t}\n\n\tconst mode = opts.record ? \"read-write\" : onMiss === \"throw\" ? \"read-strict\" : \"read\";\n\n\tconst cached = withReplayCache(leaf, {\n\t\tstorage,\n\t\tmode,\n\t\tkeyPrefix,\n\t\tcacheStreaming: true,\n\t\tcaptureStreamCadence: true,\n\t\treplaySpeed: opts.replaySpeed,\n\t\t...(opts.keyFn ? { keyFn: opts.keyFn } : {}),\n\t});\n\n\t// Wrap invoke/stream so the first call awaits the seed-complete Promise.\n\t// Adapter construction stays synchronous (`fallbackAdapter(...)` returns\n\t// immediately); inline fixture hashing happens lazily on first use.\n\treturn {\n\t\tprovider,\n\t\tmodel,\n\t\tcapabilities: cached.capabilities?.bind(cached),\n\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\tawait seedReady;\n\t\t\t// `cached` came from `withReplayCache`, whose `invoke` always returns\n\t\t\t// `Promise<LLMResponse>`. The `LLMAdapter` interface types it as the\n\t\t\t// broader `NodeInput<LLMResponse>` union; narrow here to the actual\n\t\t\t// shape so the wrapper surface stays `Promise<LLMResponse>`.\n\t\t\treturn cached.invoke(messages, invokeOpts) as Promise<LLMResponse>;\n\t\t},\n\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\tawait seedReady;\n\t\t\tfor await (const delta of cached.stream(messages, invokeOpts)) yield delta;\n\t\t},\n\t};\n}\n"],"mappings":"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","names":["monotonicNs","ResettableTimer","wallClockNs","fromAny","raw","ResettableTimer","fromAny","wallClockNs","monotonicNs","wallClockNs","wallClockNs"]}
1
+ {"version":3,"sources":["../src/utils/ai/adapters/providers/dry-run.ts","../src/utils/ai/adapters/middleware/replay-cache.ts","../src/utils/ai/adapters/_internal/content-addressed-cache.ts","../src/utils/ai/adapters/_internal/wrappers.ts","../src/utils/ai/adapters/providers/fallback.ts"],"sourcesContent":["/**\n * DryRunAdapter — zero-cost mock provider.\n *\n * Returns a deterministic fake response (plus a configurable hook for\n * customization). Useful for: pipeline smoke tests, CI without API keys,\n * local development, and as the leaf of a `cascadingLlmAdapter` when every\n * real tier fails.\n *\n * The library ships a minimal implementation only — richer scenario-\n * scripted mocks (per-stage responses, call recording) belong at the test\n * harness layer, not in the shipped library.\n *\n * Uses `ResettableTimer` for simulated latency (spec §5.10 escape hatch\n * documented on the class), and throws an `AbortError`-named Error on\n * abort so retry/timeout middleware can classify it.\n */\n\nimport { ResettableTimer } from \"@graphrefly/pure-ts/core\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tStreamDelta,\n\tTokenUsage,\n} from \"../core/types.js\";\n\nexport interface DryRunAdapterOptions {\n\tprovider?: string;\n\tmodel?: string;\n\t/** Generate the fake response. Defaults to echoing the last user message. */\n\trespond?: (messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string;\n\t/**\n\t * Generate a fake usage object. Defaults to a simple character-count\n\t * heuristic (`input = sum(messages) / 4`, `output = content / 4`).\n\t */\n\tusage?: (messages: readonly ChatMessage[], content: string) => TokenUsage;\n\t/** Simulated latency in milliseconds (applied to both invoke and stream). */\n\tlatencyMs?: number;\n\t/** Stream chunk size in characters. Default 16. */\n\tstreamChunkSize?: number;\n}\n\nfunction makeAbortError(): Error {\n\tconst err = new Error(\"aborted\") as Error & { name: string };\n\terr.name = \"AbortError\";\n\treturn err;\n}\n\n/**\n * Abort-aware sleep using `ResettableTimer`. Spec §5.10 escape hatch.\n * No-op if `ms <= 0`; rejects with `AbortError` if the signal aborts.\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\tif (ms <= 0) return Promise.resolve();\n\tif (signal?.aborted) return Promise.reject(makeAbortError());\n\treturn new Promise((resolve, reject) => {\n\t\tconst timer = new ResettableTimer();\n\t\tlet onAbort: (() => void) | undefined;\n\t\tconst cleanup = (): void => {\n\t\t\ttimer.cancel();\n\t\t\tif (signal && onAbort) signal.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\ttimer.start(ms, () => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t});\n\t\tif (signal) {\n\t\t\tonAbort = (): void => {\n\t\t\t\tcleanup();\n\t\t\t\treject(makeAbortError());\n\t\t\t};\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t});\n}\n\n/**\n * Create a DryRun adapter.\n *\n * @example\n * ```ts\n * const adapter = dryRunAdapter({ respond: (msgs) => \"hello from dry-run\" });\n * const resp = await Promise.resolve(adapter.invoke([{ role: \"user\", content: \"hi\" }]));\n * ```\n */\nexport function dryRunAdapter(opts: DryRunAdapterOptions = {}): LLMAdapter {\n\tconst provider = opts.provider ?? \"dry-run\";\n\tconst model = opts.model ?? \"dry-run-v1\";\n\tconst latencyMs = opts.latencyMs ?? 0;\n\tconst streamChunkSize = Math.max(1, opts.streamChunkSize ?? 16);\n\n\tconst respondFn =\n\t\topts.respond ??\n\t\t((msgs: readonly ChatMessage[]): string => {\n\t\t\tconst lastUser = [...msgs].reverse().find((m) => m.role === \"user\");\n\t\t\treturn lastUser ? `echo: ${lastUser.content}` : \"dry-run: no user message\";\n\t\t});\n\n\tconst usageFn =\n\t\topts.usage ??\n\t\t((msgs: readonly ChatMessage[], content: string): TokenUsage => {\n\t\t\tconst totalInput = msgs.reduce((s, m) => s + m.content.length, 0);\n\t\t\treturn {\n\t\t\t\tinput: { regular: Math.ceil(totalInput / 4) },\n\t\t\t\toutput: { regular: Math.ceil(content.length / 4) },\n\t\t\t};\n\t\t});\n\n\treturn {\n\t\tprovider,\n\t\tmodel,\n\n\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\tawait sleep(latencyMs, invokeOpts?.signal);\n\t\t\tif (invokeOpts?.signal?.aborted) throw makeAbortError();\n\t\t\tconst content = respondFn(messages, invokeOpts);\n\t\t\tconst usage = usageFn(messages, content);\n\t\t\treturn {\n\t\t\t\tcontent,\n\t\t\t\tusage,\n\t\t\t\tfinishReason: \"stop\",\n\t\t\t\tmodel: invokeOpts?.model ?? model,\n\t\t\t\tprovider,\n\t\t\t\ttier: invokeOpts?.tier,\n\t\t\t\tmetadata: { dryRun: true },\n\t\t\t};\n\t\t},\n\n\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\tconst content = respondFn(messages, invokeOpts);\n\t\t\tconst usage = usageFn(messages, content);\n\t\t\tconst chunkCount = Math.ceil(content.length / streamChunkSize) || 1;\n\t\t\tconst perChunkMs = latencyMs > 0 ? latencyMs / chunkCount : 0;\n\t\t\tfor (let i = 0; i < content.length; i += streamChunkSize) {\n\t\t\t\tif (invokeOpts?.signal?.aborted) throw makeAbortError();\n\t\t\t\tawait sleep(perChunkMs, invokeOpts?.signal);\n\t\t\t\tyield { type: \"token\", delta: content.slice(i, i + streamChunkSize) };\n\t\t\t}\n\t\t\tyield { type: \"usage\", usage };\n\t\t\tyield { type: \"finish\", reason: \"stop\" };\n\t\t},\n\t};\n}\n","/**\n * `withReplayCache` — content-addressed response cache over `KvStorageTier`.\n *\n * - Key: sha256 of canonicalized (messages + invoke options minus `signal`).\n * - `\"read-write\"` (default): returns cached response if present; on miss,\n * passes through and stores the result.\n * - `\"write-only\"`: never reads; populates the cache for later runs.\n * - `\"read\"`: reads only; on miss, passes through without writing.\n * - `\"read-strict\"`: reads only; on miss, **throws `ReplayCacheMissError`**\n * instead of passing through. Use for fixture-driven tests or offline\n * fallback adapters where any cache miss is a test failure or a signal to\n * degrade.\n *\n * Reuses the library's existing `KvStorageTier` abstraction — any kv tier\n * (memoryKv / fileKv / sqliteKv / indexedDbKv / custom).\n *\n * **Concurrent cache-miss dedup:** uses `singleFromAny` so two concurrent\n * calls with the same key share one upstream request. Second caller sees the\n * same response that the first caller fetched; no duplicate provider spend.\n *\n * **Circular-ref safe:** `canonicalJson` uses a seen-set replacer so\n * user-supplied `ToolDefinition.parameters` with `$ref` cycles don't stack-\n * overflow the key computation.\n *\n * **Stream cadence capture:** when `cacheStreaming: true` AND\n * `captureStreamCadence: true`, per-chunk delays (ms since previous chunk)\n * are recorded alongside the content. Replay honors the recorded cadence\n * unless `replaySpeed` is set, which multiplies the effective per-chunk\n * delay (`replaySpeed: 2` → 2× faster; `replaySpeed: 0` → instant).\n * Without `captureStreamCadence`, replay is instant regardless.\n */\n\nimport { monotonicNs, ResettableTimer, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport type { KvStorageTier } from \"@graphrefly/pure-ts/extra\";\nimport { fromAny } from \"@graphrefly/pure-ts/extra\";\nimport { singleFromAny } from \"../../../../base/composition/single-from-any.js\";\nimport { firstValueFrom } from \"../../../../base/sources/settled.js\";\nimport { contentAddressedCache } from \"../_internal/content-addressed-cache.js\";\nimport { adapterWrapper, withLayer } from \"../_internal/wrappers.js\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tStreamDelta,\n} from \"../core/types.js\";\n\nexport type ReplayCacheMode = \"read\" | \"read-strict\" | \"write-only\" | \"read-write\";\n\nexport class ReplayCacheMissError extends Error {\n\toverride name = \"ReplayCacheMissError\";\n\tconstructor(\n\t\tpublic readonly key: string,\n\t\tpublic readonly method: \"invoke\" | \"stream\",\n\t) {\n\t\tsuper(`withReplayCache: no cached response for ${method} (key=${key}, mode=read-strict)`);\n\t}\n}\n\n/**\n * Context object passed to {@link WithReplayCacheOptions.keyFn}. Extending\n * with additional fields is forward-compatible — current implementations\n * ignoring unknown fields continue to work.\n */\nexport interface ReplayCacheKeyContext {\n\treadonly messages: readonly ChatMessage[];\n\treadonly opts: LLMInvokeOptions | undefined;\n\t/** Shortcut to `opts?.keyContext` — avoids an extra guard in callers. */\n\treadonly context: unknown;\n}\n\nexport interface WithReplayCacheOptions {\n\tstorage: KvStorageTier;\n\tmode?: ReplayCacheMode;\n\t/**\n\t * Custom key function. Receives a {@link ReplayCacheKeyContext} with the\n\t * chat messages, the invoke options, and the caller-supplied\n\t * `opts.keyContext`. Defaults to sha256 of canonical JSON over\n\t * `(messages, opts without signal/keyContext)`.\n\t *\n\t * Legacy 2-arg form `(messages, opts?)` also accepted — the adapter\n\t * detects arity and dispatches. Prefer the object form for new code.\n\t */\n\tkeyFn?:\n\t\t| ((ctx: ReplayCacheKeyContext) => string | Promise<string>)\n\t\t| ((messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string | Promise<string>);\n\t/** Prefix for cached keys (useful when sharing a tier across domains). */\n\tkeyPrefix?: string;\n\t/**\n\t * Whether to cache streaming responses (by consuming the full stream\n\t * and replaying it as one synthetic token chunk). Default `false`.\n\t */\n\tcacheStreaming?: boolean;\n\t/**\n\t * When `cacheStreaming: true`, also record per-chunk delays (ms since\n\t * previous chunk) so replay can honor the original streaming cadence.\n\t * Default `false` (chunks replay instantly).\n\t */\n\tcaptureStreamCadence?: boolean;\n\t/**\n\t * Stream replay speed multiplier. `1` = original cadence (requires\n\t * `captureStreamCadence`). `2` = 2× faster. `0` = instant. Default `1`.\n\t */\n\treplaySpeed?: number;\n}\n\ninterface CachedEntry {\n\tresponse: LLMResponse;\n\tstoredAtNs: number;\n\t/**\n\t * Per-chunk deltas in milliseconds — populated only when `captureStreamCadence`\n\t * is `true` during the write. Replayed via `ResettableTimer` in stream().\n\t */\n\tstreamCadenceMs?: readonly number[];\n\t/**\n\t * Per-chunk bodies in order (tokens only — `usage`/`finish` are reconstructed\n\t * from `response.usage` / `response.finishReason`). Populated only when\n\t * `captureStreamCadence` is `true`, used for cadence-faithful replay.\n\t */\n\tstreamChunks?: ReadonlyArray<{ delta: string }>;\n}\n\ntype ResolveArgs = {\n\tmessages: readonly ChatMessage[];\n\tinvokeOpts: LLMInvokeOptions | undefined;\n};\ntype ResolveArgsWithKey = ResolveArgs & { _precomputedKey: string };\n\n/** Wrap an adapter with a replay cache. */\nexport function withReplayCache(inner: LLMAdapter, opts: WithReplayCacheOptions): LLMAdapter {\n\tconst mode = opts.mode ?? \"read-write\";\n\tconst cacheStreaming = opts.cacheStreaming ?? false;\n\tconst captureStreamCadence = opts.captureStreamCadence ?? false;\n\tconst replaySpeed = opts.replaySpeed ?? 1;\n\tconst keyPrefix = opts.keyPrefix ?? \"llm-replay\";\n\tconst isReadOnly = mode === \"read\" || mode === \"read-strict\";\n\n\t// Content-addressed substrate — keys via canonicalJson + sha256 over\n\t// (messages, opts minus signal/keyContext) or the caller's custom keyFn.\n\t// Value type is `CachedEntry` so we can persist stream cadence alongside\n\t// the response. Uses the shared substrate in `src/extra/content-addressed-\n\t// storage.ts` via the LLM-specific wrapper in `_internal/`.\n\t//\n\t// Mode translation: `ReplayCacheMode` uses `\"write-only\"` (legacy name);\n\t// the substrate uses `\"write\"`. All other modes map 1:1.\n\tconst cache = contentAddressedCache<CachedEntry>({\n\t\tstorage: opts.storage,\n\t\tmode: mode === \"write-only\" ? \"write\" : mode,\n\t\tkeyFn: opts.keyFn,\n\t\tkeyPrefix,\n\t});\n\n\tconst sleepMs = (ms: number): Promise<void> =>\n\t\tms <= 0\n\t\t\t? Promise.resolve()\n\t\t\t: new Promise<void>((resolve) => {\n\t\t\t\t\tconst t = new ResettableTimer();\n\t\t\t\t\tt.start(ms, () => resolve());\n\t\t\t\t});\n\n\t// Singleflight — concurrent cache-miss requests with the same key share one\n\t// upstream call. `keyFn` must be synchronous (singleflight needs the key\n\t// before dispatching), so we compute the key eagerly in `invoke`/`stream`\n\t// and thread it through as `_precomputedKey`. The passed `keyFn` reads\n\t// that precomputed value instead of re-hashing.\n\tconst upstreamInFlight = singleFromAny<ResolveArgsWithKey, LLMResponse>(\n\t\tasync ({ messages, invokeOpts }) => {\n\t\t\treturn await firstValueFrom(fromAny(inner.invoke(messages, invokeOpts)));\n\t\t},\n\t\t{ keyFn: ({ _precomputedKey }) => _precomputedKey },\n\t);\n\n\tconst wrap = adapterWrapper(inner, {\n\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\tconst key = await cache.keyFor(messages, invokeOpts);\n\t\t\tconst entry = await cache.lookup(messages, invokeOpts);\n\t\t\tif (entry?.response) {\n\t\t\t\tconst cached = entry.response;\n\t\t\t\treturn { ...cached, metadata: { ...(cached.metadata ?? {}), replayCache: \"hit\" } };\n\t\t\t}\n\n\t\t\tif (mode === \"read-strict\") throw new ReplayCacheMissError(key, \"invoke\");\n\t\t\tconst resp = await upstreamInFlight({ messages, invokeOpts, _precomputedKey: key });\n\t\t\tif (!isReadOnly) {\n\t\t\t\tawait cache.store(messages, invokeOpts, { response: resp, storedAtNs: wallClockNs() });\n\t\t\t}\n\t\t\treturn resp;\n\t\t},\n\n\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\tif (!cacheStreaming) {\n\t\t\t\t// `read-strict` only applies to cache-checked paths. When\n\t\t\t\t// `cacheStreaming: false` the cache isn't consulted for\n\t\t\t\t// streams at all, so passthrough is correct — throwing would\n\t\t\t\t// make the adapter's stream() permanently unusable.\n\t\t\t\tfor await (const delta of inner.stream(messages, invokeOpts)) yield delta;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst key = await cache.keyFor(messages, invokeOpts);\n\t\t\tconst entry = await cache.lookup(messages, invokeOpts);\n\t\t\tif (entry) {\n\t\t\t\tconst cached = entry.response;\n\t\t\t\t// Cadence-faithful replay when both recorded chunks + delays are present.\n\t\t\t\tif (entry.streamChunks && entry.streamCadenceMs) {\n\t\t\t\t\tfor (let i = 0; i < entry.streamChunks.length; i++) {\n\t\t\t\t\t\tconst delay = entry.streamCadenceMs[i] ?? 0;\n\t\t\t\t\t\tconst effective = replaySpeed > 0 ? delay / replaySpeed : 0;\n\t\t\t\t\t\tif (effective > 0) await sleepMs(effective);\n\t\t\t\t\t\tyield { type: \"token\", delta: entry.streamChunks[i]?.delta ?? \"\" };\n\t\t\t\t\t}\n\t\t\t\t} else if (cached.content) {\n\t\t\t\t\tyield { type: \"token\", delta: cached.content };\n\t\t\t\t}\n\t\t\t\tif (cached.usage) yield { type: \"usage\", usage: cached.usage };\n\t\t\t\tyield { type: \"finish\", reason: cached.finishReason ?? \"stop\" };\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (mode === \"read-strict\") throw new ReplayCacheMissError(key, \"stream\");\n\t\t\t// Miss: accumulate, store, re-yield.\n\t\t\tlet content = \"\";\n\t\t\tlet usage: LLMResponse[\"usage\"] | undefined;\n\t\t\tlet finishReason: string | undefined;\n\t\t\tconst chunks: { delta: string }[] = [];\n\t\t\tconst delaysMs: number[] = [];\n\t\t\t// Time-to-first-token (TTFT — provider latency / network / queue\n\t\t\t// warmup) is NOT cadence; setting lastNs inside the loop on first\n\t\t\t// chunk means chunk-0's delay is 0 (boundary-clean) and subsequent\n\t\t\t// delays measure only inter-chunk gaps. Use the central clock so\n\t\t\t// the scale matches every other cadence measurement in the library\n\t\t\t// and the module stays browser-safe (spec §5.11).\n\t\t\tlet lastNs: number | undefined;\n\t\t\tfor await (const delta of inner.stream(messages, invokeOpts)) {\n\t\t\t\tif (delta.type === \"token\") {\n\t\t\t\t\tcontent += delta.delta;\n\t\t\t\t\tif (captureStreamCadence) {\n\t\t\t\t\t\tconst now = monotonicNs();\n\t\t\t\t\t\tconst gap = lastNs === undefined ? 0 : (now - lastNs) / 1e6;\n\t\t\t\t\t\tdelaysMs.push(gap);\n\t\t\t\t\t\tlastNs = now;\n\t\t\t\t\t\tchunks.push({ delta: delta.delta });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (delta.type === \"usage\") usage = delta.usage;\n\t\t\t\tif (delta.type === \"finish\") finishReason = delta.reason;\n\t\t\t\tyield delta;\n\t\t\t}\n\t\t\tif ((content || usage) && !isReadOnly) {\n\t\t\t\tconst resp: LLMResponse = {\n\t\t\t\t\tcontent,\n\t\t\t\t\tusage: usage ?? { input: { regular: 0 }, output: { regular: 0 } },\n\t\t\t\t\tfinishReason,\n\t\t\t\t\tmodel: inner.model ?? invokeOpts?.model ?? \"\",\n\t\t\t\t\tprovider: inner.provider,\n\t\t\t\t};\n\t\t\t\tconst entryToStore: CachedEntry = {\n\t\t\t\t\tresponse: resp,\n\t\t\t\t\tstoredAtNs: wallClockNs(),\n\t\t\t\t\t...(captureStreamCadence ? { streamChunks: chunks, streamCadenceMs: delaysMs } : {}),\n\t\t\t\t};\n\t\t\t\tawait cache.store(messages, invokeOpts, entryToStore);\n\t\t\t}\n\t\t},\n\t});\n\twithLayer(wrap, \"withReplayCache\", inner);\n\treturn wrap;\n}\n\n// canonicalJson is no longer re-exported here — consumers import directly from\n// @graphrefly/pure-ts/extra. The presentation-layer re-export caused a\n// duplicate-export conflict at the root barrel level (A3 build gate).\n","/**\n * LLM-specific content-addressed cache wrapper over the generic\n * {@link contentAddressedStorage} substrate in `src/extra/`. Handles the\n * ChatMessage + LLMInvokeOptions → stable key shape that both\n * `withReplayCache` and `fallbackAdapter` need, so the two middleware files\n * don't drift on key construction.\n *\n * The generic substrate does the sha256 + canonicalJson + tier IO; this\n * wrapper owns:\n * - Stripping non-serializable `signal` (AbortSignal) and `keyContext`\n * (caller-opaque context) from the default key.\n * - Arity-dispatched legacy `keyFn` support (1-arg ctx-object form vs 2-arg\n * `(messages, opts)` form).\n * - LLM-conventional `llm-replay` default key prefix (override via `keyPrefix`).\n *\n * @module\n */\n\nimport type { KvStorageTier } from \"@graphrefly/pure-ts/extra\";\nimport {\n\ttype ContentAddressedMode,\n\ttype ContentAddressedStorage,\n\tcontentAddressedStorage,\n} from \"@graphrefly/pure-ts/extra\";\nimport type { ChatMessage, LLMInvokeOptions } from \"../core/types.js\";\n\n/**\n * Context object passed to the 1-arg {@link ContentAddressedCacheOptions.keyFn}\n * form. Adding fields here is forward-compatible — existing 1-arg consumers\n * that ignore unknowns keep working.\n */\nexport interface LLMCacheKeyContext {\n\treadonly messages: readonly ChatMessage[];\n\treadonly opts: LLMInvokeOptions | undefined;\n\t/** Shortcut to `opts?.keyContext` — no extra guard in callers. */\n\treadonly context: unknown;\n}\n\nexport interface ContentAddressedCacheOptions<V> {\n\tstorage: KvStorageTier;\n\tmode?: ContentAddressedMode;\n\t/**\n\t * Custom key function. Receives either {@link LLMCacheKeyContext} (1-arg\n\t * form) or `(messages, opts?)` (legacy 2-arg form) — the wrapper dispatches\n\t * on `Function.length`. Return `string` or `Promise<string>`.\n\t */\n\tkeyFn?:\n\t\t| ((ctx: LLMCacheKeyContext) => string | Promise<string>)\n\t\t| ((messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string | Promise<string>);\n\t/** Prefix applied as `${keyPrefix}:${hash}`. Default `\"llm-replay\"`. */\n\tkeyPrefix?: string;\n\t/**\n\t * Optional value type hint — callers may store anything JSON-serializable.\n\t * `withReplayCache` stores `CachedEntry` (response + stream cadence);\n\t * `fallbackAdapter` stores the raw `LLMResponse`.\n\t */\n\t_valueType?: V;\n}\n\n/** Handle returned by {@link contentAddressedCache}. */\nexport interface ContentAddressedCache<V> {\n\tkeyFor(messages: readonly ChatMessage[], invokeOpts?: LLMInvokeOptions): Promise<string>;\n\tlookup(messages: readonly ChatMessage[], invokeOpts?: LLMInvokeOptions): Promise<V | undefined>;\n\tstore(\n\t\tmessages: readonly ChatMessage[],\n\t\tinvokeOpts: LLMInvokeOptions | undefined,\n\t\tvalue: V,\n\t): Promise<void>;\n\tforget(messages: readonly ChatMessage[], invokeOpts?: LLMInvokeOptions): Promise<void>;\n}\n\n/**\n * Builds an LLM-shaped content-addressed cache over `storage`. Default keying\n * hashes `(messages, opts minus signal/keyContext)` via\n * {@link contentAddressedStorage} — the generic extra-tier substrate.\n *\n * Used by `withReplayCache` (full response cache) and `fallbackAdapter`\n * (fixture lookup). Both consumers wrap this to add their divergent features\n * (singleflight / stream cadence / canned miss response) around the shared\n * substrate.\n *\n * @category internal\n */\nexport function contentAddressedCache<V>(\n\topts: ContentAddressedCacheOptions<V>,\n): ContentAddressedCache<V> {\n\tconst { storage, mode = \"read-write\", keyFn, keyPrefix = \"llm-replay\" } = opts;\n\n\t// Substrate mode: translate `\"read-strict\"` to `\"read\"` so the substrate\n\t// returns `undefined` on miss. The LLM-middleware callers (`withReplayCache`,\n\t// `fallbackAdapter`) own the strict-mode throw because they emit\n\t// domain-specific error types (`ReplayCacheMissError` / `FallbackMissError`)\n\t// that carry context the substrate shouldn't know about.\n\tconst substrateMode = mode === \"read-strict\" ? \"read\" : mode;\n\n\t// When `keyFn` is supplied, we wire it through the substrate's raw-key API\n\t// (via a synthesized keyContext passthrough). When omitted, we let the\n\t// substrate hash the default shape (messages + opts minus non-serializable\n\t// fields) with the standard prefix.\n\tconst substrate: ContentAddressedStorage<\n\t\t{ messages: readonly ChatMessage[]; opts: LLMInvokeOptions | undefined },\n\t\tV\n\t> = contentAddressedStorage({\n\t\tstorage,\n\t\tkeyPrefix,\n\t\tmode: substrateMode,\n\t\tkeyContext: ({ messages, opts: invokeOpts }) => {\n\t\t\t// Drop `signal` (not serializable) and `keyContext` (caller-opaque\n\t\t\t// context should only participate when user opts in via keyFn).\n\t\t\tconst { signal: _signal, keyContext: _keyContext, ...rest } = invokeOpts ?? {};\n\t\t\treturn { messages, opts: rest };\n\t\t},\n\t});\n\n\tasync function keyFor(\n\t\tmessages: readonly ChatMessage[],\n\t\tinvokeOpts?: LLMInvokeOptions,\n\t): Promise<string> {\n\t\tif (keyFn) {\n\t\t\t// Arity-dispatch: Function.length === 1 → ctx-object form;\n\t\t\t// otherwise → legacy 2-arg (messages, opts). Both may return\n\t\t\t// sync or async strings.\n\t\t\tif (keyFn.length <= 1) {\n\t\t\t\tconst ctx: LLMCacheKeyContext = {\n\t\t\t\t\tmessages,\n\t\t\t\t\topts: invokeOpts,\n\t\t\t\t\tcontext: invokeOpts?.keyContext,\n\t\t\t\t};\n\t\t\t\tconst raw = await (keyFn as (c: LLMCacheKeyContext) => string | Promise<string>)(ctx);\n\t\t\t\treturn `${keyPrefix}:${raw}`;\n\t\t\t}\n\t\t\tconst raw = await (\n\t\t\t\tkeyFn as (m: readonly ChatMessage[], o?: LLMInvokeOptions) => string | Promise<string>\n\t\t\t)(messages, invokeOpts);\n\t\t\treturn `${keyPrefix}:${raw}`;\n\t\t}\n\t\treturn substrate.keyFor({ messages, opts: invokeOpts });\n\t}\n\n\treturn {\n\t\tkeyFor,\n\n\t\tasync lookup(messages, invokeOpts) {\n\t\t\tif (mode === \"write\") return undefined;\n\t\t\tif (keyFn) {\n\t\t\t\t// Custom keyFn path: we can't use substrate.lookup (it hashes\n\t\t\t\t// from its own keyContext). Build the key ourselves and load.\n\t\t\t\t// Strict-mode throw is the CALLER'S responsibility — we return\n\t\t\t\t// undefined on miss so callers can wrap the throw with their\n\t\t\t\t// domain-specific error type.\n\t\t\t\tconst key = await keyFor(messages, invokeOpts);\n\t\t\t\tconst raw = await storage.load(key);\n\t\t\t\tif (raw === undefined) return undefined;\n\t\t\t\treturn raw as V;\n\t\t\t}\n\t\t\treturn substrate.lookup({ messages, opts: invokeOpts });\n\t\t},\n\n\t\tasync store(messages, invokeOpts, value) {\n\t\t\tif (mode === \"read\") return;\n\t\t\tif (keyFn) {\n\t\t\t\tconst key = await keyFor(messages, invokeOpts);\n\t\t\t\tawait storage.save(key, value as unknown);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait substrate.store({ messages, opts: invokeOpts }, value);\n\t\t},\n\n\t\tasync forget(messages, invokeOpts) {\n\t\t\tif (mode === \"read\" || mode === \"write\") return;\n\t\t\tif (!storage.delete) return;\n\t\t\tif (keyFn) {\n\t\t\t\tconst key = await keyFor(messages, invokeOpts);\n\t\t\t\tawait storage.delete(key);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait substrate.forget({ messages, opts: invokeOpts });\n\t\t},\n\t};\n}\n","/**\n * Shared shell + shape-dispatch helpers for the LLM adapter layer.\n *\n * Wave A Unit 11 decision: the 9 middleware files and the observable adapter\n * all repeated the same three boilerplate patterns:\n *\n * 1. Building the returned `LLMAdapter` shell (provider/model/capabilities\n * pass-through). → `adapterWrapper(inner, {invoke, stream})`.\n * 2. Dispatching the adapter's `NodeInput<LLMResponse>` result across its\n * three possible shapes (Promise / Node / plain value) + a `recordedOnce`\n * double-record guard on the reactive path. → `adaptInvokeResult`.\n * 3. Constructing a `CallStatsEvent` from provider / model / tier / usage /\n * latency. → `buildCallStats`.\n *\n * Two small additions:\n * - `withLayer(adapter, layerName)` stamps a `meta.middlewareLayer` tag on\n * the returned adapter via a non-enumerable property, enabling\n * `describeAdapterStack(adapter)` to walk the wrap chain bottom-up.\n * - `describeAdapterStack(adapter)` returns the list of layer names from\n * innermost to outermost so users can inspect the resilient stack the same\n * way they inspect graph topology.\n */\n\nimport { ERROR, monotonicNs, type Node, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport { fromAny, onFirstData } from \"@graphrefly/pure-ts/extra\";\nimport type { CallStatsEvent } from \"../core/observable.js\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tNodeInput,\n\tStreamDelta,\n\tTokenUsage,\n} from \"../core/types.js\";\n\n// ---------------------------------------------------------------------------\n// adapterWrapper — LLMAdapter shell with provider/model/capabilities pass-through\n// ---------------------------------------------------------------------------\n\n/** Callable shape for the invoke / stream bodies that `adapterWrapper` composes. */\nexport type AdapterInvokeFn = (\n\tmessages: readonly ChatMessage[],\n\topts?: LLMInvokeOptions,\n) => NodeInput<LLMResponse>;\n\nexport type AdapterStreamFn = (\n\tmessages: readonly ChatMessage[],\n\topts?: LLMInvokeOptions,\n) => AsyncIterable<StreamDelta>;\n\n/**\n * Builds an `LLMAdapter` shell around the given `invoke` / `stream`\n * implementations. Pass-through fields — `provider`, `model`, `capabilities`\n * — come from `inner` unless the caller overrides (`override.provider` etc.).\n *\n * Middleware files used to repeat `provider: inner.provider, model: inner.model,\n * capabilities: inner.capabilities?.bind(inner)` verbatim; this helper\n * captures that once.\n *\n * @category internal\n */\nexport function adapterWrapper(\n\tinner: LLMAdapter,\n\timpl: { invoke: AdapterInvokeFn; stream: AdapterStreamFn },\n\toverride?: Partial<Pick<LLMAdapter, \"provider\" | \"model\" | \"capabilities\">>,\n): LLMAdapter {\n\treturn {\n\t\tprovider: override?.provider ?? inner.provider,\n\t\tmodel: override?.model ?? inner.model,\n\t\tcapabilities: override?.capabilities ?? inner.capabilities?.bind(inner),\n\t\tinvoke: impl.invoke,\n\t\tstream: impl.stream,\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// adaptInvokeResult — shape-dispatch for NodeInput<LLMResponse>\n// ---------------------------------------------------------------------------\n\n/**\n * Shape-dispatch helper for `inner.invoke(...)` return values. Converts any of\n * the three `NodeInput<LLMResponse>` shapes (Promise, plain value, Node /\n * iterable) into the caller-requested output, applying `onResp` exactly once\n * on the first DATA emission.\n *\n * **Paths.**\n * - `Promise<LLMResponse>` → `Promise<R>` (chains `onResp` via `.then`; if\n * `onError` is provided, wires `.catch` so rejected Promises flow to the\n * error path just like the stream body does).\n * - Plain `LLMResponse` (object with `content` field) → `R` (calls `onResp`\n * synchronously and returns the mapped value).\n * - Anything else → reactive `Node<R>` via `fromAny` + `onFirstData(onResp)`\n * so late subscribers don't re-fire the side effect.\n *\n * @category internal\n */\nexport function adaptInvokeResult<R>(\n\tinput: NodeInput<LLMResponse>,\n\topts: {\n\t\tonResp: (resp: LLMResponse) => R;\n\t\t/**\n\t\t * If provided, rejected Promises are piped through this handler (for\n\t\t * stats / budget recording) before re-throwing. Signature mirrors the\n\t\t * stream try/catch shape so callers can share a single error-recording\n\t\t * closure across both paths.\n\t\t */\n\t\tonError?: (err: unknown) => void;\n\t\t/** Optional node name for the reactive-path derived (describe-friendly). */\n\t\tname?: string;\n\t},\n): Promise<R> | R | Node<R> {\n\tconst { onResp, onError, name } = opts;\n\n\t// Promise / thenable: chain .then, optionally .catch.\n\tif (input != null && typeof (input as PromiseLike<LLMResponse>).then === \"function\") {\n\t\tconst p = input as Promise<LLMResponse>;\n\t\tif (onError) {\n\t\t\treturn p.then(onResp).catch((err: unknown) => {\n\t\t\t\tonError(err);\n\t\t\t\tthrow err;\n\t\t\t});\n\t\t}\n\t\treturn p.then(onResp);\n\t}\n\t// Plain LLMResponse (synchronous).\n\tif (input != null && typeof input === \"object\" && \"content\" in (input as object)) {\n\t\treturn onResp(input as LLMResponse);\n\t}\n\t// Reactive / iterable — map inside a `derived` guarded by `onFirstData`\n\t// so the side-effect fires exactly once per node lifetime (push-on-\n\t// subscribe replay on late subscribers is silent).\n\tconst bridged = fromAny(input);\n\t// Wire `onError` via a side-subscription on the bridged node. `onFirstData`\n\t// only handles DATA — ERROR messages need their own hook so stats /\n\t// budget recording fires symmetrically with the Promise path. The\n\t// subscription runs for the lifetime of the derived below (keepalive via\n\t// the downstream derived's activation), and fires at most once per ERROR\n\t// frame. Without this, ERRORs on Node-shaped adapter returns silently\n\t// bypass budget/observable recording.\n\tif (onError) {\n\t\tlet errored = false;\n\t\tbridged.subscribe((msgs) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (errored) return;\n\t\t\t\tif ((m as readonly [symbol, unknown])[0] === ERROR) {\n\t\t\t\t\terrored = true;\n\t\t\t\t\tonError((m as readonly [symbol, unknown])[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\t// `captured` holds the mapped value from the first DATA so re-subscribers\n\t// and re-emissions see a stable mapped value without re-firing `onResp`.\n\t// Use a distinct `mapped` sentinel (not nullish) because `onResp` may\n\t// legitimately return `null` / `undefined` / `0` — the prior\n\t// `captured ?? onResp(v)` guard re-fired `onResp` on falsy captured, which\n\t// broke the \"exactly once per node lifetime\" contract.\n\tlet captured: R;\n\tlet mapped = false;\n\tconst tapped = onFirstData(bridged, (v) => {\n\t\tcaptured = onResp(v);\n\t\tmapped = true;\n\t});\n\treturn node<R>(\n\t\t[tapped],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst v = data[0];\n\t\t\tif (v == null) {\n\t\t\t\tactions.emit(null as R);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (mapped) {\n\t\t\t\tactions.emit(captured);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tactions.emit(onResp(v as LLMResponse));\n\t\t},\n\t\t{ describeKind: \"derived\", name: name ?? \"adapt/invokeTap\" },\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// buildCallStats — CallStatsEvent constructor\n// ---------------------------------------------------------------------------\n\nexport interface BuildCallStatsArgs {\n\tprovider: string;\n\tmodel: string;\n\ttier?: string;\n\tusage: TokenUsage;\n\tstartNs: number;\n\tmethod: \"invoke\" | \"stream\";\n\terror?: { readonly type: string; readonly message: string };\n\t/** Override the wall-clock stamp (useful for tests / replay). */\n\tstartWallClockNs?: number;\n\t/** Override the monotonic end stamp (useful for tests). */\n\tendNs?: number;\n}\n\n/**\n * Constructs a {@link CallStatsEvent} from the arguments observable.ts and\n * budget-gate.ts used to assemble inline. The `timestamp` / `latencyMs` are\n * computed from `startNs` + (optional) `endNs`; `wallClock` is snapshot at\n * call-start via `wallClockNs()` unless overridden.\n *\n * @category internal\n */\nexport function buildCallStats(args: BuildCallStatsArgs): CallStatsEvent {\n\tconst end = args.endNs ?? monotonicNs();\n\treturn {\n\t\ttimestamp: end,\n\t\twallClock: args.startWallClockNs ?? wallClockNs(),\n\t\tprovider: args.provider,\n\t\tmodel: args.model,\n\t\ttier: args.tier,\n\t\tusage: args.usage,\n\t\tlatencyMs: Math.max(0, (end - args.startNs) / 1e6),\n\t\tmethod: args.method,\n\t\t...(args.error ? { error: args.error } : {}),\n\t};\n}\n\n/** Convenience — empty disaggregated usage stub used by every middleware. */\nexport function emptyUsageStub(): TokenUsage {\n\treturn { input: { regular: 0 }, output: { regular: 0 } };\n}\n\n// ---------------------------------------------------------------------------\n// meta.middlewareLayer + describeAdapterStack (Unit 11 Q1 user directive)\n// ---------------------------------------------------------------------------\n\n/** Symbol key carrying the wrap chain on the returned adapter. Non-enumerable. */\nconst MIDDLEWARE_LAYERS = Symbol.for(\"graphrefly.adapter.middlewareLayers\");\n\n/**\n * Stamp `adapter` with a middleware-layer name and return it. The stamp is a\n * non-enumerable property keyed by `Symbol.for(\"graphrefly.adapter.middlewareLayers\")`\n * — opaque to users, visible via {@link describeAdapterStack}.\n *\n * Each wrap prepends its layer to `inner`'s chain so the stack can be walked\n * bottom-up (innermost first). Providers have no layer stamp — they show up\n * as the bottom of the chain via their `provider` / `model` identity.\n *\n * @category internal\n */\nexport function withLayer<A extends LLMAdapter>(\n\tadapter: A,\n\tlayerName: string,\n\tinner?: LLMAdapter,\n): A {\n\tconst innerLayers = inner ? readLayers(inner) : [];\n\tconst chain = [...innerLayers, layerName];\n\tObject.defineProperty(adapter, MIDDLEWARE_LAYERS, {\n\t\tvalue: Object.freeze(chain),\n\t\tenumerable: false,\n\t\twritable: false,\n\t\tconfigurable: false,\n\t});\n\treturn adapter;\n}\n\n/**\n * Returns the middleware-layer names stamped on `adapter`, innermost first.\n * An adapter that has never been wrapped returns `[]` — callers combine the\n * result with `adapter.provider` / `adapter.model` for a full stack render.\n *\n * @example\n * ```ts\n * const stack = describeAdapterStack(resilientAdapter(anthropicAdapter(), opts).adapter);\n * // → [\"withLLMTimeout\", \"withRetry\", \"withLLMBreaker\", \"withBudgetGate\", \"withRateLimiter\", \"cascade\"]\n * ```\n *\n * @category extra\n */\nexport function describeAdapterStack(adapter: LLMAdapter): readonly string[] {\n\treturn readLayers(adapter);\n}\n\nfunction readLayers(adapter: LLMAdapter): readonly string[] {\n\tconst v = (adapter as unknown as Record<symbol, unknown>)[MIDDLEWARE_LAYERS];\n\treturn Array.isArray(v) ? (v as readonly string[]) : [];\n}\n","/**\n * `fallbackAdapter` — fixture-backed {@link LLMAdapter} for offline demos,\n * deterministic tests, and graceful degradation in production.\n *\n * A peer of `anthropicAdapter` / `openAICompatAdapter` / `googleAdapter` /\n * `ollamaAdapter` / `dryRunAdapter`, but whose role is to serve pre-recorded\n * or canned responses when real providers aren't reachable. Install it as a\n * tier via the existing routing primitives (no new composer needed):\n *\n * ```ts\n * // Graceful offline fallback for a user app:\n * resilientAdapter(anthropicAdapter({ ... }), {\n * fallback: fallbackAdapter({ fixturesDir: \"./fixtures\" }),\n * });\n *\n * // Or the general N-tier shape:\n * cascadingLlmAdapter([\n * { name: \"primary\", adapter: anthropicAdapter({ ... }) },\n * { name: \"fallback\", adapter: fallbackAdapter({ fixturesDir: \"./fixtures\" }) },\n * ]);\n * ```\n *\n * The `provider` field is `\"fallback\"` so its role is self-documenting in\n * logs, stats, cost tables, and audit trails.\n *\n * ## Three fixture sources (mutually exclusive)\n *\n * Pick exactly one of:\n *\n * 1. **`fixtures: FallbackFixture[]`** — inline, hand-authored. Supports\n * both hash-keyed and messages-keyed shapes; the adapter computes the\n * canonical hash for messages-keyed entries at init time. Ideal when you\n * want full control in code (tests, small demos).\n *\n * 2. **`fixturesStorage: KvStorageTier`** — the escape hatch for any backend.\n * Pass a `memoryKv()`, `indexedDbKv(...)`, `sqliteKv(...)`,\n * `cascadingCache(...)`, or a custom tier. You own the layout — no\n * auto-namespacing.\n *\n * **Filesystem directories (Node only):** the core `fallbackAdapter`\n * does NOT import `node:fs` / `node:path` — it's safe to bundle for\n * browsers. For a directory convenience, import `fallbackAdapter` from\n * `@graphrefly/graphrefly/utils/ai/node` (node subpath);\n * that variant adds `fixturesDir: string` (auto-namespaced to\n * `join(dir, keyPrefix)`, cache-format validated at init).\n *\n * ## Record mode\n *\n * `record: { adapter: real, storage }` proxies every call to `real` AND\n * persists the response through the provided tier. Use the node subpath's\n * `fallbackAdapter` for `record.dir` (auto-namespaced + `record.dir` defaults\n * to `fixturesDir` when both are file-backed).\n *\n * ## Three use cases, one implementation\n *\n * | Use case | Config |\n * |---|---|\n * | **User apps** — degrade when the cloud provider errors or network is down | `fallbackAdapter({ fixturesStorage: ... })` installed as a fallback tier |\n * | **Tests** — deterministic replays, fail loudly on miss | `fallbackAdapter({ fixturesStorage: ..., onMiss: \"throw\" })` |\n * | **Eval offline replay** — zero-spend repeat runs | `fallbackAdapter({ fixturesStorage: ... })` as the only adapter |\n *\n * ## Implementation\n *\n * Thin sugar over {@link withReplayCache}. Key shape comes from its\n * `canonicalJson` — fixtures written by either tool are interchangeable.\n *\n * @module\n */\n\nimport { sha256Hex, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport { canonicalJson, type KvStorageTier, memoryKv } from \"@graphrefly/pure-ts/extra\";\nimport type {\n\tChatMessage,\n\tLLMAdapter,\n\tLLMInvokeOptions,\n\tLLMResponse,\n\tStreamDelta,\n} from \"../core/types.js\";\nimport {\n\ttype ReplayCacheKeyContext,\n\tReplayCacheMissError,\n\twithReplayCache,\n} from \"../middleware/replay-cache.js\";\nimport { dryRunAdapter } from \"./dry-run.js\";\n\nexport type FallbackMissPolicy = \"throw\" | \"respond\";\n\n/**\n * Thrown when `fallbackAdapter({ onMiss: \"throw\" })` receives a request that\n * has no matching fixture. Alias of `ReplayCacheMissError` for now — the\n * adapter is a thin sugar over `withReplayCache` and shares its miss-error.\n */\nexport const FallbackMissError = ReplayCacheMissError;\nexport type FallbackMissError = ReplayCacheMissError;\n\n/**\n * One recorded fixture. Two authoring shapes:\n * - **Hash-keyed** — `{ key, response, stream? }`. Key is `sha256(canonicalJson({messages, opts}))`\n * with `fallback:` prefix. This is what `record` mode writes.\n * - **Messages-keyed** — `{ messages, invokeOpts?, response }`. The adapter\n * computes the key at init time. Ergonomic for hand-authored fixtures.\n */\nexport type FallbackFixture =\n\t| {\n\t\t\treadonly key: string;\n\t\t\treadonly response: LLMResponse;\n\t\t\treadonly stream?: {\n\t\t\t\treadonly chunks: readonly StreamDelta[];\n\t\t\t\treadonly delaysMs?: readonly number[];\n\t\t\t};\n\t }\n\t| {\n\t\t\treadonly messages: readonly ChatMessage[];\n\t\t\treadonly invokeOpts?: Omit<LLMInvokeOptions, \"signal\">;\n\t\t\treadonly response: LLMResponse;\n\t };\n\nexport interface FallbackAdapterOptions {\n\t/** Adapter provider label. Default `\"fallback\"`. */\n\treadonly provider?: string;\n\t/** Adapter model label. Default `\"fallback\"`. */\n\treadonly model?: string;\n\t/**\n\t * Inline hand-authored fixtures. Supports both hash-keyed (`{key, response, stream?}`)\n\t * and messages-keyed (`{messages, invokeOpts?, response}`) shapes — the adapter\n\t * computes the canonical hash for messages-keyed entries at init time. Held in\n\t * an internal `memoryKv`. Mutually exclusive with `fixturesDir` and\n\t * `fixturesStorage`.\n\t */\n\treadonly fixtures?: readonly FallbackFixture[];\n\t/**\n\t * Bring-your-own `KvStorageTier` (`memoryKv`, `sqliteKv`,\n\t * `indexedDbKv`, `cascadingCache`, or a custom tier). You own the\n\t * layout — no auto-namespacing. Mutually exclusive with `fixtures`.\n\t *\n\t * For filesystem directories, use the node subpath's `fallbackAdapter`\n\t * with its `fixturesDir` option (auto-namespaced + validated).\n\t */\n\treadonly fixturesStorage?: KvStorageTier;\n\t/**\n\t * Called on fixture miss when `onMiss === \"respond\"`. If not provided and\n\t * `onMiss === \"respond\"`, a canned \"service unavailable\" response is\n\t * returned (marked with `metadata.degraded: true`).\n\t */\n\treadonly respond?: (\n\t\tmessages: readonly ChatMessage[],\n\t\topts?: LLMInvokeOptions,\n\t) => string | LLMResponse;\n\t/** Miss policy. Default `\"respond\"`. */\n\treadonly onMiss?: FallbackMissPolicy;\n\t/**\n\t * Record mode. Proxies every call to `record.adapter` AND persists the\n\t * result through `record.storage`. For filesystem `record.dir` convenience,\n\t * use the node subpath's `fallbackAdapter`.\n\t */\n\treadonly record?: {\n\t\treadonly adapter: LLMAdapter;\n\t\treadonly storage?: KvStorageTier;\n\t};\n\t/** Stream replay speed multiplier. See {@link withReplayCache}. Default `1`. */\n\treadonly replaySpeed?: number;\n\t/** Key prefix. Kept compatible with `withReplayCache` defaults. Default `\"fallback\"`. */\n\treadonly keyPrefix?: string;\n\t/**\n\t * Custom key function — forwarded directly to the underlying\n\t * {@link withReplayCache}. Use to shard fixtures by `invokeOpts.keyContext`\n\t * (tenant, session, feature flag). Accepts either the new\n\t * {@link ReplayCacheKeyContext} object form or the legacy 2-arg\n\t * `(messages, opts?)` form.\n\t */\n\treadonly keyFn?:\n\t\t| ((ctx: ReplayCacheKeyContext) => string)\n\t\t| ((messages: readonly ChatMessage[], opts?: LLMInvokeOptions) => string);\n}\n\n// ---------------------------------------------------------------------------\n// Canned degraded response\n// ---------------------------------------------------------------------------\n\nfunction degradedResponse(provider: string, model: string): LLMResponse {\n\treturn {\n\t\tcontent: \"[fallback: no cached response available for this request]\",\n\t\tusage: { input: { regular: 0 }, output: { regular: 0 } },\n\t\tfinishReason: \"stop\",\n\t\tmodel,\n\t\tprovider,\n\t\tmetadata: { degraded: true, reason: \"no-fixture\" },\n\t};\n}\n\nfunction normalizeRespondResult(\n\traw: string | LLMResponse,\n\tprovider: string,\n\tmodel: string,\n): LLMResponse {\n\tif (typeof raw === \"string\") {\n\t\treturn {\n\t\t\tcontent: raw,\n\t\t\tusage: { input: { regular: 0 }, output: { regular: 0 } },\n\t\t\tfinishReason: \"stop\",\n\t\t\tmodel,\n\t\t\tprovider,\n\t\t\tmetadata: { degraded: true, reason: \"respond\" },\n\t\t};\n\t}\n\treturn raw;\n}\n\n// ---------------------------------------------------------------------------\n// Fixture → storage tier conversion\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the key a `FallbackFixture` would hash to. Messages-keyed fixtures\n * get their key derived on the spot; hash-keyed fixtures pass through. Async\n * because `sha256Hex` uses `globalThis.crypto.subtle` (universal, no\n * `node:crypto` leak) — see {@link sha256Hex}.\n */\nasync function fixtureKey(fixture: FallbackFixture, keyPrefix: string): Promise<string> {\n\tif (\"key\" in fixture) return fixture.key;\n\tconst canonical = canonicalJson({ messages: fixture.messages, opts: fixture.invokeOpts ?? {} });\n\tconst hex = await sha256Hex(canonical);\n\treturn `${keyPrefix}:${hex}`;\n}\n\n/**\n * `withReplayCache` stores values as `CachedEntry = { response, storedAtNs, streamChunks?, streamCadenceMs? }`.\n * Convert a `FallbackFixture` into that shape so inline fixtures can be\n * seeded into a memory tier alongside file-authored ones.\n */\nfunction toCachedEntry(fixture: FallbackFixture): unknown {\n\tconst base: { response: LLMResponse; storedAtNs: number } = {\n\t\tresponse: fixture.response,\n\t\t// Real timestamp so future TTL-aware tiers don't treat inline fixtures\n\t\t// as Epoch-old and evict them on first pass. Matches the wall-clock\n\t\t// write `withReplayCache` uses for disk-written entries.\n\t\tstoredAtNs: wallClockNs(),\n\t};\n\tif (\"key\" in fixture && fixture.stream) {\n\t\tconst tokenChunks = fixture.stream.chunks.filter(\n\t\t\t(c): c is Extract<StreamDelta, { type: \"token\" }> => c.type === \"token\",\n\t\t);\n\t\treturn {\n\t\t\t...base,\n\t\t\tstreamChunks: tokenChunks.map((c) => ({ delta: c.delta })),\n\t\t\tstreamCadenceMs: fixture.stream.delaysMs ?? tokenChunks.map(() => 0),\n\t\t};\n\t}\n\treturn base;\n}\n\n/**\n * Resolve the fixture source to a `KvStorageTier`. Enforces mutual exclusion\n * between `fixtures` and `fixturesStorage`. When `fixtures` is provided, the\n * seeded memory tier is returned synchronously but keys are populated\n * asynchronously via the returned seeding promise (await before first use).\n */\nfunction resolveFixtureStorage(\n\topts: FallbackAdapterOptions,\n\tkeyPrefix: string,\n): { tier: KvStorageTier | undefined; seedReady: Promise<void> } {\n\tconst sources: string[] = [];\n\tif (opts.fixtures != null) sources.push(\"fixtures\");\n\tif (opts.fixturesStorage != null) sources.push(\"fixturesStorage\");\n\tif (sources.length > 1) {\n\t\tthrow new TypeError(\n\t\t\t`fallbackAdapter: \\`fixtures\\` and \\`fixturesStorage\\` are mutually ` +\n\t\t\t\t`exclusive; got both ${sources.join(\" and \")}. Pick one source. ` +\n\t\t\t\t`For filesystem directories use the node subpath's \\`fallbackAdapter\\`.`,\n\t\t);\n\t}\n\tif (opts.fixtures) {\n\t\tconst tier = memoryKv();\n\t\tconst fixtures = opts.fixtures;\n\t\tconst seedReady = (async () => {\n\t\t\tfor (const fixture of fixtures) {\n\t\t\t\tconst key = await fixtureKey(fixture, keyPrefix);\n\t\t\t\tawait tier.save(key, toCachedEntry(fixture));\n\t\t\t}\n\t\t})();\n\t\t// Attach a no-op catch so a failure inside the IIFE (e.g. `sha256Hex`\n\t\t// throws because `globalThis.crypto.subtle` is unavailable) does NOT\n\t\t// become an unhandled rejection when the adapter is constructed but\n\t\t// never invoked. V8 records the handler attachment via this branch\n\t\t// of the promise graph, so the Node `unhandledRejection` hook stays\n\t\t// silent — yet subsequent `await seedReady` inside `invoke`/`stream`\n\t\t// still throws the original error for the caller to see.\n\t\tseedReady.catch(() => {});\n\t\treturn { tier, seedReady };\n\t}\n\tif (opts.fixturesStorage) return { tier: opts.fixturesStorage, seedReady: Promise.resolve() };\n\treturn { tier: undefined, seedReady: Promise.resolve() };\n}\n\n// ---------------------------------------------------------------------------\n// fallbackAdapter\n// ---------------------------------------------------------------------------\n\n/**\n * Build a fixture-backed {@link LLMAdapter}. See module docs for use cases\n * (offline demo, tests, degraded-mode) and recipe snippets.\n */\nexport function fallbackAdapter(opts: FallbackAdapterOptions = {}): LLMAdapter {\n\tconst provider = opts.provider ?? \"fallback\";\n\tconst model = opts.model ?? \"fallback\";\n\tconst onMiss: FallbackMissPolicy = opts.onMiss ?? \"respond\";\n\tconst keyPrefix = opts.keyPrefix ?? \"fallback\";\n\n\t// Pick the inner leaf adapter based on mode.\n\tconst leaf: LLMAdapter = (() => {\n\t\tif (opts.record) return opts.record.adapter;\n\t\tif (onMiss === \"throw\") {\n\t\t\t// `withReplayCache({ mode: \"read-strict\" })` throws on miss before\n\t\t\t// ever calling the inner. Supply a no-op to satisfy the type.\n\t\t\treturn dryRunAdapter({\n\t\t\t\tprovider,\n\t\t\t\tmodel,\n\t\t\t\trespond: () => \"[unreachable: read-strict mode throws on miss]\",\n\t\t\t});\n\t\t}\n\t\t// Custom leaf (not `dryRunAdapter`) so `metadata.degraded` is preserved\n\t\t// — `dryRunAdapter` returns a string and constructs its own response,\n\t\t// discarding our metadata. For the respond-on-miss path we want full\n\t\t// `LLMResponse` control.\n\t\treturn {\n\t\t\tprovider,\n\t\t\tmodel,\n\t\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\t\tconst raw = opts.respond\n\t\t\t\t\t? opts.respond(messages, invokeOpts)\n\t\t\t\t\t: degradedResponse(provider, model);\n\t\t\t\treturn normalizeRespondResult(raw, provider, model);\n\t\t\t},\n\t\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\t\tconst raw = opts.respond\n\t\t\t\t\t? opts.respond(messages, invokeOpts)\n\t\t\t\t\t: degradedResponse(provider, model);\n\t\t\t\tconst r = normalizeRespondResult(raw, provider, model);\n\t\t\t\tyield { type: \"token\", delta: r.content };\n\t\t\t\tif (r.usage) yield { type: \"usage\", usage: r.usage };\n\t\t\t\tyield { type: \"finish\", reason: r.finishReason ?? \"stop\" };\n\t\t\t},\n\t\t} satisfies LLMAdapter;\n\t})();\n\n\t// Resolve the storage tier.\n\t// - `record` mode: require `record.storage`.\n\t// - Replay-only: `fixtures` seeds an in-memory tier (async) OR\n\t// `fixturesStorage` passes through.\n\tlet storage: KvStorageTier;\n\tlet seedReady: Promise<void> = Promise.resolve();\n\tif (opts.record) {\n\t\tif (!opts.record.storage) {\n\t\t\tthrow new TypeError(\n\t\t\t\t\"fallbackAdapter: `record.storage` is required in record mode. For filesystem \" +\n\t\t\t\t\t\"`record.dir` convenience, use the node subpath's `fallbackAdapter`.\",\n\t\t\t);\n\t\t}\n\t\tstorage = opts.record.storage;\n\t} else {\n\t\tconst resolved = resolveFixtureStorage(opts, keyPrefix);\n\t\tstorage = resolved.tier ?? memoryKv();\n\t\tseedReady = resolved.seedReady;\n\t}\n\n\tconst mode = opts.record ? \"read-write\" : onMiss === \"throw\" ? \"read-strict\" : \"read\";\n\n\tconst cached = withReplayCache(leaf, {\n\t\tstorage,\n\t\tmode,\n\t\tkeyPrefix,\n\t\tcacheStreaming: true,\n\t\tcaptureStreamCadence: true,\n\t\treplaySpeed: opts.replaySpeed,\n\t\t...(opts.keyFn ? { keyFn: opts.keyFn } : {}),\n\t});\n\n\t// Wrap invoke/stream so the first call awaits the seed-complete Promise.\n\t// Adapter construction stays synchronous (`fallbackAdapter(...)` returns\n\t// immediately); inline fixture hashing happens lazily on first use.\n\treturn {\n\t\tprovider,\n\t\tmodel,\n\t\tcapabilities: cached.capabilities?.bind(cached),\n\t\tasync invoke(messages, invokeOpts): Promise<LLMResponse> {\n\t\t\tawait seedReady;\n\t\t\t// `cached` came from `withReplayCache`, whose `invoke` always returns\n\t\t\t// `Promise<LLMResponse>`. The `LLMAdapter` interface types it as the\n\t\t\t// broader `NodeInput<LLMResponse>` union; narrow here to the actual\n\t\t\t// shape so the wrapper surface stays `Promise<LLMResponse>`.\n\t\t\treturn cached.invoke(messages, invokeOpts) as Promise<LLMResponse>;\n\t\t},\n\t\tasync *stream(messages, invokeOpts): AsyncGenerator<StreamDelta> {\n\t\t\tawait seedReady;\n\t\t\tfor await (const delta of cached.stream(messages, invokeOpts)) yield delta;\n\t\t},\n\t};\n}\n"],"mappings":"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","names":["monotonicNs","ResettableTimer","wallClockNs","fromAny","raw","ResettableTimer","fromAny","wallClockNs","monotonicNs","wallClockNs","wallClockNs"]}