@warlock.js/ai 4.3.0 → 4.5.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 (541) hide show
  1. package/CHANGELOG.md +52 -48
  2. package/cjs/index.cjs +221 -14144
  3. package/cjs/src-C02yzsLs.cjs +22991 -0
  4. package/cjs/src-C02yzsLs.cjs.map +1 -0
  5. package/cjs/src-DFibP2FQ.cjs +30 -0
  6. package/esm/agent/agent-config.type.d.mts +90 -1
  7. package/esm/agent/agent-config.type.d.mts.map +1 -1
  8. package/esm/agent/agent-input-builder.mjs +27 -6
  9. package/esm/agent/agent-input-builder.mjs.map +1 -1
  10. package/esm/agent/agent.d.mts +40 -1
  11. package/esm/agent/agent.d.mts.map +1 -1
  12. package/esm/agent/agent.mjs +203 -30
  13. package/esm/agent/agent.mjs.map +1 -1
  14. package/esm/agent/index.d.mts +2 -1
  15. package/esm/agent/index.mjs +1 -0
  16. package/esm/agent/judge-config.type.d.mts +33 -0
  17. package/esm/agent/judge-config.type.d.mts.map +1 -0
  18. package/esm/agent/judge-config.type.mjs +13 -0
  19. package/esm/agent/judge-config.type.mjs.map +1 -0
  20. package/esm/ai-openai/src/embedder.mjs +4 -0
  21. package/esm/ai-openai/src/index.mjs +4 -0
  22. package/esm/ai-openai/src/model.mjs +5 -0
  23. package/esm/ai-openai/src/sdk.mjs +6 -0
  24. package/esm/ai-openai/src/utils/index.mjs +4 -0
  25. package/esm/ai-openai/src/utils/to-openai-tools.mjs +3 -0
  26. package/esm/ai-openai/src/utils/wrap-openai-error.mjs +4 -0
  27. package/esm/ai.d.mts +119 -53
  28. package/esm/ai.d.mts.map +1 -1
  29. package/esm/ai.mjs +43 -8
  30. package/esm/ai.mjs.map +1 -1
  31. package/esm/batch/batch.d.mts.map +1 -1
  32. package/esm/batch/batch.mjs +21 -1
  33. package/esm/batch/batch.mjs.map +1 -1
  34. package/esm/batch/batch.type.d.mts +11 -4
  35. package/esm/batch/batch.type.d.mts.map +1 -1
  36. package/esm/config.d.mts +39 -3
  37. package/esm/config.d.mts.map +1 -1
  38. package/esm/config.mjs +26 -2
  39. package/esm/config.mjs.map +1 -1
  40. package/esm/contracts/agent/agent-options.type.d.mts +11 -3
  41. package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
  42. package/esm/contracts/agent/eval.type.d.mts +43 -2
  43. package/esm/contracts/agent/eval.type.d.mts.map +1 -1
  44. package/esm/contracts/attachment-policy.type.d.mts +51 -0
  45. package/esm/contracts/attachment-policy.type.d.mts.map +1 -0
  46. package/esm/contracts/attachment.type.d.mts +16 -7
  47. package/esm/contracts/attachment.type.d.mts.map +1 -1
  48. package/esm/contracts/content-part.type.d.mts +14 -5
  49. package/esm/contracts/content-part.type.d.mts.map +1 -1
  50. package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -1
  51. package/esm/contracts/index.d.mts +6 -4
  52. package/esm/contracts/model.contract.d.mts +11 -8
  53. package/esm/contracts/model.contract.d.mts.map +1 -1
  54. package/esm/contracts/orchestrator/index.d.mts +1 -0
  55. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +31 -2
  56. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
  57. package/esm/contracts/orchestrator/session-lock.contract.d.mts +47 -0
  58. package/esm/contracts/orchestrator/session-lock.contract.d.mts.map +1 -0
  59. package/esm/contracts/planner/index.d.mts +1 -1
  60. package/esm/contracts/planner/planner-config.type.d.mts +31 -0
  61. package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
  62. package/esm/contracts/planner/planner-execute-options.type.d.mts +48 -1
  63. package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
  64. package/esm/contracts/planner/planner-result.type.d.mts +19 -5
  65. package/esm/contracts/planner/planner-result.type.d.mts.map +1 -1
  66. package/esm/contracts/planner/planner.contract.d.mts +1 -1
  67. package/esm/contracts/result/agent-result.type.d.mts +6 -4
  68. package/esm/contracts/result/agent-result.type.d.mts.map +1 -1
  69. package/esm/contracts/result/base-report.type.d.mts +21 -3
  70. package/esm/contracts/result/base-report.type.d.mts.map +1 -1
  71. package/esm/contracts/result/base-report.type.mjs.map +1 -1
  72. package/esm/contracts/result/execution-report.type.d.mts +53 -1
  73. package/esm/contracts/result/execution-report.type.d.mts.map +1 -1
  74. package/esm/contracts/result/orchestrator-result.type.d.mts +15 -11
  75. package/esm/contracts/result/orchestrator-result.type.d.mts.map +1 -1
  76. package/esm/contracts/result/supervisor-result.type.d.mts +13 -1
  77. package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -1
  78. package/esm/contracts/result/tool-call.type.d.mts +2 -2
  79. package/esm/contracts/result/tool-call.type.d.mts.map +1 -1
  80. package/esm/contracts/result/workflow-result.type.d.mts +1 -0
  81. package/esm/contracts/result/workflow-result.type.d.mts.map +1 -1
  82. package/esm/contracts/supervisor/supervisor-config.type.d.mts +29 -0
  83. package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
  84. package/esm/contracts/system-prompt.contract.d.mts +103 -1
  85. package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
  86. package/esm/contracts/team/index.d.mts +1 -0
  87. package/esm/contracts/team/team-config.type.d.mts +127 -0
  88. package/esm/contracts/team/team-config.type.d.mts.map +1 -0
  89. package/esm/contracts/tool.contract.d.mts +4 -2
  90. package/esm/contracts/tool.contract.d.mts.map +1 -1
  91. package/esm/contracts/workflow/step.contract.d.mts +30 -5
  92. package/esm/contracts/workflow/step.contract.d.mts.map +1 -1
  93. package/esm/contracts/workflow/workflow.contract.d.mts +16 -0
  94. package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
  95. package/esm/errors/error-code.type.d.mts +1 -1
  96. package/esm/errors/index.d.mts +1 -0
  97. package/esm/errors/index.mjs +1 -0
  98. package/esm/errors/outbound-policy-error.d.mts +27 -0
  99. package/esm/errors/outbound-policy-error.d.mts.map +1 -0
  100. package/esm/errors/outbound-policy-error.mjs +32 -0
  101. package/esm/errors/outbound-policy-error.mjs.map +1 -0
  102. package/esm/eval/dataset.d.mts +28 -0
  103. package/esm/eval/dataset.d.mts.map +1 -0
  104. package/esm/eval/dataset.mjs +112 -0
  105. package/esm/eval/dataset.mjs.map +1 -0
  106. package/esm/eval/dataset.type.d.mts +53 -0
  107. package/esm/eval/dataset.type.d.mts.map +1 -0
  108. package/esm/eval/eval-runner.d.mts.map +1 -1
  109. package/esm/eval/eval-runner.mjs +16 -2
  110. package/esm/eval/eval-runner.mjs.map +1 -1
  111. package/esm/eval/index.d.mts +20 -1
  112. package/esm/eval/index.d.mts.map +1 -1
  113. package/esm/eval/index.mjs +20 -2
  114. package/esm/eval/index.mjs.map +1 -1
  115. package/esm/eval/regression.d.mts +30 -0
  116. package/esm/eval/regression.d.mts.map +1 -0
  117. package/esm/eval/regression.mjs +51 -0
  118. package/esm/eval/regression.mjs.map +1 -0
  119. package/esm/eval/report-json.d.mts +30 -0
  120. package/esm/eval/report-json.d.mts.map +1 -0
  121. package/esm/eval/report-json.mjs +33 -0
  122. package/esm/eval/report-json.mjs.map +1 -0
  123. package/esm/eval/report-junit.d.mts +22 -0
  124. package/esm/eval/report-junit.d.mts.map +1 -0
  125. package/esm/eval/report-junit.mjs +60 -0
  126. package/esm/eval/report-junit.mjs.map +1 -0
  127. package/esm/guard/contracts/guard-options.type.d.mts +165 -0
  128. package/esm/guard/contracts/guard-options.type.d.mts.map +1 -0
  129. package/esm/guard/contracts/guardrail.contract.d.mts +78 -0
  130. package/esm/guard/contracts/guardrail.contract.d.mts.map +1 -0
  131. package/esm/guard/contracts/index.d.mts +4 -0
  132. package/esm/guard/contracts/openai-client.contract.d.mts +51 -0
  133. package/esm/guard/contracts/openai-client.contract.d.mts.map +1 -0
  134. package/esm/guard/contracts/verdict.type.d.mts +76 -0
  135. package/esm/guard/contracts/verdict.type.d.mts.map +1 -0
  136. package/esm/guard/detectors/index.d.mts +4 -0
  137. package/esm/guard/detectors/index.mjs +6 -0
  138. package/esm/guard/detectors/injection.d.mts +34 -0
  139. package/esm/guard/detectors/injection.d.mts.map +1 -0
  140. package/esm/guard/detectors/injection.mjs +254 -0
  141. package/esm/guard/detectors/injection.mjs.map +1 -0
  142. package/esm/guard/detectors/moderation.d.mts +32 -0
  143. package/esm/guard/detectors/moderation.d.mts.map +1 -0
  144. package/esm/guard/detectors/moderation.mjs +134 -0
  145. package/esm/guard/detectors/moderation.mjs.map +1 -0
  146. package/esm/guard/detectors/pii.d.mts +41 -0
  147. package/esm/guard/detectors/pii.d.mts.map +1 -0
  148. package/esm/guard/detectors/pii.mjs +199 -0
  149. package/esm/guard/detectors/pii.mjs.map +1 -0
  150. package/esm/guard/detectors/topic.d.mts +29 -0
  151. package/esm/guard/detectors/topic.d.mts.map +1 -0
  152. package/esm/guard/detectors/topic.mjs +99 -0
  153. package/esm/guard/detectors/topic.mjs.map +1 -0
  154. package/esm/guard/errors.d.mts +12 -0
  155. package/esm/guard/errors.d.mts.map +1 -0
  156. package/esm/guard/errors.mjs +18 -0
  157. package/esm/guard/errors.mjs.map +1 -0
  158. package/esm/guard/guard.d.mts +67 -0
  159. package/esm/guard/guard.d.mts.map +1 -0
  160. package/esm/guard/guard.mjs +209 -0
  161. package/esm/guard/guard.mjs.map +1 -0
  162. package/esm/guard/guardrail.d.mts +39 -0
  163. package/esm/guard/guardrail.d.mts.map +1 -0
  164. package/esm/guard/guardrail.mjs +22 -0
  165. package/esm/guard/guardrail.mjs.map +1 -0
  166. package/esm/human/contracts/approval.type.d.mts +154 -0
  167. package/esm/human/contracts/approval.type.d.mts.map +1 -0
  168. package/esm/human/contracts/human-approval.type.d.mts +38 -0
  169. package/esm/human/contracts/human-approval.type.d.mts.map +1 -0
  170. package/esm/human/contracts/index.d.mts +4 -0
  171. package/esm/human/contracts/interrupt-store.contract.d.mts +112 -0
  172. package/esm/human/contracts/interrupt-store.contract.d.mts.map +1 -0
  173. package/esm/human/contracts/resume.type.d.mts +77 -0
  174. package/esm/human/contracts/resume.type.d.mts.map +1 -0
  175. package/esm/human/errors.d.mts +84 -0
  176. package/esm/human/errors.d.mts.map +1 -0
  177. package/esm/human/errors.mjs +60 -0
  178. package/esm/human/errors.mjs.map +1 -0
  179. package/esm/human/human-approval.d.mts +57 -0
  180. package/esm/human/human-approval.d.mts.map +1 -0
  181. package/esm/human/human-approval.mjs +170 -0
  182. package/esm/human/human-approval.mjs.map +1 -0
  183. package/esm/human/policy.d.mts +55 -0
  184. package/esm/human/policy.d.mts.map +1 -0
  185. package/esm/human/policy.mjs +67 -0
  186. package/esm/human/policy.mjs.map +1 -0
  187. package/esm/human/register.mjs +37 -0
  188. package/esm/human/register.mjs.map +1 -0
  189. package/esm/human/resume-seed.mjs +53 -0
  190. package/esm/human/resume-seed.mjs.map +1 -0
  191. package/esm/human/resume.d.mts +54 -0
  192. package/esm/human/resume.d.mts.map +1 -0
  193. package/esm/human/resume.mjs +121 -0
  194. package/esm/human/resume.mjs.map +1 -0
  195. package/esm/human/stores/index.mjs +5 -0
  196. package/esm/human/stores/memory.d.mts +30 -0
  197. package/esm/human/stores/memory.d.mts.map +1 -0
  198. package/esm/human/stores/memory.mjs +91 -0
  199. package/esm/human/stores/memory.mjs.map +1 -0
  200. package/esm/human/stores/pg.d.mts +59 -0
  201. package/esm/human/stores/pg.d.mts.map +1 -0
  202. package/esm/human/stores/pg.mjs +220 -0
  203. package/esm/human/stores/pg.mjs.map +1 -0
  204. package/esm/human/stores/redis.d.mts +56 -0
  205. package/esm/human/stores/redis.d.mts.map +1 -0
  206. package/esm/human/stores/redis.mjs +201 -0
  207. package/esm/human/stores/redis.mjs.map +1 -0
  208. package/esm/index.d.mts +113 -22
  209. package/esm/index.mjs +82 -11
  210. package/esm/middleware/builtins/budget.mjs +6 -2
  211. package/esm/middleware/builtins/budget.mjs.map +1 -1
  212. package/esm/middleware/utils/extract-user-text.d.mts +8 -1
  213. package/esm/middleware/utils/extract-user-text.d.mts.map +1 -1
  214. package/esm/middleware/utils/extract-user-text.mjs +8 -1
  215. package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
  216. package/esm/object-stream/index.d.mts +2 -0
  217. package/esm/object-stream/index.mjs +4 -0
  218. package/esm/object-stream/parse-partial-json.d.mts +22 -0
  219. package/esm/object-stream/parse-partial-json.d.mts.map +1 -0
  220. package/esm/object-stream/parse-partial-json.mjs +78 -0
  221. package/esm/object-stream/parse-partial-json.mjs.map +1 -0
  222. package/esm/object-stream/stream-object.d.mts +68 -0
  223. package/esm/object-stream/stream-object.d.mts.map +1 -0
  224. package/esm/object-stream/stream-object.mjs +104 -0
  225. package/esm/object-stream/stream-object.mjs.map +1 -0
  226. package/esm/observe/index.mjs +4 -0
  227. package/esm/observe/observer-registry.d.mts +30 -0
  228. package/esm/observe/observer-registry.d.mts.map +1 -0
  229. package/esm/observe/observer-registry.mjs +51 -0
  230. package/esm/observe/observer-registry.mjs.map +1 -0
  231. package/esm/observe/observer.contract.d.mts +40 -0
  232. package/esm/observe/observer.contract.d.mts.map +1 -0
  233. package/esm/observe/resolve-observers.d.mts +40 -0
  234. package/esm/observe/resolve-observers.d.mts.map +1 -0
  235. package/esm/observe/resolve-observers.mjs +73 -0
  236. package/esm/observe/resolve-observers.mjs.map +1 -0
  237. package/esm/orchestrator/execution.d.mts.map +1 -1
  238. package/esm/orchestrator/execution.mjs +5 -2
  239. package/esm/orchestrator/execution.mjs.map +1 -1
  240. package/esm/orchestrator/index.d.mts +1 -0
  241. package/esm/orchestrator/index.mjs +1 -0
  242. package/esm/orchestrator/orchestrator.d.mts.map +1 -1
  243. package/esm/orchestrator/orchestrator.mjs +39 -6
  244. package/esm/orchestrator/orchestrator.mjs.map +1 -1
  245. package/esm/orchestrator/session-lock.d.mts +25 -0
  246. package/esm/orchestrator/session-lock.d.mts.map +1 -0
  247. package/esm/orchestrator/session-lock.mjs +83 -0
  248. package/esm/orchestrator/session-lock.mjs.map +1 -0
  249. package/esm/planner/dag-scheduler.mjs +97 -0
  250. package/esm/planner/dag-scheduler.mjs.map +1 -0
  251. package/esm/planner/plan-prompt.d.mts +1 -1
  252. package/esm/planner/plan-prompt.d.mts.map +1 -1
  253. package/esm/planner/plan-prompt.mjs +2 -1
  254. package/esm/planner/plan-prompt.mjs.map +1 -1
  255. package/esm/planner/plan-schema.mjs +17 -14
  256. package/esm/planner/plan-schema.mjs.map +1 -1
  257. package/esm/planner/planner-run.d.mts.map +1 -1
  258. package/esm/planner/planner-run.mjs +300 -29
  259. package/esm/planner/planner-run.mjs.map +1 -1
  260. package/esm/planner/planner.mjs +1 -1
  261. package/esm/planner/planner.mjs.map +1 -1
  262. package/esm/prompt/errors.d.mts +57 -0
  263. package/esm/prompt/errors.d.mts.map +1 -0
  264. package/esm/prompt/errors.mjs +73 -0
  265. package/esm/prompt/errors.mjs.map +1 -0
  266. package/esm/prompt/index.d.mts +3 -0
  267. package/esm/prompt/index.mjs +4 -0
  268. package/esm/prompt/prompt-langfuse-sync.mjs +104 -0
  269. package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -0
  270. package/esm/prompt/prompt-langfuse-sync.type.d.mts +32 -0
  271. package/esm/prompt/prompt-langfuse-sync.type.d.mts.map +1 -0
  272. package/esm/prompt/prompt-validate.mjs +170 -0
  273. package/esm/prompt/prompt-validate.mjs.map +1 -0
  274. package/esm/prompt/prompt.d.mts +54 -0
  275. package/esm/prompt/prompt.d.mts.map +1 -0
  276. package/esm/prompt/prompt.mjs +218 -0
  277. package/esm/prompt/prompt.mjs.map +1 -0
  278. package/esm/prompt/prompt.type.d.mts +174 -0
  279. package/esm/prompt/prompt.type.d.mts.map +1 -0
  280. package/esm/prompts/index.d.mts +3 -0
  281. package/esm/prompts/index.mjs +3 -0
  282. package/esm/prompts/prompts-manager.contract.d.mts +154 -0
  283. package/esm/prompts/prompts-manager.contract.d.mts.map +1 -0
  284. package/esm/prompts/prompts-manager.d.mts +38 -0
  285. package/esm/prompts/prompts-manager.d.mts.map +1 -0
  286. package/esm/prompts/prompts-manager.mjs +410 -0
  287. package/esm/prompts/prompts-manager.mjs.map +1 -0
  288. package/esm/prompts/prompts-manager.type.d.mts +172 -0
  289. package/esm/prompts/prompts-manager.type.d.mts.map +1 -0
  290. package/esm/prompts/prompts-validate.mjs +200 -0
  291. package/esm/prompts/prompts-validate.mjs.map +1 -0
  292. package/esm/rag/as-tool.mjs +48 -0
  293. package/esm/rag/as-tool.mjs.map +1 -0
  294. package/esm/rag/chunk/chunk.d.mts +24 -0
  295. package/esm/rag/chunk/chunk.d.mts.map +1 -0
  296. package/esm/rag/chunk/chunk.mjs +44 -0
  297. package/esm/rag/chunk/chunk.mjs.map +1 -0
  298. package/esm/rag/chunk/fixed.mjs +32 -0
  299. package/esm/rag/chunk/fixed.mjs.map +1 -0
  300. package/esm/rag/chunk/markdown.mjs +75 -0
  301. package/esm/rag/chunk/markdown.mjs.map +1 -0
  302. package/esm/rag/chunk/recursive.mjs +132 -0
  303. package/esm/rag/chunk/recursive.mjs.map +1 -0
  304. package/esm/rag/chunk/sentence.mjs +73 -0
  305. package/esm/rag/chunk/sentence.mjs.map +1 -0
  306. package/esm/rag/contracts/chunk-options.type.d.mts +35 -0
  307. package/esm/rag/contracts/chunk-options.type.d.mts.map +1 -0
  308. package/esm/rag/contracts/citation.type.d.mts +35 -0
  309. package/esm/rag/contracts/citation.type.d.mts.map +1 -0
  310. package/esm/rag/contracts/index.d.mts +4 -0
  311. package/esm/rag/contracts/rag-config.type.d.mts +68 -0
  312. package/esm/rag/contracts/rag-config.type.d.mts.map +1 -0
  313. package/esm/rag/contracts/rag-document.type.d.mts +21 -0
  314. package/esm/rag/contracts/rag-document.type.d.mts.map +1 -0
  315. package/esm/rag/hybrid/bm25.d.mts +23 -0
  316. package/esm/rag/hybrid/bm25.d.mts.map +1 -0
  317. package/esm/rag/hybrid/bm25.mjs +51 -0
  318. package/esm/rag/hybrid/bm25.mjs.map +1 -0
  319. package/esm/rag/hybrid/hybrid-rank.d.mts +33 -0
  320. package/esm/rag/hybrid/hybrid-rank.d.mts.map +1 -0
  321. package/esm/rag/hybrid/hybrid-rank.mjs +29 -0
  322. package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -0
  323. package/esm/rag/hybrid/rrf.d.mts +25 -0
  324. package/esm/rag/hybrid/rrf.d.mts.map +1 -0
  325. package/esm/rag/hybrid/rrf.mjs +30 -0
  326. package/esm/rag/hybrid/rrf.mjs.map +1 -0
  327. package/esm/rag/index.d.mts +15 -0
  328. package/esm/rag/index.mjs +11 -0
  329. package/esm/rag/rag.d.mts +38 -0
  330. package/esm/rag/rag.d.mts.map +1 -0
  331. package/esm/rag/rag.mjs +126 -0
  332. package/esm/rag/rag.mjs.map +1 -0
  333. package/esm/rag/rerank/keyword-reranker.d.mts +32 -0
  334. package/esm/rag/rerank/keyword-reranker.d.mts.map +1 -0
  335. package/esm/rag/rerank/keyword-reranker.mjs +58 -0
  336. package/esm/rag/rerank/keyword-reranker.mjs.map +1 -0
  337. package/esm/rag/rerank/llm-reranker.d.mts +36 -0
  338. package/esm/rag/rerank/llm-reranker.d.mts.map +1 -0
  339. package/esm/rag/rerank/llm-reranker.mjs +85 -0
  340. package/esm/rag/rerank/llm-reranker.mjs.map +1 -0
  341. package/esm/rag/rerank/reranker.contract.d.mts +28 -0
  342. package/esm/rag/rerank/reranker.contract.d.mts.map +1 -0
  343. package/esm/rag/retrieve.mjs +68 -0
  344. package/esm/rag/retrieve.mjs.map +1 -0
  345. package/esm/rag/store/cache-vector-store.d.mts +27 -0
  346. package/esm/rag/store/cache-vector-store.d.mts.map +1 -0
  347. package/esm/rag/store/cache-vector-store.mjs +48 -0
  348. package/esm/rag/store/cache-vector-store.mjs.map +1 -0
  349. package/esm/rag/store/vector-store.contract.d.mts +38 -0
  350. package/esm/rag/store/vector-store.contract.d.mts.map +1 -0
  351. package/esm/rag/transforms/multi-query.d.mts +27 -0
  352. package/esm/rag/transforms/multi-query.d.mts.map +1 -0
  353. package/esm/rag/transforms/multi-query.mjs +41 -0
  354. package/esm/rag/transforms/multi-query.mjs.map +1 -0
  355. package/esm/security/index.mjs +5 -0
  356. package/esm/security/outbound-policy.d.mts +46 -0
  357. package/esm/security/outbound-policy.d.mts.map +1 -0
  358. package/esm/security/outbound-policy.mjs +187 -0
  359. package/esm/security/outbound-policy.mjs.map +1 -0
  360. package/esm/security/outbound-policy.type.d.mts +74 -0
  361. package/esm/security/outbound-policy.type.d.mts.map +1 -0
  362. package/esm/security/private-ip.d.mts +15 -0
  363. package/esm/security/private-ip.d.mts.map +1 -0
  364. package/esm/security/private-ip.mjs +48 -0
  365. package/esm/security/private-ip.mjs.map +1 -0
  366. package/esm/security/redact.d.mts +59 -0
  367. package/esm/security/redact.d.mts.map +1 -0
  368. package/esm/security/redact.mjs +122 -0
  369. package/esm/security/redact.mjs.map +1 -0
  370. package/esm/serve/serve.d.mts +50 -0
  371. package/esm/serve/serve.d.mts.map +1 -0
  372. package/esm/serve/serve.mjs +90 -0
  373. package/esm/serve/serve.mjs.map +1 -0
  374. package/esm/serve/sse.d.mts +20 -0
  375. package/esm/serve/sse.d.mts.map +1 -0
  376. package/esm/serve/sse.mjs +25 -0
  377. package/esm/serve/sse.mjs.map +1 -0
  378. package/esm/serve/stream-to-sse.d.mts +29 -0
  379. package/esm/serve/stream-to-sse.d.mts.map +1 -0
  380. package/esm/serve/stream-to-sse.mjs +37 -0
  381. package/esm/serve/stream-to-sse.mjs.map +1 -0
  382. package/esm/skills/catalog.d.mts +49 -0
  383. package/esm/skills/catalog.d.mts.map +1 -0
  384. package/esm/skills/catalog.mjs +140 -0
  385. package/esm/skills/catalog.mjs.map +1 -0
  386. package/esm/skills/contracts/skill-record.type.d.mts +37 -0
  387. package/esm/skills/contracts/skill-record.type.d.mts.map +1 -0
  388. package/esm/skills/contracts/skills-config.type.d.mts +108 -0
  389. package/esm/skills/contracts/skills-config.type.d.mts.map +1 -0
  390. package/esm/skills/contracts/skills-store.contract.d.mts +28 -0
  391. package/esm/skills/contracts/skills-store.contract.d.mts.map +1 -0
  392. package/esm/skills/contracts/skills.contract.d.mts +43 -0
  393. package/esm/skills/contracts/skills.contract.d.mts.map +1 -0
  394. package/esm/skills/index.d.mts +16 -0
  395. package/esm/skills/index.mjs +14 -0
  396. package/esm/skills/load-skill-tool.d.mts +38 -0
  397. package/esm/skills/load-skill-tool.d.mts.map +1 -0
  398. package/esm/skills/load-skill-tool.mjs +65 -0
  399. package/esm/skills/load-skill-tool.mjs.map +1 -0
  400. package/esm/skills/review-gate.d.mts +33 -0
  401. package/esm/skills/review-gate.d.mts.map +1 -0
  402. package/esm/skills/review-gate.mjs +60 -0
  403. package/esm/skills/review-gate.mjs.map +1 -0
  404. package/esm/skills/save-skill-tool.d.mts +39 -0
  405. package/esm/skills/save-skill-tool.d.mts.map +1 -0
  406. package/esm/skills/save-skill-tool.mjs +65 -0
  407. package/esm/skills/save-skill-tool.mjs.map +1 -0
  408. package/esm/skills/skills.d.mts +33 -0
  409. package/esm/skills/skills.d.mts.map +1 -0
  410. package/esm/skills/skills.mjs +109 -0
  411. package/esm/skills/skills.mjs.map +1 -0
  412. package/esm/skills/sources/directory-source.d.mts +19 -0
  413. package/esm/skills/sources/directory-source.d.mts.map +1 -0
  414. package/esm/skills/sources/directory-source.mjs +108 -0
  415. package/esm/skills/sources/directory-source.mjs.map +1 -0
  416. package/esm/skills/sources/index.d.mts +18 -0
  417. package/esm/skills/sources/index.d.mts.map +1 -0
  418. package/esm/skills/sources/index.mjs +27 -0
  419. package/esm/skills/sources/index.mjs.map +1 -0
  420. package/esm/skills/sources/parse-frontmatter.d.mts +27 -0
  421. package/esm/skills/sources/parse-frontmatter.d.mts.map +1 -0
  422. package/esm/skills/sources/parse-frontmatter.mjs +46 -0
  423. package/esm/skills/sources/parse-frontmatter.mjs.map +1 -0
  424. package/esm/skills/sources/store-source.d.mts +14 -0
  425. package/esm/skills/sources/store-source.d.mts.map +1 -0
  426. package/esm/skills/sources/store-source.mjs +15 -0
  427. package/esm/skills/sources/store-source.mjs.map +1 -0
  428. package/esm/skills/sources/url-source.d.mts +29 -0
  429. package/esm/skills/sources/url-source.d.mts.map +1 -0
  430. package/esm/skills/sources/url-source.mjs +117 -0
  431. package/esm/skills/sources/url-source.mjs.map +1 -0
  432. package/esm/skills/store/mock-skills-store.d.mts +57 -0
  433. package/esm/skills/store/mock-skills-store.d.mts.map +1 -0
  434. package/esm/skills/store/mock-skills-store.mjs +100 -0
  435. package/esm/skills/store/mock-skills-store.mjs.map +1 -0
  436. package/esm/skills/store/procedural-skill-store.d.mts +30 -0
  437. package/esm/skills/store/procedural-skill-store.d.mts.map +1 -0
  438. package/esm/skills/store/procedural-skill-store.mjs +125 -0
  439. package/esm/skills/store/procedural-skill-store.mjs.map +1 -0
  440. package/esm/supervisor/as-tool.mjs +2 -2
  441. package/esm/supervisor/as-tool.mjs.map +1 -1
  442. package/esm/supervisor/execution.d.mts.map +1 -1
  443. package/esm/supervisor/execution.mjs +31 -28
  444. package/esm/supervisor/execution.mjs.map +1 -1
  445. package/esm/supervisor/supervisor.d.mts.map +1 -1
  446. package/esm/supervisor/supervisor.mjs +8 -3
  447. package/esm/supervisor/supervisor.mjs.map +1 -1
  448. package/esm/system-prompt/index.d.mts +4 -0
  449. package/esm/system-prompt/system-prompt.d.mts +68 -4
  450. package/esm/system-prompt/system-prompt.d.mts.map +1 -1
  451. package/esm/system-prompt/system-prompt.mjs +89 -5
  452. package/esm/system-prompt/system-prompt.mjs.map +1 -1
  453. package/esm/team/gates.mjs +48 -0
  454. package/esm/team/gates.mjs.map +1 -0
  455. package/esm/team/index.d.mts +1 -0
  456. package/esm/team/index.mjs +3 -0
  457. package/esm/team/team.d.mts +42 -0
  458. package/esm/team/team.d.mts.map +1 -0
  459. package/esm/team/team.mjs +94 -0
  460. package/esm/team/team.mjs.map +1 -0
  461. package/esm/tool/executable-as-tool.d.mts.map +1 -1
  462. package/esm/tool/executable-as-tool.mjs +2 -2
  463. package/esm/tool/executable-as-tool.mjs.map +1 -1
  464. package/esm/tool/tool.d.mts.map +1 -1
  465. package/esm/tool/tool.mjs +2 -2
  466. package/esm/tool/tool.mjs.map +1 -1
  467. package/esm/utils/compute-cost.d.mts +17 -1
  468. package/esm/utils/compute-cost.d.mts.map +1 -1
  469. package/esm/utils/compute-cost.mjs +26 -1
  470. package/esm/utils/compute-cost.mjs.map +1 -1
  471. package/esm/utils/extract-json-lenient.d.mts +42 -0
  472. package/esm/utils/extract-json-lenient.d.mts.map +1 -0
  473. package/esm/utils/extract-json-lenient.mjs +97 -0
  474. package/esm/utils/extract-json-lenient.mjs.map +1 -0
  475. package/esm/utils/index.d.mts +4 -2
  476. package/esm/utils/index.mjs +3 -1
  477. package/esm/utils/json-schema.d.mts +1 -1
  478. package/esm/utils/prepare-attachment-part.d.mts +10 -1
  479. package/esm/utils/prepare-attachment-part.d.mts.map +1 -1
  480. package/esm/utils/prepare-attachment-part.mjs +103 -11
  481. package/esm/utils/prepare-attachment-part.mjs.map +1 -1
  482. package/esm/utils/resolve-attachment.d.mts +4 -3
  483. package/esm/utils/resolve-attachment.d.mts.map +1 -1
  484. package/esm/utils/resolve-attachment.mjs +4 -3
  485. package/esm/utils/resolve-attachment.mjs.map +1 -1
  486. package/esm/utils/run-context.d.mts +94 -0
  487. package/esm/utils/run-context.d.mts.map +1 -0
  488. package/esm/utils/run-context.mjs +98 -0
  489. package/esm/utils/run-context.mjs.map +1 -0
  490. package/esm/vcr/cassette-io.mjs +57 -0
  491. package/esm/vcr/cassette-io.mjs.map +1 -0
  492. package/esm/vcr/errors.d.mts +42 -0
  493. package/esm/vcr/errors.d.mts.map +1 -0
  494. package/esm/vcr/errors.mjs +37 -0
  495. package/esm/vcr/errors.mjs.map +1 -0
  496. package/esm/vcr/hash-request.d.mts +28 -0
  497. package/esm/vcr/hash-request.d.mts.map +1 -0
  498. package/esm/vcr/hash-request.mjs +118 -0
  499. package/esm/vcr/hash-request.mjs.map +1 -0
  500. package/esm/vcr/index.d.mts +4 -0
  501. package/esm/vcr/index.mjs +5 -0
  502. package/esm/vcr/vcr.d.mts +32 -0
  503. package/esm/vcr/vcr.d.mts.map +1 -0
  504. package/esm/vcr/vcr.mjs +248 -0
  505. package/esm/vcr/vcr.mjs.map +1 -0
  506. package/esm/vcr/vcr.type.d.mts +118 -0
  507. package/esm/vcr/vcr.type.d.mts.map +1 -0
  508. package/esm/workflow/as-tool.mjs +2 -2
  509. package/esm/workflow/as-tool.mjs.map +1 -1
  510. package/esm/workflow/engine.mjs +1 -0
  511. package/esm/workflow/engine.mjs.map +1 -1
  512. package/esm/workflow/step-runner.mjs +19 -20
  513. package/esm/workflow/step-runner.mjs.map +1 -1
  514. package/esm/workflow/workflow.d.mts.map +1 -1
  515. package/esm/workflow/workflow.mjs +8 -3
  516. package/esm/workflow/workflow.mjs.map +1 -1
  517. package/llms-full.txt +1683 -68
  518. package/llms.txt +16 -4
  519. package/package.json +7 -3
  520. package/skills/README.md +40 -4
  521. package/skills/ai-dx-helpers/SKILL.md +2 -2
  522. package/skills/approve-tool-calls/SKILL.md +134 -0
  523. package/skills/attach-ai-middleware/SKILL.md +1 -1
  524. package/skills/detect-and-redact-pii/SKILL.md +104 -0
  525. package/skills/durable-resume/SKILL.md +128 -0
  526. package/skills/escalate-block-to-human/SKILL.md +85 -0
  527. package/skills/eval-datasets-and-ci/SKILL.md +117 -0
  528. package/skills/guard-input-output/SKILL.md +117 -0
  529. package/skills/manage-prompts/SKILL.md +186 -0
  530. package/skills/observe-ai-flows/SKILL.md +94 -0
  531. package/skills/record-replay-llm/SKILL.md +92 -0
  532. package/skills/run-ai-agent/SKILL.md +26 -1
  533. package/skills/run-ai-rag/SKILL.md +139 -0
  534. package/skills/run-ai-team/SKILL.md +107 -0
  535. package/skills/run-orchestrator/SKILL.md +2 -0
  536. package/skills/run-planner/SKILL.md +73 -8
  537. package/skills/run-supervisor/SKILL.md +20 -1
  538. package/skills/use-runtime-skills/SKILL.md +106 -0
  539. package/skills/write-system-prompt/SKILL.md +30 -1
  540. package/cjs/index.cjs.map +0 -1
  541. package/esm/contracts/result/index.d.mts +0 -15
@@ -0,0 +1,37 @@
1
+ import { AIError } from "../errors/ai-error.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/vcr/errors.ts
4
+ /**
5
+ * Thrown when a `vcr(model, { mode: "replay" })` call finds no cassette
6
+ * entry matching the request hash.
7
+ *
8
+ * **The whole point of deterministic tests.** VCR in `replay` mode never
9
+ * falls back to a live provider call on a miss — that would silently
10
+ * re-introduce network/non-determinism into a test that asked for the
11
+ * opposite. Instead it throws this error so the run fails loud, telling
12
+ * the developer to re-record the cassette (run once in `record`/`auto`).
13
+ *
14
+ * Extends {@link AIError} directly (not `ProviderError`) — a cassette miss
15
+ * is a harness/config failure, not a provider failure.
16
+ *
17
+ * @example
18
+ * try {
19
+ * await vcrModel.complete(messages);
20
+ * } catch (error) {
21
+ * if (error instanceof VcrCassetteMissError) {
22
+ * console.error("Re-record the cassette:", error.path);
23
+ * }
24
+ * }
25
+ */
26
+ var VcrCassetteMissError = class extends AIError {
27
+ constructor(message, options) {
28
+ super("VCR_CASSETTE_MISS", message, options);
29
+ this.name = "VcrCassetteMissError";
30
+ this.requestHash = options?.requestHash;
31
+ this.path = options?.path;
32
+ }
33
+ };
34
+
35
+ //#endregion
36
+ export { VcrCassetteMissError };
37
+ //# sourceMappingURL=errors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/errors.ts"],"sourcesContent":["import { AIError } from \"../errors/ai-error\";\nimport type { AIErrorOptions } from \"../errors/ai-error\";\n\n/**\n * Payload for {@link VcrCassetteMissError}. Carries the looked-up request\n * hash and the cassette path so a failing CI run names exactly which call\n * was not recorded.\n */\nexport type VcrCassetteMissErrorOptions = AIErrorOptions & {\n /** The normalized request hash that found no matching cassette entry. */\n requestHash?: string;\n /** Cassette file path the lookup ran against. */\n path?: string;\n};\n\n/**\n * Thrown when a `vcr(model, { mode: \"replay\" })` call finds no cassette\n * entry matching the request hash.\n *\n * **The whole point of deterministic tests.** VCR in `replay` mode never\n * falls back to a live provider call on a miss — that would silently\n * re-introduce network/non-determinism into a test that asked for the\n * opposite. Instead it throws this error so the run fails loud, telling\n * the developer to re-record the cassette (run once in `record`/`auto`).\n *\n * Extends {@link AIError} directly (not `ProviderError`) — a cassette miss\n * is a harness/config failure, not a provider failure.\n *\n * @example\n * try {\n * await vcrModel.complete(messages);\n * } catch (error) {\n * if (error instanceof VcrCassetteMissError) {\n * console.error(\"Re-record the cassette:\", error.path);\n * }\n * }\n */\nexport class VcrCassetteMissError extends AIError {\n public readonly requestHash?: string;\n public readonly path?: string;\n\n public constructor(message: string, options?: VcrCassetteMissErrorOptions) {\n super(\"VCR_CASSETTE_MISS\", message, options);\n this.name = \"VcrCassetteMissError\";\n this.requestHash = options?.requestHash;\n this.path = options?.path;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,IAAa,uBAAb,cAA0C,QAAQ;CAIhD,AAAO,YAAY,SAAiB,SAAuC;EACzE,MAAM,qBAAqB,SAAS,OAAO;EAC3C,KAAK,OAAO;EACZ,KAAK,cAAc,SAAS;EAC5B,KAAK,OAAO,SAAS;CACvB;AACF"}
@@ -0,0 +1,28 @@
1
+ import { Message } from "../contracts/conversation-message.type.mjs";
2
+ import { ModelCallOptions } from "../contracts/model.contract.mjs";
3
+
4
+ //#region ../@warlock.js/ai/src/vcr/hash-request.d.ts
5
+ /**
6
+ * Default `ModelCallOptions` fields folded into the request hash. These are
7
+ * the inputs that materially change the model's output; everything else
8
+ * (notably `signal` and unknown provider keys) is excluded so an otherwise
9
+ * identical logical call still matches its recording.
10
+ */
11
+ declare const DEFAULT_HASH_OPTIONS: readonly string[];
12
+ /**
13
+ * Compute the stable VCR request hash for a model call.
14
+ *
15
+ * The hash covers the full `messages` array plus the picked, normalized
16
+ * `options` subset (see {@link DEFAULT_HASH_OPTIONS}). Inputs are
17
+ * canonicalized (recursive key sort) before serialization so property order
18
+ * never affects the result. `signal` and unknown provider keys are excluded.
19
+ *
20
+ * @example
21
+ * const a = hashRequest(messages, { temperature: 0.2 });
22
+ * const b = hashRequest(messages, { temperature: 0.2, signal });
23
+ * // a === b — signal is excluded.
24
+ */
25
+ declare function hashRequest(messages: Message[], options?: ModelCallOptions, hashOptions?: readonly string[]): string;
26
+ //#endregion
27
+ export { DEFAULT_HASH_OPTIONS, hashRequest };
28
+ //# sourceMappingURL=hash-request.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash-request.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/hash-request.ts"],"mappings":";;;;;;AAUA;;;;cAAa,oBAAA;AAiJb;;;;;;;;;;AAGuD;;;AAHvD,iBAAgB,WAAA,CACd,QAAA,EAAU,OAAA,IACV,OAAA,GAAU,gBAAgB,EAC1B,WAAA"}
@@ -0,0 +1,118 @@
1
+ //#region ../@warlock.js/ai/src/vcr/hash-request.ts
2
+ /**
3
+ * Default `ModelCallOptions` fields folded into the request hash. These are
4
+ * the inputs that materially change the model's output; everything else
5
+ * (notably `signal` and unknown provider keys) is excluded so an otherwise
6
+ * identical logical call still matches its recording.
7
+ */
8
+ const DEFAULT_HASH_OPTIONS = [
9
+ "temperature",
10
+ "maxTokens",
11
+ "responseSchema",
12
+ "tools",
13
+ "reasoning"
14
+ ];
15
+ /**
16
+ * Reduce a tool to the parts the model actually conditions on: its name,
17
+ * description, and the *shape* of its input schema. Two tools that differ
18
+ * only by object identity (a fresh schema instance per import) hash
19
+ * identically; a real contract change (renamed field, new description)
20
+ * invalidates the recording.
21
+ *
22
+ * The input schema is fingerprinted structurally — a Standard Schema is an
23
+ * opaque object, so we serialize its enumerable own keys rather than
24
+ * attempting to read its internals.
25
+ */
26
+ function fingerprintTool(tool) {
27
+ return {
28
+ name: tool.name,
29
+ description: tool.description,
30
+ input: tool.input ? schemaShape(tool.input) : void 0
31
+ };
32
+ }
33
+ /**
34
+ * Produce a stable, JSON-safe fingerprint of an arbitrary schema object.
35
+ * Records only the structural skeleton (own enumerable keys, recursively)
36
+ * so harmless instance differences don't perturb the hash while a genuine
37
+ * structural change does.
38
+ */
39
+ function schemaShape(value, depth = 0) {
40
+ if (depth > 6 || value === null || typeof value !== "object") return typeof value;
41
+ if (Array.isArray(value)) return value.map((item) => schemaShape(item, depth + 1));
42
+ const out = {};
43
+ for (const key of Object.keys(value).sort()) out[key] = schemaShape(value[key], depth + 1);
44
+ return out;
45
+ }
46
+ /**
47
+ * Pick the hashable subset of `options`, normalizing `tools` into their
48
+ * name+description+schema-shape fingerprint. `signal` and any field not in
49
+ * `hashOptions` are dropped.
50
+ */
51
+ function pickOptions(options, hashOptions) {
52
+ if (!options) return {};
53
+ const picked = {};
54
+ for (const key of hashOptions) {
55
+ const value = options[key];
56
+ if (value === void 0) continue;
57
+ if (key === "tools" && Array.isArray(value)) {
58
+ picked[key] = value.map(fingerprintTool);
59
+ continue;
60
+ }
61
+ picked[key] = value;
62
+ }
63
+ return picked;
64
+ }
65
+ /**
66
+ * Recursively sort object keys so two logically-equal payloads serialize to
67
+ * byte-identical JSON regardless of property insertion order.
68
+ */
69
+ function canonicalize(value) {
70
+ if (value === null || typeof value !== "object") return value;
71
+ if (Array.isArray(value)) return value.map(canonicalize);
72
+ const out = {};
73
+ for (const key of Object.keys(value).sort()) out[key] = canonicalize(value[key]);
74
+ return out;
75
+ }
76
+ /**
77
+ * Non-cryptographic 53-bit string hash (FNV-style, cyrb53). Deterministic
78
+ * across runs and platforms; collision-resistant enough for a per-cassette
79
+ * keyspace. Returned as a base-36 string.
80
+ */
81
+ function hashString(input) {
82
+ let h1 = 3735928559;
83
+ let h2 = 1103547991;
84
+ for (let i = 0; i < input.length; i++) {
85
+ const ch = input.charCodeAt(i);
86
+ h1 = Math.imul(h1 ^ ch, 2654435761);
87
+ h2 = Math.imul(h2 ^ ch, 1597334677);
88
+ }
89
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
90
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
91
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
92
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
93
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
94
+ }
95
+ /**
96
+ * Compute the stable VCR request hash for a model call.
97
+ *
98
+ * The hash covers the full `messages` array plus the picked, normalized
99
+ * `options` subset (see {@link DEFAULT_HASH_OPTIONS}). Inputs are
100
+ * canonicalized (recursive key sort) before serialization so property order
101
+ * never affects the result. `signal` and unknown provider keys are excluded.
102
+ *
103
+ * @example
104
+ * const a = hashRequest(messages, { temperature: 0.2 });
105
+ * const b = hashRequest(messages, { temperature: 0.2, signal });
106
+ * // a === b — signal is excluded.
107
+ */
108
+ function hashRequest(messages, options, hashOptions = DEFAULT_HASH_OPTIONS) {
109
+ const payload = canonicalize({
110
+ messages,
111
+ options: pickOptions(options, hashOptions)
112
+ });
113
+ return hashString(JSON.stringify(payload));
114
+ }
115
+
116
+ //#endregion
117
+ export { DEFAULT_HASH_OPTIONS, hashRequest };
118
+ //# sourceMappingURL=hash-request.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash-request.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/hash-request.ts"],"sourcesContent":["import type { Message } from \"../contracts/conversation-message.type\";\nimport type { ModelCallOptions } from \"../contracts/model.contract\";\nimport type { ToolConfig } from \"../contracts/tool.contract\";\n\n/**\n * Default `ModelCallOptions` fields folded into the request hash. These are\n * the inputs that materially change the model's output; everything else\n * (notably `signal` and unknown provider keys) is excluded so an otherwise\n * identical logical call still matches its recording.\n */\nexport const DEFAULT_HASH_OPTIONS: readonly string[] = [\n \"temperature\",\n \"maxTokens\",\n \"responseSchema\",\n \"tools\",\n \"reasoning\",\n];\n\n/**\n * Reduce a tool to the parts the model actually conditions on: its name,\n * description, and the *shape* of its input schema. Two tools that differ\n * only by object identity (a fresh schema instance per import) hash\n * identically; a real contract change (renamed field, new description)\n * invalidates the recording.\n *\n * The input schema is fingerprinted structurally — a Standard Schema is an\n * opaque object, so we serialize its enumerable own keys rather than\n * attempting to read its internals.\n */\nfunction fingerprintTool(tool: ToolConfig<unknown, unknown>): unknown {\n return {\n name: tool.name,\n description: tool.description,\n input: tool.input ? schemaShape(tool.input) : undefined,\n };\n}\n\n/**\n * Produce a stable, JSON-safe fingerprint of an arbitrary schema object.\n * Records only the structural skeleton (own enumerable keys, recursively)\n * so harmless instance differences don't perturb the hash while a genuine\n * structural change does.\n */\nfunction schemaShape(value: unknown, depth = 0): unknown {\n if (depth > 6 || value === null || typeof value !== \"object\") {\n return typeof value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => schemaShape(item, depth + 1));\n }\n\n const out: Record<string, unknown> = {};\n\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n out[key] = schemaShape((value as Record<string, unknown>)[key], depth + 1);\n }\n\n return out;\n}\n\n/**\n * Pick the hashable subset of `options`, normalizing `tools` into their\n * name+description+schema-shape fingerprint. `signal` and any field not in\n * `hashOptions` are dropped.\n */\nfunction pickOptions(\n options: ModelCallOptions | undefined,\n hashOptions: readonly string[],\n): Record<string, unknown> {\n if (!options) {\n return {};\n }\n\n const picked: Record<string, unknown> = {};\n\n for (const key of hashOptions) {\n const value = options[key];\n\n if (value === undefined) {\n continue;\n }\n\n if (key === \"tools\" && Array.isArray(value)) {\n picked[key] = (value as ToolConfig<unknown, unknown>[]).map(fingerprintTool);\n continue;\n }\n\n picked[key] = value;\n }\n\n return picked;\n}\n\n/**\n * Recursively sort object keys so two logically-equal payloads serialize to\n * byte-identical JSON regardless of property insertion order.\n */\nfunction canonicalize(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map(canonicalize);\n }\n\n const out: Record<string, unknown> = {};\n\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n out[key] = canonicalize((value as Record<string, unknown>)[key]);\n }\n\n return out;\n}\n\n/**\n * Non-cryptographic 53-bit string hash (FNV-style, cyrb53). Deterministic\n * across runs and platforms; collision-resistant enough for a per-cassette\n * keyspace. Returned as a base-36 string.\n */\nfunction hashString(input: string): string {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n\n for (let i = 0; i < input.length; i++) {\n const ch = input.charCodeAt(i);\n\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n\n const combined = 4294967296 * (2097151 & h2) + (h1 >>> 0);\n\n return combined.toString(36);\n}\n\n/**\n * Compute the stable VCR request hash for a model call.\n *\n * The hash covers the full `messages` array plus the picked, normalized\n * `options` subset (see {@link DEFAULT_HASH_OPTIONS}). Inputs are\n * canonicalized (recursive key sort) before serialization so property order\n * never affects the result. `signal` and unknown provider keys are excluded.\n *\n * @example\n * const a = hashRequest(messages, { temperature: 0.2 });\n * const b = hashRequest(messages, { temperature: 0.2, signal });\n * // a === b — signal is excluded.\n */\nexport function hashRequest(\n messages: Message[],\n options?: ModelCallOptions,\n hashOptions: readonly string[] = DEFAULT_HASH_OPTIONS,\n): string {\n const payload = canonicalize({\n messages,\n options: pickOptions(options, hashOptions),\n });\n\n return hashString(JSON.stringify(payload));\n}\n"],"mappings":";;;;;;;AAUA,MAAa,uBAA0C;CACrD;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;AAaA,SAAS,gBAAgB,MAA6C;CACpE,OAAO;EACL,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,OAAO,KAAK,QAAQ,YAAY,KAAK,KAAK,IAAI;CAChD;AACF;;;;;;;AAQA,SAAS,YAAY,OAAgB,QAAQ,GAAY;CACvD,IAAI,QAAQ,KAAK,UAAU,QAAQ,OAAO,UAAU,UAClD,OAAO,OAAO;CAGhB,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,SAAS,YAAY,MAAM,QAAQ,CAAC,CAAC;CAGzD,MAAM,MAA+B,CAAC;CAEtC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAgC,CAAC,CAAC,KAAK,GACnE,IAAI,OAAO,YAAa,MAAkC,MAAM,QAAQ,CAAC;CAG3E,OAAO;AACT;;;;;;AAOA,SAAS,YACP,SACA,aACyB;CACzB,IAAI,CAAC,SACH,OAAO,CAAC;CAGV,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,OAAO,aAAa;EAC7B,MAAM,QAAQ,QAAQ;EAEtB,IAAI,UAAU,QACZ;EAGF,IAAI,QAAQ,WAAW,MAAM,QAAQ,KAAK,GAAG;GAC3C,OAAO,OAAQ,MAAyC,IAAI,eAAe;GAC3E;EACF;EAEA,OAAO,OAAO;CAChB;CAEA,OAAO;AACT;;;;;AAMA,SAAS,aAAa,OAAyB;CAC7C,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,IAAI,YAAY;CAG/B,MAAM,MAA+B,CAAC;CAEtC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAgC,CAAC,CAAC,KAAK,GACnE,IAAI,OAAO,aAAc,MAAkC,IAAI;CAGjE,OAAO;AACT;;;;;;AAOA,SAAS,WAAW,OAAuB;CACzC,IAAI,KAAK;CACT,IAAI,KAAK;CAET,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,KAAK,MAAM,WAAW,CAAC;EAE7B,KAAK,KAAK,KAAK,KAAK,IAAI,UAAU;EAClC,KAAK,KAAK,KAAK,KAAK,IAAI,UAAU;CACpC;CAEA,KAAK,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC3C,MAAM,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC5C,KAAK,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC3C,MAAM,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAI5C,QAFiB,cAAc,UAAU,OAAO,OAAO,GAExC,CAAC,SAAS,EAAE;AAC7B;;;;;;;;;;;;;;AAeA,SAAgB,YACd,UACA,SACA,cAAiC,sBACzB;CACR,MAAM,UAAU,aAAa;EAC3B;EACA,SAAS,YAAY,SAAS,WAAW;CAC3C,CAAC;CAED,OAAO,WAAW,KAAK,UAAU,OAAO,CAAC;AAC3C"}
@@ -0,0 +1,4 @@
1
+ import { Cassette, CassetteEntry, VcrMode, VcrModel, VcrOptions } from "./vcr.type.mjs";
2
+ import { vcr } from "./vcr.mjs";
3
+ import { VcrCassetteMissError, VcrCassetteMissErrorOptions } from "./errors.mjs";
4
+ import { DEFAULT_HASH_OPTIONS, hashRequest } from "./hash-request.mjs";
@@ -0,0 +1,5 @@
1
+ import { VcrCassetteMissError } from "./errors.mjs";
2
+ import { DEFAULT_HASH_OPTIONS, hashRequest } from "./hash-request.mjs";
3
+ import { vcr } from "./vcr.mjs";
4
+
5
+ export { };
@@ -0,0 +1,32 @@
1
+ import { ModelContract } from "../contracts/model.contract.mjs";
2
+ import { VcrModel, VcrOptions } from "./vcr.type.mjs";
3
+
4
+ //#region ../@warlock.js/ai/src/vcr/vcr.d.ts
5
+ /**
6
+ * Wrap any `ModelContract` in a record/replay decorator backed by a JSON
7
+ * cassette on disk.
8
+ *
9
+ * **What it does.** Intercepts only `complete()`/`stream()` — the single
10
+ * seam every agent trip funnels through — and delegates `name`, `provider`,
11
+ * `capabilities`, and `pricing` to the inner model untouched. On a call it
12
+ * computes a stable hash over `{ messages, picked options }` and, depending
13
+ * on `mode`:
14
+ *
15
+ * - **`record`** — always calls the inner model and appends a cassette entry.
16
+ * - **`replay`** — returns the matching entry (or re-yields its chunks /
17
+ * re-throws its error); a miss throws `VcrCassetteMissError`, never a live
18
+ * call.
19
+ * - **`auto`** (default) — replays a hit, records a miss.
20
+ *
21
+ * Composes *below* `fallbackModel` and works with any adapter because it
22
+ * depends only on `ModelContract`. Call `save()` to flush new entries.
23
+ *
24
+ * @example
25
+ * const model = vcr(liveModel, { path: "./cassettes/support.json" });
26
+ * const response = await model.complete(messages);
27
+ * await model.save(); // first run records; later runs replay deterministically.
28
+ */
29
+ declare function vcr(model: ModelContract, options: VcrOptions): VcrModel;
30
+ //#endregion
31
+ export { vcr };
32
+ //# sourceMappingURL=vcr.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vcr.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/vcr.ts"],"mappings":";;;;;;AA8UA;;;;;;;;;;;;;;AAAwE;;;;;;;;iBAAxD,GAAA,CAAI,KAAA,EAAO,aAAA,EAAe,OAAA,EAAS,UAAA,GAAa,QAAA"}
@@ -0,0 +1,248 @@
1
+ import { redact } from "../security/redact.mjs";
2
+ import { emptyCassette, loadCassette, saveCassette } from "./cassette-io.mjs";
3
+ import { VcrCassetteMissError } from "./errors.mjs";
4
+ import { DEFAULT_HASH_OPTIONS, hashRequest } from "./hash-request.mjs";
5
+
6
+ //#region ../@warlock.js/ai/src/vcr/vcr.ts
7
+ /**
8
+ * Internal decorator that wraps an inner `ModelContract`, intercepting only
9
+ * `complete()`/`stream()` and delegating every identity getter to the inner
10
+ * model. Drives the record/replay state machine over a single in-memory
11
+ * {@link Cassette}.
12
+ *
13
+ * **Why a class.** It holds mutable per-instance state (the loaded cassette,
14
+ * the dirty flag, the load promise) behind a stable `ModelContract` surface;
15
+ * the public API is the `vcr()` factory, never `new`.
16
+ */
17
+ var Vcr = class {
18
+ constructor(inner, options) {
19
+ this.inner = inner;
20
+ this.dirty = false;
21
+ this.warnedVerbatim = false;
22
+ this.path = options.path;
23
+ this.mode = options.mode ?? "auto";
24
+ this.hashOptions = options.hashOptions ?? DEFAULT_HASH_OPTIONS;
25
+ this.recordRequest = options.recordRequest ?? "verbatim";
26
+ this.redactRequestHook = options.redactRequest;
27
+ this.redactResponseHook = options.redactResponse;
28
+ this.redactErrorHook = options.redactError;
29
+ this.loadedCassette = emptyCassette(inner.name, inner.provider);
30
+ }
31
+ /** Inner model identifier — delegated verbatim. */
32
+ get name() {
33
+ return this.inner.name;
34
+ }
35
+ /** Inner provider — delegated verbatim. */
36
+ get provider() {
37
+ return this.inner.provider;
38
+ }
39
+ /** Inner capability flags — delegated verbatim. */
40
+ get capabilities() {
41
+ return this.inner.capabilities;
42
+ }
43
+ /** Inner pricing — delegated verbatim so cost accounting is unchanged. */
44
+ get pricing() {
45
+ return this.inner.pricing;
46
+ }
47
+ /** Loaded/recorded cassette, exposed for assertions. */
48
+ get cassette() {
49
+ return this.loadedCassette;
50
+ }
51
+ /**
52
+ * Load the on-disk cassette exactly once. Pure `record` mode skips the
53
+ * read — it always writes fresh — but the in-memory cassette still starts
54
+ * empty so a record run never accidentally replays a stale entry.
55
+ */
56
+ async ensureLoaded() {
57
+ if (this.loadPromise) return this.loadPromise;
58
+ this.loadPromise = this.mode === "record" ? Promise.resolve() : (async () => {
59
+ this.loadedCassette = await loadCassette(this.path, this.inner.name, this.inner.provider);
60
+ })();
61
+ return this.loadPromise;
62
+ }
63
+ /** Find a recorded entry whose hash matches the current request. */
64
+ findEntry(hash) {
65
+ return this.loadedCassette.entries.find((entry) => entry.requestHash === hash);
66
+ }
67
+ /** Re-throw a recorded error by reconstructing a plain `Error`. */
68
+ throwRecordedError(entry) {
69
+ const error = new Error(entry.error?.message ?? "Recorded error");
70
+ error.name = entry.error?.name ?? "Error";
71
+ throw error;
72
+ }
73
+ /**
74
+ * Non-streaming call. In `replay` a miss throws; in `auto`/`record` a miss
75
+ * calls the inner model and records the outcome (response or error).
76
+ */
77
+ async complete(messages, options) {
78
+ await this.ensureLoaded();
79
+ const hash = hashRequest(messages, options, this.hashOptions);
80
+ if (this.mode !== "record") {
81
+ const entry = this.findEntry(hash);
82
+ if (entry) {
83
+ if (entry.error) this.throwRecordedError(entry);
84
+ if (entry.response) return entry.response;
85
+ }
86
+ if (this.mode === "replay") throw new VcrCassetteMissError(`No cassette entry for this request (model "${this.inner.name}", hash ${hash}).`, {
87
+ requestHash: hash,
88
+ path: this.path
89
+ });
90
+ }
91
+ try {
92
+ const response = await this.inner.complete(messages, options);
93
+ this.record({
94
+ requestHash: hash,
95
+ request: {
96
+ messages,
97
+ options
98
+ },
99
+ response
100
+ });
101
+ return response;
102
+ } catch (error) {
103
+ this.record({
104
+ requestHash: hash,
105
+ request: {
106
+ messages,
107
+ options
108
+ },
109
+ error: {
110
+ name: error.name,
111
+ message: error.message
112
+ }
113
+ });
114
+ throw error;
115
+ }
116
+ }
117
+ /**
118
+ * Streaming call. On replay the stored `chunks` are re-yielded in order
119
+ * (reproducing the `delta`/`tool-call`/`done` sequence) or the stored
120
+ * error is re-thrown. On record the inner stream is buffered into
121
+ * `chunks[]` while being re-emitted, then recorded once exhausted.
122
+ */
123
+ async *stream(messages, options) {
124
+ await this.ensureLoaded();
125
+ const hash = hashRequest(messages, options, this.hashOptions);
126
+ if (this.mode !== "record") {
127
+ const entry = this.findEntry(hash);
128
+ if (entry) {
129
+ if (entry.error) this.throwRecordedError(entry);
130
+ if (entry.chunks) {
131
+ for (const chunk of entry.chunks) yield chunk;
132
+ return;
133
+ }
134
+ }
135
+ if (this.mode === "replay") throw new VcrCassetteMissError(`No cassette entry for this request (model "${this.inner.name}", hash ${hash}).`, {
136
+ requestHash: hash,
137
+ path: this.path
138
+ });
139
+ }
140
+ const chunks = [];
141
+ try {
142
+ for await (const chunk of this.inner.stream(messages, options)) {
143
+ chunks.push(chunk);
144
+ yield chunk;
145
+ }
146
+ } catch (error) {
147
+ this.record({
148
+ requestHash: hash,
149
+ request: {
150
+ messages,
151
+ options
152
+ },
153
+ error: {
154
+ name: error.name,
155
+ message: error.message
156
+ }
157
+ });
158
+ throw error;
159
+ }
160
+ this.record({
161
+ requestHash: hash,
162
+ request: {
163
+ messages,
164
+ options
165
+ },
166
+ chunks
167
+ });
168
+ }
169
+ /**
170
+ * Append an entry to the in-memory cassette and mark it dirty, applying
171
+ * the configured request/response/error redaction first (S2). Pure
172
+ * `replay` never reaches this path, so no replay run is ever dirtied.
173
+ */
174
+ record(entry) {
175
+ this.loadedCassette.entries.push(this.applyRedaction(entry));
176
+ this.dirty = true;
177
+ this.maybeWarnVerbatim();
178
+ }
179
+ /**
180
+ * Apply the persisted-body privacy controls to an entry before it is
181
+ * stored. The request body follows `recordRequest`; response/error
182
+ * redactors are applied only when supplied. Replay matching is by the
183
+ * recomputed hash (kept verbatim), so none of this affects replay.
184
+ */
185
+ applyRedaction(entry) {
186
+ const out = {
187
+ requestHash: entry.requestHash,
188
+ request: entry.request
189
+ };
190
+ if (this.recordRequest === "hash-only") out.request = { messages: [] };
191
+ else if (this.recordRequest === "redacted") out.request = this.redactRequestHook ? this.redactRequestHook(entry.request) : redact(entry.request);
192
+ if (entry.response) out.response = this.redactResponseHook ? this.redactResponseHook(entry.response) : entry.response;
193
+ if (entry.chunks) out.chunks = entry.chunks;
194
+ if (entry.error) out.error = this.redactErrorHook ? this.redactErrorHook(entry.error) : entry.error;
195
+ return out;
196
+ }
197
+ /**
198
+ * Warn once (outside tests) when the cassette is recording verbatim
199
+ * request bodies — they may carry prompts, tool args, and PII, so the
200
+ * file is not safe to commit until sanitized.
201
+ */
202
+ maybeWarnVerbatim() {
203
+ if (this.warnedVerbatim || this.recordRequest !== "verbatim") return;
204
+ if (process.env.VITEST || process.env.NODE_ENV === "test") return;
205
+ this.warnedVerbatim = true;
206
+ console.warn(`[warlock-ai] VCR is recording verbatim request bodies to "${this.path}" — prompts, tool args, and any PII are stored unredacted. Sanitize before committing, or set recordRequest: "redacted" | "hash-only".`);
207
+ }
208
+ /**
209
+ * Flush newly recorded entries to `path`. No-op when nothing was recorded
210
+ * (pure replay, or a record/auto run that only ever hit cached entries).
211
+ */
212
+ async save() {
213
+ if (!this.dirty) return;
214
+ await saveCassette(this.path, this.loadedCassette);
215
+ this.dirty = false;
216
+ }
217
+ };
218
+ /**
219
+ * Wrap any `ModelContract` in a record/replay decorator backed by a JSON
220
+ * cassette on disk.
221
+ *
222
+ * **What it does.** Intercepts only `complete()`/`stream()` — the single
223
+ * seam every agent trip funnels through — and delegates `name`, `provider`,
224
+ * `capabilities`, and `pricing` to the inner model untouched. On a call it
225
+ * computes a stable hash over `{ messages, picked options }` and, depending
226
+ * on `mode`:
227
+ *
228
+ * - **`record`** — always calls the inner model and appends a cassette entry.
229
+ * - **`replay`** — returns the matching entry (or re-yields its chunks /
230
+ * re-throws its error); a miss throws `VcrCassetteMissError`, never a live
231
+ * call.
232
+ * - **`auto`** (default) — replays a hit, records a miss.
233
+ *
234
+ * Composes *below* `fallbackModel` and works with any adapter because it
235
+ * depends only on `ModelContract`. Call `save()` to flush new entries.
236
+ *
237
+ * @example
238
+ * const model = vcr(liveModel, { path: "./cassettes/support.json" });
239
+ * const response = await model.complete(messages);
240
+ * await model.save(); // first run records; later runs replay deterministically.
241
+ */
242
+ function vcr(model, options) {
243
+ return new Vcr(model, options);
244
+ }
245
+
246
+ //#endregion
247
+ export { vcr };
248
+ //# sourceMappingURL=vcr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vcr.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/vcr.ts"],"sourcesContent":["import type { Message } from \"../contracts/conversation-message.type\";\nimport type {\n ModelCallOptions,\n ModelCapabilities,\n ModelContract,\n ModelResponse,\n ModelStreamChunk,\n} from \"../contracts/model.contract\";\nimport type { ModelPricing } from \"../contracts/result/model-pricing.type\";\nimport { redact } from \"../security/redact\";\nimport { emptyCassette, loadCassette, saveCassette } from \"./cassette-io\";\nimport { VcrCassetteMissError } from \"./errors\";\nimport { DEFAULT_HASH_OPTIONS, hashRequest } from \"./hash-request\";\nimport type { Cassette, CassetteEntry, VcrMode, VcrModel, VcrOptions } from \"./vcr.type\";\n\n/**\n * Internal decorator that wraps an inner `ModelContract`, intercepting only\n * `complete()`/`stream()` and delegating every identity getter to the inner\n * model. Drives the record/replay state machine over a single in-memory\n * {@link Cassette}.\n *\n * **Why a class.** It holds mutable per-instance state (the loaded cassette,\n * the dirty flag, the load promise) behind a stable `ModelContract` surface;\n * the public API is the `vcr()` factory, never `new`.\n */\nclass Vcr implements VcrModel {\n private readonly mode: VcrMode;\n private readonly path: string;\n private readonly hashOptions: readonly string[];\n\n /** Loaded + newly recorded entries. Mutated in place as we record. */\n private loadedCassette: Cassette;\n\n /** Set when an entry is recorded so `save()` knows there's work to flush. */\n private dirty = false;\n\n /** One-shot lazy load of the on-disk cassette, shared across calls. */\n private loadPromise: Promise<void> | undefined;\n\n /** Persisted-body privacy controls (S2). */\n private readonly recordRequest: NonNullable<VcrOptions[\"recordRequest\"]>;\n private readonly redactRequestHook: VcrOptions[\"redactRequest\"];\n private readonly redactResponseHook: VcrOptions[\"redactResponse\"];\n private readonly redactErrorHook: VcrOptions[\"redactError\"];\n\n /** Verbatim-recording warning fires at most once per instance. */\n private warnedVerbatim = false;\n\n public constructor(\n private readonly inner: ModelContract,\n options: VcrOptions,\n ) {\n this.path = options.path;\n this.mode = options.mode ?? \"auto\";\n this.hashOptions = options.hashOptions ?? DEFAULT_HASH_OPTIONS;\n this.recordRequest = options.recordRequest ?? \"verbatim\";\n this.redactRequestHook = options.redactRequest;\n this.redactResponseHook = options.redactResponse;\n this.redactErrorHook = options.redactError;\n this.loadedCassette = emptyCassette(inner.name, inner.provider);\n }\n\n /** Inner model identifier — delegated verbatim. */\n public get name(): string {\n return this.inner.name;\n }\n\n /** Inner provider — delegated verbatim. */\n public get provider(): string {\n return this.inner.provider;\n }\n\n /** Inner capability flags — delegated verbatim. */\n public get capabilities(): ModelCapabilities | undefined {\n return this.inner.capabilities;\n }\n\n /** Inner pricing — delegated verbatim so cost accounting is unchanged. */\n public get pricing(): ModelPricing | undefined {\n return this.inner.pricing;\n }\n\n /** Loaded/recorded cassette, exposed for assertions. */\n public get cassette(): Cassette {\n return this.loadedCassette;\n }\n\n /**\n * Load the on-disk cassette exactly once. Pure `record` mode skips the\n * read — it always writes fresh — but the in-memory cassette still starts\n * empty so a record run never accidentally replays a stale entry.\n */\n private async ensureLoaded(): Promise<void> {\n if (this.loadPromise) {\n return this.loadPromise;\n }\n\n this.loadPromise =\n this.mode === \"record\"\n ? Promise.resolve()\n : (async () => {\n this.loadedCassette = await loadCassette(\n this.path,\n this.inner.name,\n this.inner.provider,\n );\n })();\n\n return this.loadPromise;\n }\n\n /** Find a recorded entry whose hash matches the current request. */\n private findEntry(hash: string): CassetteEntry | undefined {\n return this.loadedCassette.entries.find((entry) => entry.requestHash === hash);\n }\n\n /** Re-throw a recorded error by reconstructing a plain `Error`. */\n private throwRecordedError(entry: CassetteEntry): never {\n const error = new Error(entry.error?.message ?? \"Recorded error\");\n\n error.name = entry.error?.name ?? \"Error\";\n\n throw error;\n }\n\n /**\n * Non-streaming call. In `replay` a miss throws; in `auto`/`record` a miss\n * calls the inner model and records the outcome (response or error).\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n await this.ensureLoaded();\n\n const hash = hashRequest(messages, options, this.hashOptions);\n\n if (this.mode !== \"record\") {\n const entry = this.findEntry(hash);\n\n if (entry) {\n if (entry.error) {\n this.throwRecordedError(entry);\n }\n\n if (entry.response) {\n return entry.response;\n }\n }\n\n if (this.mode === \"replay\") {\n throw new VcrCassetteMissError(\n `No cassette entry for this request (model \"${this.inner.name}\", hash ${hash}).`,\n { requestHash: hash, path: this.path },\n );\n }\n }\n\n try {\n const response = await this.inner.complete(messages, options);\n\n this.record({ requestHash: hash, request: { messages, options }, response });\n\n return response;\n } catch (error) {\n this.record({\n requestHash: hash,\n request: { messages, options },\n error: { name: (error as Error).name, message: (error as Error).message },\n });\n\n throw error;\n }\n }\n\n /**\n * Streaming call. On replay the stored `chunks` are re-yielded in order\n * (reproducing the `delta`/`tool-call`/`done` sequence) or the stored\n * error is re-thrown. On record the inner stream is buffered into\n * `chunks[]` while being re-emitted, then recorded once exhausted.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n await this.ensureLoaded();\n\n const hash = hashRequest(messages, options, this.hashOptions);\n\n if (this.mode !== \"record\") {\n const entry = this.findEntry(hash);\n\n if (entry) {\n if (entry.error) {\n this.throwRecordedError(entry);\n }\n\n if (entry.chunks) {\n for (const chunk of entry.chunks) {\n yield chunk;\n }\n\n return;\n }\n }\n\n if (this.mode === \"replay\") {\n throw new VcrCassetteMissError(\n `No cassette entry for this request (model \"${this.inner.name}\", hash ${hash}).`,\n { requestHash: hash, path: this.path },\n );\n }\n }\n\n const chunks: ModelStreamChunk[] = [];\n\n try {\n for await (const chunk of this.inner.stream(messages, options)) {\n chunks.push(chunk);\n\n yield chunk;\n }\n } catch (error) {\n this.record({\n requestHash: hash,\n request: { messages, options },\n error: { name: (error as Error).name, message: (error as Error).message },\n });\n\n throw error;\n }\n\n this.record({ requestHash: hash, request: { messages, options }, chunks });\n }\n\n /**\n * Append an entry to the in-memory cassette and mark it dirty, applying\n * the configured request/response/error redaction first (S2). Pure\n * `replay` never reaches this path, so no replay run is ever dirtied.\n */\n private record(entry: CassetteEntry): void {\n this.loadedCassette.entries.push(this.applyRedaction(entry));\n this.dirty = true;\n this.maybeWarnVerbatim();\n }\n\n /**\n * Apply the persisted-body privacy controls to an entry before it is\n * stored. The request body follows `recordRequest`; response/error\n * redactors are applied only when supplied. Replay matching is by the\n * recomputed hash (kept verbatim), so none of this affects replay.\n */\n private applyRedaction(entry: CassetteEntry): CassetteEntry {\n const out: CassetteEntry = {\n requestHash: entry.requestHash,\n request: entry.request,\n };\n\n if (this.recordRequest === \"hash-only\") {\n out.request = { messages: [] };\n } else if (this.recordRequest === \"redacted\") {\n out.request = this.redactRequestHook\n ? this.redactRequestHook(entry.request)\n : redact(entry.request);\n }\n\n if (entry.response) {\n out.response = this.redactResponseHook\n ? this.redactResponseHook(entry.response)\n : entry.response;\n }\n if (entry.chunks) {\n out.chunks = entry.chunks;\n }\n if (entry.error) {\n out.error = this.redactErrorHook\n ? this.redactErrorHook(entry.error)\n : entry.error;\n }\n\n return out;\n }\n\n /**\n * Warn once (outside tests) when the cassette is recording verbatim\n * request bodies — they may carry prompts, tool args, and PII, so the\n * file is not safe to commit until sanitized.\n */\n private maybeWarnVerbatim(): void {\n if (this.warnedVerbatim || this.recordRequest !== \"verbatim\") return;\n if (process.env.VITEST || process.env.NODE_ENV === \"test\") return;\n\n this.warnedVerbatim = true;\n console.warn(\n `[warlock-ai] VCR is recording verbatim request bodies to \"${this.path}\" — prompts, tool args, and any PII are stored unredacted. ` +\n 'Sanitize before committing, or set recordRequest: \"redacted\" | \"hash-only\".',\n );\n }\n\n /**\n * Flush newly recorded entries to `path`. No-op when nothing was recorded\n * (pure replay, or a record/auto run that only ever hit cached entries).\n */\n public async save(): Promise<void> {\n if (!this.dirty) {\n return;\n }\n\n await saveCassette(this.path, this.loadedCassette);\n this.dirty = false;\n }\n}\n\n/**\n * Wrap any `ModelContract` in a record/replay decorator backed by a JSON\n * cassette on disk.\n *\n * **What it does.** Intercepts only `complete()`/`stream()` — the single\n * seam every agent trip funnels through — and delegates `name`, `provider`,\n * `capabilities`, and `pricing` to the inner model untouched. On a call it\n * computes a stable hash over `{ messages, picked options }` and, depending\n * on `mode`:\n *\n * - **`record`** — always calls the inner model and appends a cassette entry.\n * - **`replay`** — returns the matching entry (or re-yields its chunks /\n * re-throws its error); a miss throws `VcrCassetteMissError`, never a live\n * call.\n * - **`auto`** (default) — replays a hit, records a miss.\n *\n * Composes *below* `fallbackModel` and works with any adapter because it\n * depends only on `ModelContract`. Call `save()` to flush new entries.\n *\n * @example\n * const model = vcr(liveModel, { path: \"./cassettes/support.json\" });\n * const response = await model.complete(messages);\n * await model.save(); // first run records; later runs replay deterministically.\n */\nexport function vcr(model: ModelContract, options: VcrOptions): VcrModel {\n return new Vcr(model, options);\n}\n"],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { Message } from "../contracts/conversation-message.type.mjs";
2
+ import { ModelCallOptions, ModelContract, ModelResponse, ModelStreamChunk } from "../contracts/model.contract.mjs";
3
+
4
+ //#region ../@warlock.js/ai/src/vcr/vcr.type.d.ts
5
+ /**
6
+ * Record/replay mode for a {@link VcrModel}.
7
+ *
8
+ * - **`record`** — always call the inner model and append the result to
9
+ * the cassette. Never replays. Use to (re)capture a fresh cassette.
10
+ * - **`replay`** — never call the inner model. A cassette hit returns the
11
+ * stored response/chunks/error; a miss throws `VcrCassetteMissError`
12
+ * (never a silent live call). Use in CI for deterministic, offline tests.
13
+ * - **`auto`** (default) — replay on a cassette hit, record on a miss.
14
+ * The friendliest mode for local dev: records once, replays thereafter.
15
+ */
16
+ type VcrMode = "record" | "replay" | "auto";
17
+ /**
18
+ * A single recorded request → response pair on a {@link Cassette}.
19
+ *
20
+ * Exactly one of `response` / `chunks` / `error` is populated, mirroring
21
+ * the three outcomes of a model call: a non-streaming reply, a streamed
22
+ * chunk sequence, or a thrown provider error.
23
+ */
24
+ type CassetteEntry = {
25
+ /** Stable hash of the normalized request (messages + hashed options). */requestHash: string;
26
+ /**
27
+ * What was sent — stored verbatim for human readability and so the
28
+ * cassette can be re-hashed if the hashing format ever changes.
29
+ */
30
+ request: {
31
+ messages: Message[];
32
+ options?: ModelCallOptions;
33
+ }; /** A non-streaming reply, recorded from `complete()`. */
34
+ response?: ModelResponse; /** A streaming reply, captured from `stream()` as the ordered chunk list. */
35
+ chunks?: ModelStreamChunk[]; /** A thrown provider error, replayed by re-throwing a reconstructed `Error`. */
36
+ error?: {
37
+ name: string;
38
+ message: string;
39
+ };
40
+ };
41
+ /**
42
+ * On-disk cassette format: the inner model identity plus every recorded
43
+ * {@link CassetteEntry}. Serialized as JSON to {@link VcrOptions.path}.
44
+ */
45
+ type Cassette = {
46
+ /** Cassette schema version. Bump only on a breaking format change. */version: 1; /** Inner `model.name` at record time — informational. */
47
+ model: string; /** Inner `model.provider` at record time — informational. */
48
+ provider: string; /** Recorded request → response pairs, in capture order. */
49
+ entries: CassetteEntry[];
50
+ };
51
+ /**
52
+ * Options for {@link vcr}.
53
+ */
54
+ type VcrOptions = {
55
+ /** Cassette file path (JSON). Read on construct, written on `save()`. */path: string; /** Record/replay behavior. Default `"auto"`. */
56
+ mode?: VcrMode;
57
+ /**
58
+ * Fields of `ModelCallOptions` to include in the request hash. Default
59
+ * `["temperature","maxTokens","responseSchema","tools","reasoning"]`.
60
+ * `signal` and unknown provider keys are always excluded so an
61
+ * otherwise-identical logical call still matches. `tools` are hashed by
62
+ * name + description + input-schema shape, not object identity.
63
+ */
64
+ hashOptions?: string[];
65
+ /**
66
+ * How the request BODY is persisted (S2). Replay matching is by the
67
+ * recomputed hash, never the stored body — so privacy modes don't break
68
+ * replay. Default `"verbatim"`.
69
+ *
70
+ * - `"verbatim"` — store the full request (prompts, tool args, options)
71
+ * for human-readable diffs and re-hashing. Records a loud one-time
72
+ * warning (outside tests) since the body may carry PII / secrets.
73
+ * - `"redacted"` — run the request through {@link redactRequest}, or —
74
+ * without one — the key-based `redact()` (strips structured secrets
75
+ * like API keys in options; free-text prompts need a custom hook).
76
+ * - `"hash-only"` — drop the request body entirely; keep only the hash
77
+ * and the outcome. Maximum privacy; loses human readability and the
78
+ * ability to re-hash.
79
+ */
80
+ recordRequest?: "verbatim" | "redacted" | "hash-only";
81
+ /**
82
+ * Custom request redactor for `recordRequest: "redacted"`. Receives the
83
+ * `{ messages, options }` about to be stored; return a sanitized copy.
84
+ * Overrides the default key-based `redact()`.
85
+ */
86
+ redactRequest?: (request: {
87
+ messages: Message[];
88
+ options?: ModelCallOptions;
89
+ }) => {
90
+ messages: Message[];
91
+ options?: ModelCallOptions;
92
+ };
93
+ /**
94
+ * Optional redactor for the recorded RESPONSE. Off by default — redacting
95
+ * the response changes what replay returns, so opt in only when a secret
96
+ * leaks into model output (e.g. an echoed token).
97
+ */
98
+ redactResponse?: (response: ModelResponse) => ModelResponse; /** Optional redactor for a recorded error's `{ name, message }`. */
99
+ redactError?: (error: {
100
+ name: string;
101
+ message: string;
102
+ }) => {
103
+ name: string;
104
+ message: string;
105
+ };
106
+ };
107
+ /**
108
+ * A `ModelContract` decorator returned by {@link vcr}. Delegates
109
+ * `name`/`provider`/`capabilities`/`pricing` to the inner model and
110
+ * intercepts only `complete()`/`stream()`.
111
+ */
112
+ type VcrModel = ModelContract & {
113
+ /** Flush newly recorded entries to `path`. No-op in pure replay. */save(): Promise<void>; /** Recorded/loaded entries, exposed for assertions. */
114
+ readonly cassette: Cassette;
115
+ };
116
+ //#endregion
117
+ export { Cassette, CassetteEntry, VcrMode, VcrModel, VcrOptions };
118
+ //# sourceMappingURL=vcr.type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vcr.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/vcr/vcr.type.ts"],"mappings":";;;;;;AAmBA;;;;AAAmB;AASnB;;;;KATY,OAAA;;;;;;;;KASA,aAAA;EASR,yEAPF,WAAA;EAUA;;;;EALA,OAAA;IACE,QAAA,EAAU,OAAA;IACV,OAAA,GAAU,gBAAA;EAAA,GASH;EANT,QAAA,GAAW,aAAA,EAcO;EAZlB,MAAA,GAAS,gBAAA,IAoBa;EAlBtB,KAAA;IACE,IAAA;IACA,OAAA;EAAA;AAAA;;AAgBoB;AAMxB;;KAdY,QAAA;EAkBH,sEAhBP,OAAA,KAgDY;EA9CZ,KAAA,UA+CuC;EA7CvC,QAAA,UAmD8C;EAjD9C,OAAA,EAAS,aAAa;AAAA;;;;KAMZ,UAAA;EA4BV,yEA1BA,IAAA,UAiCE;EA/BF,IAAA,GAAO,OAAA;EAgCL;;;;;;;EAxBF,WAAA;EA+B4B;;;;;;;;;;AAInB;AASX;;;;EA5BE,aAAA;EAgCmB;;;;;EA1BnB,aAAA,IAAiB,OAAA;IACf,QAAA,EAAU,OAAA;IACV,OAAA,GAAU,gBAAA;EAAA;IACJ,QAAA,EAAU,OAAA;IAAW,OAAA,GAAU,gBAAA;EAAA;;;;;;EAMvC,cAAA,IAAkB,QAAA,EAAU,aAAA,KAAkB,aAAA;EAE9C,WAAA,IAAe,KAAA;IAAS,IAAA;IAAc,OAAA;EAAA;IACpC,IAAA;IACA,OAAA;EAAA;AAAA;;;;;;KASQ,QAAA,GAAW,aAAA;sEAErB,IAAA,IAAQ,OAAA;WAEC,QAAA,EAAU,QAAA;AAAA"}