@warlock.js/ai 4.4.0 → 4.6.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 (648) hide show
  1. package/CHANGELOG.md +63 -52
  2. package/cjs/index.cjs +240 -14147
  3. package/cjs/src-Bmajk4Qg.cjs +30 -0
  4. package/cjs/src-OZyDYHxm.cjs +25089 -0
  5. package/cjs/src-OZyDYHxm.cjs.map +1 -0
  6. package/esm/agent/agent-config.type.d.mts +119 -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 +328 -36
  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/agent/signature.mjs +57 -0
  21. package/esm/agent/signature.mjs.map +1 -0
  22. package/esm/agent/snapshot.mjs +101 -0
  23. package/esm/agent/snapshot.mjs.map +1 -0
  24. package/esm/ai-openai/src/embedder.mjs +4 -0
  25. package/esm/ai-openai/src/image.mjs +5 -0
  26. package/esm/ai-openai/src/index.mjs +7 -0
  27. package/esm/ai-openai/src/model.mjs +5 -0
  28. package/esm/ai-openai/src/sdk.mjs +9 -0
  29. package/esm/ai-openai/src/speech.mjs +5 -0
  30. package/esm/ai-openai/src/transcription.mjs +6 -0
  31. package/esm/ai-openai/src/utils/index.mjs +5 -0
  32. package/esm/ai-openai/src/utils/to-openai-messages.mjs +3 -0
  33. package/esm/ai-openai/src/utils/to-openai-tools.mjs +3 -0
  34. package/esm/ai-openai/src/utils/wrap-openai-error.mjs +4 -0
  35. package/esm/ai.d.mts +164 -53
  36. package/esm/ai.d.mts.map +1 -1
  37. package/esm/ai.mjs +79 -8
  38. package/esm/ai.mjs.map +1 -1
  39. package/esm/batch/batch.d.mts.map +1 -1
  40. package/esm/batch/batch.mjs +21 -1
  41. package/esm/batch/batch.mjs.map +1 -1
  42. package/esm/batch/batch.type.d.mts +11 -4
  43. package/esm/batch/batch.type.d.mts.map +1 -1
  44. package/esm/config.d.mts +39 -3
  45. package/esm/config.d.mts.map +1 -1
  46. package/esm/config.mjs +26 -2
  47. package/esm/config.mjs.map +1 -1
  48. package/esm/contracts/agent/agent-options.type.d.mts +33 -5
  49. package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
  50. package/esm/contracts/agent/agent-snapshot.type.d.mts +90 -0
  51. package/esm/contracts/agent/agent-snapshot.type.d.mts.map +1 -0
  52. package/esm/contracts/agent/agent.contract.d.mts +29 -1
  53. package/esm/contracts/agent/agent.contract.d.mts.map +1 -1
  54. package/esm/contracts/agent/eval.type.d.mts +43 -2
  55. package/esm/contracts/agent/eval.type.d.mts.map +1 -1
  56. package/esm/contracts/agent/index.d.mts +2 -1
  57. package/esm/contracts/attachment-policy.type.d.mts +51 -0
  58. package/esm/contracts/attachment-policy.type.d.mts.map +1 -0
  59. package/esm/contracts/attachment.type.d.mts +16 -7
  60. package/esm/contracts/attachment.type.d.mts.map +1 -1
  61. package/esm/contracts/content-part.type.d.mts +14 -5
  62. package/esm/contracts/content-part.type.d.mts.map +1 -1
  63. package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -1
  64. package/esm/contracts/image-model.contract.d.mts +156 -0
  65. package/esm/contracts/image-model.contract.d.mts.map +1 -0
  66. package/esm/contracts/index.d.mts +13 -6
  67. package/esm/contracts/model.contract.d.mts +11 -8
  68. package/esm/contracts/model.contract.d.mts.map +1 -1
  69. package/esm/contracts/orchestrator/index.d.mts +1 -0
  70. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +31 -2
  71. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
  72. package/esm/contracts/orchestrator/session-lock.contract.d.mts +47 -0
  73. package/esm/contracts/orchestrator/session-lock.contract.d.mts.map +1 -0
  74. package/esm/contracts/planner/index.d.mts +3 -2
  75. package/esm/contracts/planner/planner-config.type.d.mts +61 -0
  76. package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
  77. package/esm/contracts/planner/planner-execute-options.type.d.mts +60 -1
  78. package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
  79. package/esm/contracts/planner/planner-result.type.d.mts +8 -0
  80. package/esm/contracts/planner/planner-result.type.d.mts.map +1 -1
  81. package/esm/contracts/planner/planner-snapshot.type.d.mts +77 -0
  82. package/esm/contracts/planner/planner-snapshot.type.d.mts.map +1 -0
  83. package/esm/contracts/planner/planner.contract.d.mts +21 -1
  84. package/esm/contracts/planner/planner.contract.d.mts.map +1 -1
  85. package/esm/contracts/result/agent-result.type.d.mts +6 -4
  86. package/esm/contracts/result/agent-result.type.d.mts.map +1 -1
  87. package/esm/contracts/result/base-report.type.d.mts +21 -3
  88. package/esm/contracts/result/base-report.type.d.mts.map +1 -1
  89. package/esm/contracts/result/base-report.type.mjs.map +1 -1
  90. package/esm/contracts/result/execution-report.type.d.mts +53 -1
  91. package/esm/contracts/result/execution-report.type.d.mts.map +1 -1
  92. package/esm/contracts/result/supervisor-result.type.d.mts +14 -2
  93. package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -1
  94. package/esm/contracts/result/workflow-result.type.d.mts +2 -1
  95. package/esm/contracts/result/workflow-result.type.d.mts.map +1 -1
  96. package/esm/contracts/sdk-adapter.contract.d.mts +37 -0
  97. package/esm/contracts/sdk-adapter.contract.d.mts.map +1 -1
  98. package/esm/contracts/speech-model.contract.d.mts +97 -0
  99. package/esm/contracts/speech-model.contract.d.mts.map +1 -0
  100. package/esm/contracts/supervisor/supervisor-config.type.d.mts +29 -0
  101. package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
  102. package/esm/contracts/system-prompt.contract.d.mts +103 -1
  103. package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
  104. package/esm/contracts/team/index.d.mts +1 -0
  105. package/esm/contracts/team/team-config.type.d.mts +127 -0
  106. package/esm/contracts/team/team-config.type.d.mts.map +1 -0
  107. package/esm/contracts/tool.contract.d.mts +4 -2
  108. package/esm/contracts/tool.contract.d.mts.map +1 -1
  109. package/esm/contracts/transcription-model.contract.d.mts +101 -0
  110. package/esm/contracts/transcription-model.contract.d.mts.map +1 -0
  111. package/esm/contracts/workflow/step.contract.d.mts +30 -5
  112. package/esm/contracts/workflow/step.contract.d.mts.map +1 -1
  113. package/esm/contracts/workflow/workflow.contract.d.mts +16 -0
  114. package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
  115. package/esm/errors/agent-drift-error.d.mts +32 -0
  116. package/esm/errors/agent-drift-error.d.mts.map +1 -0
  117. package/esm/errors/agent-drift-error.mjs +31 -0
  118. package/esm/errors/agent-drift-error.mjs.map +1 -0
  119. package/esm/errors/error-code.type.d.mts +1 -1
  120. package/esm/errors/index.d.mts +3 -0
  121. package/esm/errors/index.mjs +3 -0
  122. package/esm/errors/outbound-policy-error.d.mts +27 -0
  123. package/esm/errors/outbound-policy-error.d.mts.map +1 -0
  124. package/esm/errors/outbound-policy-error.mjs +32 -0
  125. package/esm/errors/outbound-policy-error.mjs.map +1 -0
  126. package/esm/errors/planner-drift-error.d.mts +34 -0
  127. package/esm/errors/planner-drift-error.d.mts.map +1 -0
  128. package/esm/errors/planner-drift-error.mjs +33 -0
  129. package/esm/errors/planner-drift-error.mjs.map +1 -0
  130. package/esm/eval/dataset.d.mts +28 -0
  131. package/esm/eval/dataset.d.mts.map +1 -0
  132. package/esm/eval/dataset.mjs +112 -0
  133. package/esm/eval/dataset.mjs.map +1 -0
  134. package/esm/eval/dataset.type.d.mts +53 -0
  135. package/esm/eval/dataset.type.d.mts.map +1 -0
  136. package/esm/eval/eval-runner.d.mts.map +1 -1
  137. package/esm/eval/eval-runner.mjs +16 -2
  138. package/esm/eval/eval-runner.mjs.map +1 -1
  139. package/esm/eval/index.d.mts +20 -1
  140. package/esm/eval/index.d.mts.map +1 -1
  141. package/esm/eval/index.mjs +20 -2
  142. package/esm/eval/index.mjs.map +1 -1
  143. package/esm/eval/regression.d.mts +30 -0
  144. package/esm/eval/regression.d.mts.map +1 -0
  145. package/esm/eval/regression.mjs +51 -0
  146. package/esm/eval/regression.mjs.map +1 -0
  147. package/esm/eval/report-json.d.mts +30 -0
  148. package/esm/eval/report-json.d.mts.map +1 -0
  149. package/esm/eval/report-json.mjs +33 -0
  150. package/esm/eval/report-json.mjs.map +1 -0
  151. package/esm/eval/report-junit.d.mts +22 -0
  152. package/esm/eval/report-junit.d.mts.map +1 -0
  153. package/esm/eval/report-junit.mjs +60 -0
  154. package/esm/eval/report-junit.mjs.map +1 -0
  155. package/esm/guard/contracts/guard-options.type.d.mts +165 -0
  156. package/esm/guard/contracts/guard-options.type.d.mts.map +1 -0
  157. package/esm/guard/contracts/guardrail.contract.d.mts +78 -0
  158. package/esm/guard/contracts/guardrail.contract.d.mts.map +1 -0
  159. package/esm/guard/contracts/index.d.mts +4 -0
  160. package/esm/guard/contracts/openai-client.contract.d.mts +51 -0
  161. package/esm/guard/contracts/openai-client.contract.d.mts.map +1 -0
  162. package/esm/guard/contracts/verdict.type.d.mts +76 -0
  163. package/esm/guard/contracts/verdict.type.d.mts.map +1 -0
  164. package/esm/guard/detectors/index.d.mts +4 -0
  165. package/esm/guard/detectors/index.mjs +6 -0
  166. package/esm/guard/detectors/injection.d.mts +34 -0
  167. package/esm/guard/detectors/injection.d.mts.map +1 -0
  168. package/esm/guard/detectors/injection.mjs +254 -0
  169. package/esm/guard/detectors/injection.mjs.map +1 -0
  170. package/esm/guard/detectors/moderation.d.mts +32 -0
  171. package/esm/guard/detectors/moderation.d.mts.map +1 -0
  172. package/esm/guard/detectors/moderation.mjs +134 -0
  173. package/esm/guard/detectors/moderation.mjs.map +1 -0
  174. package/esm/guard/detectors/pii.d.mts +41 -0
  175. package/esm/guard/detectors/pii.d.mts.map +1 -0
  176. package/esm/guard/detectors/pii.mjs +199 -0
  177. package/esm/guard/detectors/pii.mjs.map +1 -0
  178. package/esm/guard/detectors/topic.d.mts +29 -0
  179. package/esm/guard/detectors/topic.d.mts.map +1 -0
  180. package/esm/guard/detectors/topic.mjs +99 -0
  181. package/esm/guard/detectors/topic.mjs.map +1 -0
  182. package/esm/guard/errors.d.mts +12 -0
  183. package/esm/guard/errors.d.mts.map +1 -0
  184. package/esm/guard/errors.mjs +18 -0
  185. package/esm/guard/errors.mjs.map +1 -0
  186. package/esm/guard/guard.d.mts +67 -0
  187. package/esm/guard/guard.d.mts.map +1 -0
  188. package/esm/guard/guard.mjs +209 -0
  189. package/esm/guard/guard.mjs.map +1 -0
  190. package/esm/guard/guardrail.d.mts +39 -0
  191. package/esm/guard/guardrail.d.mts.map +1 -0
  192. package/esm/guard/guardrail.mjs +22 -0
  193. package/esm/guard/guardrail.mjs.map +1 -0
  194. package/esm/human/contracts/approval.type.d.mts +154 -0
  195. package/esm/human/contracts/approval.type.d.mts.map +1 -0
  196. package/esm/human/contracts/human-approval.type.d.mts +38 -0
  197. package/esm/human/contracts/human-approval.type.d.mts.map +1 -0
  198. package/esm/human/contracts/index.d.mts +4 -0
  199. package/esm/human/contracts/interrupt-store.contract.d.mts +112 -0
  200. package/esm/human/contracts/interrupt-store.contract.d.mts.map +1 -0
  201. package/esm/human/contracts/resume.type.d.mts +77 -0
  202. package/esm/human/contracts/resume.type.d.mts.map +1 -0
  203. package/esm/human/errors.d.mts +84 -0
  204. package/esm/human/errors.d.mts.map +1 -0
  205. package/esm/human/errors.mjs +60 -0
  206. package/esm/human/errors.mjs.map +1 -0
  207. package/esm/human/human-approval.d.mts +57 -0
  208. package/esm/human/human-approval.d.mts.map +1 -0
  209. package/esm/human/human-approval.mjs +170 -0
  210. package/esm/human/human-approval.mjs.map +1 -0
  211. package/esm/human/policy.d.mts +55 -0
  212. package/esm/human/policy.d.mts.map +1 -0
  213. package/esm/human/policy.mjs +67 -0
  214. package/esm/human/policy.mjs.map +1 -0
  215. package/esm/human/register.mjs +37 -0
  216. package/esm/human/register.mjs.map +1 -0
  217. package/esm/human/resume-seed.mjs +53 -0
  218. package/esm/human/resume-seed.mjs.map +1 -0
  219. package/esm/human/resume.d.mts +54 -0
  220. package/esm/human/resume.d.mts.map +1 -0
  221. package/esm/human/resume.mjs +121 -0
  222. package/esm/human/resume.mjs.map +1 -0
  223. package/esm/human/stores/index.mjs +5 -0
  224. package/esm/human/stores/memory.d.mts +30 -0
  225. package/esm/human/stores/memory.d.mts.map +1 -0
  226. package/esm/human/stores/memory.mjs +91 -0
  227. package/esm/human/stores/memory.mjs.map +1 -0
  228. package/esm/human/stores/pg.d.mts +59 -0
  229. package/esm/human/stores/pg.d.mts.map +1 -0
  230. package/esm/human/stores/pg.mjs +220 -0
  231. package/esm/human/stores/pg.mjs.map +1 -0
  232. package/esm/human/stores/redis.d.mts +56 -0
  233. package/esm/human/stores/redis.d.mts.map +1 -0
  234. package/esm/human/stores/redis.mjs +201 -0
  235. package/esm/human/stores/redis.mjs.map +1 -0
  236. package/esm/image/image-cost.d.mts +32 -0
  237. package/esm/image/image-cost.d.mts.map +1 -0
  238. package/esm/image/image-cost.mjs +55 -0
  239. package/esm/image/image-cost.mjs.map +1 -0
  240. package/esm/image/image.d.mts +92 -0
  241. package/esm/image/image.d.mts.map +1 -0
  242. package/esm/image/image.mjs +113 -0
  243. package/esm/image/image.mjs.map +1 -0
  244. package/esm/image/index.mjs +4 -0
  245. package/esm/index.d.mts +137 -24
  246. package/esm/index.mjs +101 -11
  247. package/esm/middleware/builtins/budget.mjs +6 -2
  248. package/esm/middleware/builtins/budget.mjs.map +1 -1
  249. package/esm/middleware/utils/extract-user-text.d.mts +8 -1
  250. package/esm/middleware/utils/extract-user-text.d.mts.map +1 -1
  251. package/esm/middleware/utils/extract-user-text.mjs +8 -1
  252. package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
  253. package/esm/mock/index.d.mts +3 -0
  254. package/esm/mock/index.mjs +3 -0
  255. package/esm/mock/mock-config.type.d.mts +22 -0
  256. package/esm/mock/mock-config.type.d.mts.map +1 -1
  257. package/esm/mock/mock-image-model.d.mts +41 -0
  258. package/esm/mock/mock-image-model.d.mts.map +1 -0
  259. package/esm/mock/mock-image-model.mjs +52 -0
  260. package/esm/mock/mock-image-model.mjs.map +1 -0
  261. package/esm/mock/mock-sdk.d.mts +7 -1
  262. package/esm/mock/mock-sdk.d.mts.map +1 -1
  263. package/esm/mock/mock-sdk.mjs +27 -0
  264. package/esm/mock/mock-sdk.mjs.map +1 -1
  265. package/esm/mock/mock-speech-model.d.mts +31 -0
  266. package/esm/mock/mock-speech-model.d.mts.map +1 -0
  267. package/esm/mock/mock-speech-model.mjs +39 -0
  268. package/esm/mock/mock-speech-model.mjs.map +1 -0
  269. package/esm/mock/mock-transcription-model.d.mts +32 -0
  270. package/esm/mock/mock-transcription-model.d.mts.map +1 -0
  271. package/esm/mock/mock-transcription-model.mjs +36 -0
  272. package/esm/mock/mock-transcription-model.mjs.map +1 -0
  273. package/esm/object-stream/index.d.mts +2 -0
  274. package/esm/object-stream/index.mjs +4 -0
  275. package/esm/object-stream/parse-partial-json.d.mts +22 -0
  276. package/esm/object-stream/parse-partial-json.d.mts.map +1 -0
  277. package/esm/object-stream/parse-partial-json.mjs +78 -0
  278. package/esm/object-stream/parse-partial-json.mjs.map +1 -0
  279. package/esm/object-stream/stream-object.d.mts +68 -0
  280. package/esm/object-stream/stream-object.d.mts.map +1 -0
  281. package/esm/object-stream/stream-object.mjs +104 -0
  282. package/esm/object-stream/stream-object.mjs.map +1 -0
  283. package/esm/observe/index.mjs +4 -0
  284. package/esm/observe/observer-registry.d.mts +30 -0
  285. package/esm/observe/observer-registry.d.mts.map +1 -0
  286. package/esm/observe/observer-registry.mjs +51 -0
  287. package/esm/observe/observer-registry.mjs.map +1 -0
  288. package/esm/observe/observer.contract.d.mts +40 -0
  289. package/esm/observe/observer.contract.d.mts.map +1 -0
  290. package/esm/observe/resolve-observers.d.mts +40 -0
  291. package/esm/observe/resolve-observers.d.mts.map +1 -0
  292. package/esm/observe/resolve-observers.mjs +73 -0
  293. package/esm/observe/resolve-observers.mjs.map +1 -0
  294. package/esm/orchestrator/execution.d.mts.map +1 -1
  295. package/esm/orchestrator/execution.mjs +5 -2
  296. package/esm/orchestrator/execution.mjs.map +1 -1
  297. package/esm/orchestrator/index.d.mts +1 -0
  298. package/esm/orchestrator/index.mjs +1 -0
  299. package/esm/orchestrator/orchestrator.d.mts.map +1 -1
  300. package/esm/orchestrator/orchestrator.mjs +39 -6
  301. package/esm/orchestrator/orchestrator.mjs.map +1 -1
  302. package/esm/orchestrator/session-lock.d.mts +25 -0
  303. package/esm/orchestrator/session-lock.d.mts.map +1 -0
  304. package/esm/orchestrator/session-lock.mjs +83 -0
  305. package/esm/orchestrator/session-lock.mjs.map +1 -0
  306. package/esm/planner/dag-scheduler.mjs +97 -0
  307. package/esm/planner/dag-scheduler.mjs.map +1 -0
  308. package/esm/planner/plan-prompt.d.mts +1 -1
  309. package/esm/planner/plan-prompt.d.mts.map +1 -1
  310. package/esm/planner/plan-prompt.mjs +2 -1
  311. package/esm/planner/plan-prompt.mjs.map +1 -1
  312. package/esm/planner/planner-run.d.mts +8 -0
  313. package/esm/planner/planner-run.d.mts.map +1 -1
  314. package/esm/planner/planner-run.mjs +457 -31
  315. package/esm/planner/planner-run.mjs.map +1 -1
  316. package/esm/planner/planner.d.mts.map +1 -1
  317. package/esm/planner/planner.mjs +26 -2
  318. package/esm/planner/planner.mjs.map +1 -1
  319. package/esm/planner/snapshot.mjs +95 -0
  320. package/esm/planner/snapshot.mjs.map +1 -0
  321. package/esm/prompt/errors.d.mts +57 -0
  322. package/esm/prompt/errors.d.mts.map +1 -0
  323. package/esm/prompt/errors.mjs +73 -0
  324. package/esm/prompt/errors.mjs.map +1 -0
  325. package/esm/prompt/index.d.mts +3 -0
  326. package/esm/prompt/index.mjs +4 -0
  327. package/esm/prompt/prompt-langfuse-sync.mjs +104 -0
  328. package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -0
  329. package/esm/prompt/prompt-langfuse-sync.type.d.mts +32 -0
  330. package/esm/prompt/prompt-langfuse-sync.type.d.mts.map +1 -0
  331. package/esm/prompt/prompt-validate.mjs +170 -0
  332. package/esm/prompt/prompt-validate.mjs.map +1 -0
  333. package/esm/prompt/prompt.d.mts +54 -0
  334. package/esm/prompt/prompt.d.mts.map +1 -0
  335. package/esm/prompt/prompt.mjs +218 -0
  336. package/esm/prompt/prompt.mjs.map +1 -0
  337. package/esm/prompt/prompt.type.d.mts +174 -0
  338. package/esm/prompt/prompt.type.d.mts.map +1 -0
  339. package/esm/prompts/index.d.mts +3 -0
  340. package/esm/prompts/index.mjs +3 -0
  341. package/esm/prompts/prompts-manager.contract.d.mts +154 -0
  342. package/esm/prompts/prompts-manager.contract.d.mts.map +1 -0
  343. package/esm/prompts/prompts-manager.d.mts +38 -0
  344. package/esm/prompts/prompts-manager.d.mts.map +1 -0
  345. package/esm/prompts/prompts-manager.mjs +410 -0
  346. package/esm/prompts/prompts-manager.mjs.map +1 -0
  347. package/esm/prompts/prompts-manager.type.d.mts +172 -0
  348. package/esm/prompts/prompts-manager.type.d.mts.map +1 -0
  349. package/esm/prompts/prompts-validate.mjs +200 -0
  350. package/esm/prompts/prompts-validate.mjs.map +1 -0
  351. package/esm/rag/as-tool.mjs +48 -0
  352. package/esm/rag/as-tool.mjs.map +1 -0
  353. package/esm/rag/chunk/chunk.d.mts +24 -0
  354. package/esm/rag/chunk/chunk.d.mts.map +1 -0
  355. package/esm/rag/chunk/chunk.mjs +44 -0
  356. package/esm/rag/chunk/chunk.mjs.map +1 -0
  357. package/esm/rag/chunk/fixed.mjs +32 -0
  358. package/esm/rag/chunk/fixed.mjs.map +1 -0
  359. package/esm/rag/chunk/markdown.mjs +75 -0
  360. package/esm/rag/chunk/markdown.mjs.map +1 -0
  361. package/esm/rag/chunk/recursive.mjs +132 -0
  362. package/esm/rag/chunk/recursive.mjs.map +1 -0
  363. package/esm/rag/chunk/sentence.mjs +73 -0
  364. package/esm/rag/chunk/sentence.mjs.map +1 -0
  365. package/esm/rag/contracts/chunk-options.type.d.mts +35 -0
  366. package/esm/rag/contracts/chunk-options.type.d.mts.map +1 -0
  367. package/esm/rag/contracts/citation.type.d.mts +35 -0
  368. package/esm/rag/contracts/citation.type.d.mts.map +1 -0
  369. package/esm/rag/contracts/index.d.mts +4 -0
  370. package/esm/rag/contracts/rag-config.type.d.mts +68 -0
  371. package/esm/rag/contracts/rag-config.type.d.mts.map +1 -0
  372. package/esm/rag/contracts/rag-document.type.d.mts +21 -0
  373. package/esm/rag/contracts/rag-document.type.d.mts.map +1 -0
  374. package/esm/rag/hybrid/bm25.d.mts +23 -0
  375. package/esm/rag/hybrid/bm25.d.mts.map +1 -0
  376. package/esm/rag/hybrid/bm25.mjs +51 -0
  377. package/esm/rag/hybrid/bm25.mjs.map +1 -0
  378. package/esm/rag/hybrid/hybrid-rank.d.mts +33 -0
  379. package/esm/rag/hybrid/hybrid-rank.d.mts.map +1 -0
  380. package/esm/rag/hybrid/hybrid-rank.mjs +29 -0
  381. package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -0
  382. package/esm/rag/hybrid/rrf.d.mts +25 -0
  383. package/esm/rag/hybrid/rrf.d.mts.map +1 -0
  384. package/esm/rag/hybrid/rrf.mjs +30 -0
  385. package/esm/rag/hybrid/rrf.mjs.map +1 -0
  386. package/esm/rag/index.d.mts +22 -0
  387. package/esm/rag/index.mjs +18 -0
  388. package/esm/rag/loaders/errors.d.mts +19 -0
  389. package/esm/rag/loaders/errors.d.mts.map +1 -0
  390. package/esm/rag/loaders/errors.mjs +25 -0
  391. package/esm/rag/loaders/errors.mjs.map +1 -0
  392. package/esm/rag/loaders/index.mjs +7 -0
  393. package/esm/rag/loaders/load-html.d.mts +26 -0
  394. package/esm/rag/loaders/load-html.d.mts.map +1 -0
  395. package/esm/rag/loaders/load-html.mjs +138 -0
  396. package/esm/rag/loaders/load-html.mjs.map +1 -0
  397. package/esm/rag/loaders/load-pdf.d.mts +38 -0
  398. package/esm/rag/loaders/load-pdf.d.mts.map +1 -0
  399. package/esm/rag/loaders/load-pdf.mjs +150 -0
  400. package/esm/rag/loaders/load-pdf.mjs.map +1 -0
  401. package/esm/rag/loaders/load-text.d.mts +47 -0
  402. package/esm/rag/loaders/load-text.d.mts.map +1 -0
  403. package/esm/rag/loaders/load-text.mjs +60 -0
  404. package/esm/rag/loaders/load-text.mjs.map +1 -0
  405. package/esm/rag/loaders/load-web.d.mts +42 -0
  406. package/esm/rag/loaders/load-web.d.mts.map +1 -0
  407. package/esm/rag/loaders/load-web.mjs +89 -0
  408. package/esm/rag/loaders/load-web.mjs.map +1 -0
  409. package/esm/rag/loaders/loader.type.d.mts +89 -0
  410. package/esm/rag/loaders/loader.type.d.mts.map +1 -0
  411. package/esm/rag/rag.d.mts +38 -0
  412. package/esm/rag/rag.d.mts.map +1 -0
  413. package/esm/rag/rag.mjs +126 -0
  414. package/esm/rag/rag.mjs.map +1 -0
  415. package/esm/rag/rerank/keyword-reranker.d.mts +32 -0
  416. package/esm/rag/rerank/keyword-reranker.d.mts.map +1 -0
  417. package/esm/rag/rerank/keyword-reranker.mjs +58 -0
  418. package/esm/rag/rerank/keyword-reranker.mjs.map +1 -0
  419. package/esm/rag/rerank/llm-reranker.d.mts +36 -0
  420. package/esm/rag/rerank/llm-reranker.d.mts.map +1 -0
  421. package/esm/rag/rerank/llm-reranker.mjs +85 -0
  422. package/esm/rag/rerank/llm-reranker.mjs.map +1 -0
  423. package/esm/rag/rerank/reranker.contract.d.mts +28 -0
  424. package/esm/rag/rerank/reranker.contract.d.mts.map +1 -0
  425. package/esm/rag/retrieve.mjs +68 -0
  426. package/esm/rag/retrieve.mjs.map +1 -0
  427. package/esm/rag/store/cache-vector-store.d.mts +27 -0
  428. package/esm/rag/store/cache-vector-store.d.mts.map +1 -0
  429. package/esm/rag/store/cache-vector-store.mjs +48 -0
  430. package/esm/rag/store/cache-vector-store.mjs.map +1 -0
  431. package/esm/rag/store/pg-vector-store.d.mts +139 -0
  432. package/esm/rag/store/pg-vector-store.d.mts.map +1 -0
  433. package/esm/rag/store/pg-vector-store.mjs +328 -0
  434. package/esm/rag/store/pg-vector-store.mjs.map +1 -0
  435. package/esm/rag/store/vector-store.contract.d.mts +38 -0
  436. package/esm/rag/store/vector-store.contract.d.mts.map +1 -0
  437. package/esm/rag/transforms/multi-query.d.mts +27 -0
  438. package/esm/rag/transforms/multi-query.d.mts.map +1 -0
  439. package/esm/rag/transforms/multi-query.mjs +41 -0
  440. package/esm/rag/transforms/multi-query.mjs.map +1 -0
  441. package/esm/security/index.mjs +5 -0
  442. package/esm/security/outbound-policy.d.mts +46 -0
  443. package/esm/security/outbound-policy.d.mts.map +1 -0
  444. package/esm/security/outbound-policy.mjs +187 -0
  445. package/esm/security/outbound-policy.mjs.map +1 -0
  446. package/esm/security/outbound-policy.type.d.mts +74 -0
  447. package/esm/security/outbound-policy.type.d.mts.map +1 -0
  448. package/esm/security/private-ip.d.mts +15 -0
  449. package/esm/security/private-ip.d.mts.map +1 -0
  450. package/esm/security/private-ip.mjs +48 -0
  451. package/esm/security/private-ip.mjs.map +1 -0
  452. package/esm/security/redact.d.mts +59 -0
  453. package/esm/security/redact.d.mts.map +1 -0
  454. package/esm/security/redact.mjs +122 -0
  455. package/esm/security/redact.mjs.map +1 -0
  456. package/esm/serve/serve.d.mts +50 -0
  457. package/esm/serve/serve.d.mts.map +1 -0
  458. package/esm/serve/serve.mjs +90 -0
  459. package/esm/serve/serve.mjs.map +1 -0
  460. package/esm/serve/sse.d.mts +20 -0
  461. package/esm/serve/sse.d.mts.map +1 -0
  462. package/esm/serve/sse.mjs +25 -0
  463. package/esm/serve/sse.mjs.map +1 -0
  464. package/esm/serve/stream-to-sse.d.mts +29 -0
  465. package/esm/serve/stream-to-sse.d.mts.map +1 -0
  466. package/esm/serve/stream-to-sse.mjs +37 -0
  467. package/esm/serve/stream-to-sse.mjs.map +1 -0
  468. package/esm/skills/catalog.d.mts +49 -0
  469. package/esm/skills/catalog.d.mts.map +1 -0
  470. package/esm/skills/catalog.mjs +140 -0
  471. package/esm/skills/catalog.mjs.map +1 -0
  472. package/esm/skills/contracts/skill-record.type.d.mts +37 -0
  473. package/esm/skills/contracts/skill-record.type.d.mts.map +1 -0
  474. package/esm/skills/contracts/skills-config.type.d.mts +108 -0
  475. package/esm/skills/contracts/skills-config.type.d.mts.map +1 -0
  476. package/esm/skills/contracts/skills-store.contract.d.mts +28 -0
  477. package/esm/skills/contracts/skills-store.contract.d.mts.map +1 -0
  478. package/esm/skills/contracts/skills.contract.d.mts +43 -0
  479. package/esm/skills/contracts/skills.contract.d.mts.map +1 -0
  480. package/esm/skills/index.d.mts +16 -0
  481. package/esm/skills/index.mjs +14 -0
  482. package/esm/skills/load-skill-tool.d.mts +38 -0
  483. package/esm/skills/load-skill-tool.d.mts.map +1 -0
  484. package/esm/skills/load-skill-tool.mjs +65 -0
  485. package/esm/skills/load-skill-tool.mjs.map +1 -0
  486. package/esm/skills/review-gate.d.mts +33 -0
  487. package/esm/skills/review-gate.d.mts.map +1 -0
  488. package/esm/skills/review-gate.mjs +60 -0
  489. package/esm/skills/review-gate.mjs.map +1 -0
  490. package/esm/skills/save-skill-tool.d.mts +39 -0
  491. package/esm/skills/save-skill-tool.d.mts.map +1 -0
  492. package/esm/skills/save-skill-tool.mjs +65 -0
  493. package/esm/skills/save-skill-tool.mjs.map +1 -0
  494. package/esm/skills/skills.d.mts +33 -0
  495. package/esm/skills/skills.d.mts.map +1 -0
  496. package/esm/skills/skills.mjs +109 -0
  497. package/esm/skills/skills.mjs.map +1 -0
  498. package/esm/skills/sources/directory-source.d.mts +19 -0
  499. package/esm/skills/sources/directory-source.d.mts.map +1 -0
  500. package/esm/skills/sources/directory-source.mjs +108 -0
  501. package/esm/skills/sources/directory-source.mjs.map +1 -0
  502. package/esm/skills/sources/index.d.mts +18 -0
  503. package/esm/skills/sources/index.d.mts.map +1 -0
  504. package/esm/skills/sources/index.mjs +27 -0
  505. package/esm/skills/sources/index.mjs.map +1 -0
  506. package/esm/skills/sources/parse-frontmatter.d.mts +27 -0
  507. package/esm/skills/sources/parse-frontmatter.d.mts.map +1 -0
  508. package/esm/skills/sources/parse-frontmatter.mjs +46 -0
  509. package/esm/skills/sources/parse-frontmatter.mjs.map +1 -0
  510. package/esm/skills/sources/store-source.d.mts +14 -0
  511. package/esm/skills/sources/store-source.d.mts.map +1 -0
  512. package/esm/skills/sources/store-source.mjs +15 -0
  513. package/esm/skills/sources/store-source.mjs.map +1 -0
  514. package/esm/skills/sources/url-source.d.mts +29 -0
  515. package/esm/skills/sources/url-source.d.mts.map +1 -0
  516. package/esm/skills/sources/url-source.mjs +117 -0
  517. package/esm/skills/sources/url-source.mjs.map +1 -0
  518. package/esm/skills/store/mock-skills-store.d.mts +57 -0
  519. package/esm/skills/store/mock-skills-store.d.mts.map +1 -0
  520. package/esm/skills/store/mock-skills-store.mjs +100 -0
  521. package/esm/skills/store/mock-skills-store.mjs.map +1 -0
  522. package/esm/skills/store/procedural-skill-store.d.mts +30 -0
  523. package/esm/skills/store/procedural-skill-store.d.mts.map +1 -0
  524. package/esm/skills/store/procedural-skill-store.mjs +125 -0
  525. package/esm/skills/store/procedural-skill-store.mjs.map +1 -0
  526. package/esm/speech/index.mjs +3 -0
  527. package/esm/speech/speech.d.mts +65 -0
  528. package/esm/speech/speech.d.mts.map +1 -0
  529. package/esm/speech/speech.mjs +123 -0
  530. package/esm/speech/speech.mjs.map +1 -0
  531. package/esm/supervisor/as-tool.mjs +2 -2
  532. package/esm/supervisor/as-tool.mjs.map +1 -1
  533. package/esm/supervisor/entries.mjs +2 -2
  534. package/esm/supervisor/entries.mjs.map +1 -1
  535. package/esm/supervisor/execution.d.mts.map +1 -1
  536. package/esm/supervisor/execution.mjs +31 -28
  537. package/esm/supervisor/execution.mjs.map +1 -1
  538. package/esm/supervisor/supervisor.d.mts.map +1 -1
  539. package/esm/supervisor/supervisor.mjs +8 -3
  540. package/esm/supervisor/supervisor.mjs.map +1 -1
  541. package/esm/system-prompt/index.d.mts +4 -0
  542. package/esm/system-prompt/system-prompt.d.mts +68 -4
  543. package/esm/system-prompt/system-prompt.d.mts.map +1 -1
  544. package/esm/system-prompt/system-prompt.mjs +89 -5
  545. package/esm/system-prompt/system-prompt.mjs.map +1 -1
  546. package/esm/team/gates.mjs +48 -0
  547. package/esm/team/gates.mjs.map +1 -0
  548. package/esm/team/index.d.mts +1 -0
  549. package/esm/team/index.mjs +3 -0
  550. package/esm/team/team.d.mts +42 -0
  551. package/esm/team/team.d.mts.map +1 -0
  552. package/esm/team/team.mjs +94 -0
  553. package/esm/team/team.mjs.map +1 -0
  554. package/esm/tool/executable-as-tool.d.mts.map +1 -1
  555. package/esm/tool/executable-as-tool.mjs +2 -2
  556. package/esm/tool/executable-as-tool.mjs.map +1 -1
  557. package/esm/tool/tool.d.mts.map +1 -1
  558. package/esm/tool/tool.mjs +2 -2
  559. package/esm/tool/tool.mjs.map +1 -1
  560. package/esm/transcribe/audio-input.d.mts +47 -0
  561. package/esm/transcribe/audio-input.d.mts.map +1 -0
  562. package/esm/transcribe/audio-input.mjs +84 -0
  563. package/esm/transcribe/audio-input.mjs.map +1 -0
  564. package/esm/transcribe/index.mjs +4 -0
  565. package/esm/transcribe/transcribe.d.mts +64 -0
  566. package/esm/transcribe/transcribe.d.mts.map +1 -0
  567. package/esm/transcribe/transcribe.mjs +128 -0
  568. package/esm/transcribe/transcribe.mjs.map +1 -0
  569. package/esm/utils/compute-cost.d.mts +17 -1
  570. package/esm/utils/compute-cost.d.mts.map +1 -1
  571. package/esm/utils/compute-cost.mjs +26 -1
  572. package/esm/utils/compute-cost.mjs.map +1 -1
  573. package/esm/utils/extract-json-lenient.d.mts +42 -0
  574. package/esm/utils/extract-json-lenient.d.mts.map +1 -0
  575. package/esm/utils/extract-json-lenient.mjs +97 -0
  576. package/esm/utils/extract-json-lenient.mjs.map +1 -0
  577. package/esm/utils/index.d.mts +4 -2
  578. package/esm/utils/index.mjs +3 -1
  579. package/esm/utils/json-schema.d.mts +1 -1
  580. package/esm/utils/prepare-attachment-part.d.mts +10 -1
  581. package/esm/utils/prepare-attachment-part.d.mts.map +1 -1
  582. package/esm/utils/prepare-attachment-part.mjs +103 -11
  583. package/esm/utils/prepare-attachment-part.mjs.map +1 -1
  584. package/esm/utils/resolve-attachment.d.mts +4 -3
  585. package/esm/utils/resolve-attachment.d.mts.map +1 -1
  586. package/esm/utils/resolve-attachment.mjs +4 -3
  587. package/esm/utils/resolve-attachment.mjs.map +1 -1
  588. package/esm/utils/run-context.d.mts +94 -0
  589. package/esm/utils/run-context.d.mts.map +1 -0
  590. package/esm/utils/run-context.mjs +98 -0
  591. package/esm/utils/run-context.mjs.map +1 -0
  592. package/esm/vcr/cassette-io.mjs +57 -0
  593. package/esm/vcr/cassette-io.mjs.map +1 -0
  594. package/esm/vcr/errors.d.mts +42 -0
  595. package/esm/vcr/errors.d.mts.map +1 -0
  596. package/esm/vcr/errors.mjs +37 -0
  597. package/esm/vcr/errors.mjs.map +1 -0
  598. package/esm/vcr/hash-request.d.mts +28 -0
  599. package/esm/vcr/hash-request.d.mts.map +1 -0
  600. package/esm/vcr/hash-request.mjs +118 -0
  601. package/esm/vcr/hash-request.mjs.map +1 -0
  602. package/esm/vcr/index.d.mts +4 -0
  603. package/esm/vcr/index.mjs +5 -0
  604. package/esm/vcr/vcr.d.mts +32 -0
  605. package/esm/vcr/vcr.d.mts.map +1 -0
  606. package/esm/vcr/vcr.mjs +248 -0
  607. package/esm/vcr/vcr.mjs.map +1 -0
  608. package/esm/vcr/vcr.type.d.mts +118 -0
  609. package/esm/vcr/vcr.type.d.mts.map +1 -0
  610. package/esm/workflow/as-tool.mjs +2 -2
  611. package/esm/workflow/as-tool.mjs.map +1 -1
  612. package/esm/workflow/engine.mjs +1 -0
  613. package/esm/workflow/engine.mjs.map +1 -1
  614. package/esm/workflow/step-runner.mjs +19 -20
  615. package/esm/workflow/step-runner.mjs.map +1 -1
  616. package/esm/workflow/workflow.d.mts.map +1 -1
  617. package/esm/workflow/workflow.mjs +8 -3
  618. package/esm/workflow/workflow.mjs.map +1 -1
  619. package/llms-full.txt +2572 -204
  620. package/llms.txt +21 -4
  621. package/package.json +7 -3
  622. package/skills/README.md +44 -4
  623. package/skills/ai-dx-helpers/SKILL.md +2 -2
  624. package/skills/approve-tool-calls/SKILL.md +134 -0
  625. package/skills/attach-ai-middleware/SKILL.md +1 -1
  626. package/skills/detect-and-redact-pii/SKILL.md +104 -0
  627. package/skills/durable-agent-runs/SKILL.md +135 -0
  628. package/skills/durable-resume/SKILL.md +128 -0
  629. package/skills/escalate-block-to-human/SKILL.md +85 -0
  630. package/skills/eval-datasets-and-ci/SKILL.md +117 -0
  631. package/skills/generate-images/SKILL.md +138 -0
  632. package/skills/generate-speech/SKILL.md +139 -0
  633. package/skills/guard-input-output/SKILL.md +117 -0
  634. package/skills/manage-prompts/SKILL.md +186 -0
  635. package/skills/observe-ai-flows/SKILL.md +94 -0
  636. package/skills/rag-loaders-and-stores/SKILL.md +164 -0
  637. package/skills/record-replay-llm/SKILL.md +92 -0
  638. package/skills/run-ai-agent/SKILL.md +26 -1
  639. package/skills/run-ai-rag/SKILL.md +139 -0
  640. package/skills/run-ai-team/SKILL.md +107 -0
  641. package/skills/run-orchestrator/SKILL.md +2 -0
  642. package/skills/run-planner/SKILL.md +73 -8
  643. package/skills/run-supervisor/SKILL.md +20 -1
  644. package/skills/transcribe-audio/SKILL.md +157 -0
  645. package/skills/use-runtime-skills/SKILL.md +106 -0
  646. package/skills/write-system-prompt/SKILL.md +30 -1
  647. package/cjs/index.cjs.map +0 -1
  648. package/esm/contracts/result/index.d.mts +0 -15
@@ -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":";;;;;;;;;;;;;;;;AAyBA,IAAM,MAAN,MAA8B;CAuB5B,AAAO,YACL,AAAiB,OACjB,SACA;EAFiB;eAfH;wBAYS;EAMvB,KAAK,OAAO,QAAQ;EACpB,KAAK,OAAO,QAAQ,QAAQ;EAC5B,KAAK,cAAc,QAAQ,eAAe;EAC1C,KAAK,gBAAgB,QAAQ,iBAAiB;EAC9C,KAAK,oBAAoB,QAAQ;EACjC,KAAK,qBAAqB,QAAQ;EAClC,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,iBAAiB,cAAc,MAAM,MAAM,MAAM,QAAQ;CAChE;;CAGA,IAAW,OAAe;EACxB,OAAO,KAAK,MAAM;CACpB;;CAGA,IAAW,WAAmB;EAC5B,OAAO,KAAK,MAAM;CACpB;;CAGA,IAAW,eAA8C;EACvD,OAAO,KAAK,MAAM;CACpB;;CAGA,IAAW,UAAoC;EAC7C,OAAO,KAAK,MAAM;CACpB;;CAGA,IAAW,WAAqB;EAC9B,OAAO,KAAK;CACd;;;;;;CAOA,MAAc,eAA8B;EAC1C,IAAI,KAAK,aACP,OAAO,KAAK;EAGd,KAAK,cACH,KAAK,SAAS,WACV,QAAQ,QAAQ,KACf,YAAY;GACX,KAAK,iBAAiB,MAAM,aAC1B,KAAK,MACL,KAAK,MAAM,MACX,KAAK,MAAM,QACb;EACF,EAAC,CAAE;EAET,OAAO,KAAK;CACd;;CAGA,AAAQ,UAAU,MAAyC;EACzD,OAAO,KAAK,eAAe,QAAQ,MAAM,UAAU,MAAM,gBAAgB,IAAI;CAC/E;;CAGA,AAAQ,mBAAmB,OAA6B;EACtD,MAAM,QAAQ,IAAI,MAAM,MAAM,OAAO,WAAW,gBAAgB;EAEhE,MAAM,OAAO,MAAM,OAAO,QAAQ;EAElC,MAAM;CACR;;;;;CAMA,MAAa,SAAS,UAAqB,SAAoD;EAC7F,MAAM,KAAK,aAAa;EAExB,MAAM,OAAO,YAAY,UAAU,SAAS,KAAK,WAAW;EAE5D,IAAI,KAAK,SAAS,UAAU;GAC1B,MAAM,QAAQ,KAAK,UAAU,IAAI;GAEjC,IAAI,OAAO;IACT,IAAI,MAAM,OACR,KAAK,mBAAmB,KAAK;IAG/B,IAAI,MAAM,UACR,OAAO,MAAM;GAEjB;GAEA,IAAI,KAAK,SAAS,UAChB,MAAM,IAAI,qBACR,8CAA8C,KAAK,MAAM,KAAK,UAAU,KAAK,KAC7E;IAAE,aAAa;IAAM,MAAM,KAAK;GAAK,CACvC;EAEJ;EAEA,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,MAAM,SAAS,UAAU,OAAO;GAE5D,KAAK,OAAO;IAAE,aAAa;IAAM,SAAS;KAAE;KAAU;IAAQ;IAAG;GAAS,CAAC;GAE3E,OAAO;EACT,SAAS,OAAO;GACd,KAAK,OAAO;IACV,aAAa;IACb,SAAS;KAAE;KAAU;IAAQ;IAC7B,OAAO;KAAE,MAAO,MAAgB;KAAM,SAAU,MAAgB;IAAQ;GAC1E,CAAC;GAED,MAAM;EACR;CACF;;;;;;;CAQA,OAAc,OACZ,UACA,SACiC;EACjC,MAAM,KAAK,aAAa;EAExB,MAAM,OAAO,YAAY,UAAU,SAAS,KAAK,WAAW;EAE5D,IAAI,KAAK,SAAS,UAAU;GAC1B,MAAM,QAAQ,KAAK,UAAU,IAAI;GAEjC,IAAI,OAAO;IACT,IAAI,MAAM,OACR,KAAK,mBAAmB,KAAK;IAG/B,IAAI,MAAM,QAAQ;KAChB,KAAK,MAAM,SAAS,MAAM,QACxB,MAAM;KAGR;IACF;GACF;GAEA,IAAI,KAAK,SAAS,UAChB,MAAM,IAAI,qBACR,8CAA8C,KAAK,MAAM,KAAK,UAAU,KAAK,KAC7E;IAAE,aAAa;IAAM,MAAM,KAAK;GAAK,CACvC;EAEJ;EAEA,MAAM,SAA6B,CAAC;EAEpC,IAAI;GACF,WAAW,MAAM,SAAS,KAAK,MAAM,OAAO,UAAU,OAAO,GAAG;IAC9D,OAAO,KAAK,KAAK;IAEjB,MAAM;GACR;EACF,SAAS,OAAO;GACd,KAAK,OAAO;IACV,aAAa;IACb,SAAS;KAAE;KAAU;IAAQ;IAC7B,OAAO;KAAE,MAAO,MAAgB;KAAM,SAAU,MAAgB;IAAQ;GAC1E,CAAC;GAED,MAAM;EACR;EAEA,KAAK,OAAO;GAAE,aAAa;GAAM,SAAS;IAAE;IAAU;GAAQ;GAAG;EAAO,CAAC;CAC3E;;;;;;CAOA,AAAQ,OAAO,OAA4B;EACzC,KAAK,eAAe,QAAQ,KAAK,KAAK,eAAe,KAAK,CAAC;EAC3D,KAAK,QAAQ;EACb,KAAK,kBAAkB;CACzB;;;;;;;CAQA,AAAQ,eAAe,OAAqC;EAC1D,MAAM,MAAqB;GACzB,aAAa,MAAM;GACnB,SAAS,MAAM;EACjB;EAEA,IAAI,KAAK,kBAAkB,aACzB,IAAI,UAAU,EAAE,UAAU,CAAC,EAAE;OACxB,IAAI,KAAK,kBAAkB,YAChC,IAAI,UAAU,KAAK,oBACf,KAAK,kBAAkB,MAAM,OAAO,IACpC,OAAO,MAAM,OAAO;EAG1B,IAAI,MAAM,UACR,IAAI,WAAW,KAAK,qBAChB,KAAK,mBAAmB,MAAM,QAAQ,IACtC,MAAM;EAEZ,IAAI,MAAM,QACR,IAAI,SAAS,MAAM;EAErB,IAAI,MAAM,OACR,IAAI,QAAQ,KAAK,kBACb,KAAK,gBAAgB,MAAM,KAAK,IAChC,MAAM;EAGZ,OAAO;CACT;;;;;;CAOA,AAAQ,oBAA0B;EAChC,IAAI,KAAK,kBAAkB,KAAK,kBAAkB,YAAY;EAC9D,IAAI,QAAQ,IAAI,UAAU,QAAQ,IAAI,aAAa,QAAQ;EAE3D,KAAK,iBAAiB;EACtB,QAAQ,KACN,6DAA6D,KAAK,KAAK,uIAEzE;CACF;;;;;CAMA,MAAa,OAAsB;EACjC,IAAI,CAAC,KAAK,OACR;EAGF,MAAM,aAAa,KAAK,MAAM,KAAK,cAAc;EACjD,KAAK,QAAQ;CACf;AACF;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,IAAI,OAAsB,SAA+B;CACvE,OAAO,IAAI,IAAI,OAAO,OAAO;AAC/B"}
@@ -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"}
@@ -32,8 +32,8 @@ function asTool(workflowInstance, options) {
32
32
  name: workflowInstance.name,
33
33
  description: options.description ?? `Invoke workflow "${workflowInstance.name}" as a tool.`,
34
34
  input: options.inputSchema,
35
- execute: async (input) => {
36
- const result = await workflowInstance.execute(input);
35
+ execute: async (input, ctx) => {
36
+ const result = await workflowInstance.execute(input, ctx?.signal ? { signal: ctx.signal } : void 0);
37
37
  if (result.error) throw result.error;
38
38
  return {
39
39
  data: result.data,
@@ -1 +1 @@
1
- {"version":3,"file":"as-tool.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/workflow/as-tool.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { WorkflowInstance } from \"../contracts/workflow/workflow.contract\";\nimport { WorkflowError } from \"../errors\";\nimport { compositeAsTool, type ToolContract } from \"../tool/tool\";\n\n/**\n * Wrap a `WorkflowInstance` as a `ToolContract` so an agent can invoke\n * it inside its tool-call loop. Closes the agent-calls-workflow\n * composition gap.\n *\n * Behavior:\n * - Tool `name` mirrors `workflow.name` — workflows without a name throw\n * `WorkflowError` at wrap time (the agent surface needs a stable id).\n * - Tool `input` is the supplied `inputSchema`; the validated value is\n * forwarded straight to `workflow.execute(input)`.\n * - On `result.error`, the workflow error is wrapped in\n * `ToolExecutionError` with `cause` set to the original\n * `WorkflowError` subclass — the agent's tool-call loop sees a\n * uniform error class regardless of which primitive failed.\n *\n * @example\n * const wf = workflow({ name: \"triage\", steps: [...] });\n * const triageTool = asTool(wf, {\n * description: \"Run the support-ticket triage flow\",\n * inputSchema: ticketSchema,\n * });\n * const a = ai.agent({ model, tools: [triageTool] });\n */\nexport function asTool<TInput, TOutput, TToolInput = TInput>(\n workflowInstance: WorkflowInstance<TInput, TOutput>,\n options: {\n description?: string;\n inputSchema: StandardSchemaV1<TToolInput>;\n },\n): ToolContract<TToolInput, TOutput> {\n if (!workflowInstance.name || typeof workflowInstance.name !== \"string\") {\n throw new WorkflowError(\n \"workflow.asTool(): workflow must have a `name` to be wrapped as a tool\",\n );\n }\n\n return compositeAsTool<TToolInput, TOutput>({\n name: workflowInstance.name,\n description: options.description ?? `Invoke workflow \"${workflowInstance.name}\" as a tool.`,\n input: options.inputSchema,\n execute: async (input) => {\n const result = await workflowInstance.execute(input as unknown as TInput);\n\n if (result.error) {\n // Throw the workflow error so the surrounding wrapper catches\n // it and produces a `ToolExecutionError` with `cause` pointing\n // back at the original `WorkflowError` subclass — keeps the\n // agent's tool-call loop seeing one uniform error class\n // regardless of which primitive failed.\n throw result.error;\n }\n\n return {\n data: result.data as TOutput,\n usage: result.usage,\n report: result.report,\n };\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,OACd,kBACA,SAImC;CACnC,IAAI,CAAC,iBAAiB,QAAQ,OAAO,iBAAiB,SAAS,UAC7D,MAAM,IAAI,cACR,wEACF;CAGF,OAAO,gBAAqC;EAC1C,MAAM,iBAAiB;EACvB,aAAa,QAAQ,eAAe,oBAAoB,iBAAiB,KAAK;EAC9E,OAAO,QAAQ;EACf,SAAS,OAAO,UAAU;GACxB,MAAM,SAAS,MAAM,iBAAiB,QAAQ,KAA0B;GAExE,IAAI,OAAO,OAMT,MAAM,OAAO;GAGf,OAAO;IACL,MAAM,OAAO;IACb,OAAO,OAAO;IACd,QAAQ,OAAO;GACjB;EACF;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"as-tool.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/workflow/as-tool.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { WorkflowInstance } from \"../contracts/workflow/workflow.contract\";\nimport { WorkflowError } from \"../errors\";\nimport { compositeAsTool, type ToolContract } from \"../tool/tool\";\n\n/**\n * Wrap a `WorkflowInstance` as a `ToolContract` so an agent can invoke\n * it inside its tool-call loop. Closes the agent-calls-workflow\n * composition gap.\n *\n * Behavior:\n * - Tool `name` mirrors `workflow.name` — workflows without a name throw\n * `WorkflowError` at wrap time (the agent surface needs a stable id).\n * - Tool `input` is the supplied `inputSchema`; the validated value is\n * forwarded straight to `workflow.execute(input)`.\n * - On `result.error`, the workflow error is wrapped in\n * `ToolExecutionError` with `cause` set to the original\n * `WorkflowError` subclass — the agent's tool-call loop sees a\n * uniform error class regardless of which primitive failed.\n *\n * @example\n * const wf = workflow({ name: \"triage\", steps: [...] });\n * const triageTool = asTool(wf, {\n * description: \"Run the support-ticket triage flow\",\n * inputSchema: ticketSchema,\n * });\n * const a = ai.agent({ model, tools: [triageTool] });\n */\nexport function asTool<TInput, TOutput, TToolInput = TInput>(\n workflowInstance: WorkflowInstance<TInput, TOutput>,\n options: {\n description?: string;\n inputSchema: StandardSchemaV1<TToolInput>;\n },\n): ToolContract<TToolInput, TOutput> {\n if (!workflowInstance.name || typeof workflowInstance.name !== \"string\") {\n throw new WorkflowError(\n \"workflow.asTool(): workflow must have a `name` to be wrapped as a tool\",\n );\n }\n\n return compositeAsTool<TToolInput, TOutput>({\n name: workflowInstance.name,\n description: options.description ?? `Invoke workflow \"${workflowInstance.name}\" as a tool.`,\n input: options.inputSchema,\n execute: async (input, ctx) => {\n // Relay the outer agent's cancellation signal so cancelling the\n // parent aborts this nested workflow run (C2).\n const result = await workflowInstance.execute(\n input as unknown as TInput,\n ctx?.signal ? { signal: ctx.signal } : undefined,\n );\n\n if (result.error) {\n // Throw the workflow error so the surrounding wrapper catches\n // it and produces a `ToolExecutionError` with `cause` pointing\n // back at the original `WorkflowError` subclass — keeps the\n // agent's tool-call loop seeing one uniform error class\n // regardless of which primitive failed.\n throw result.error;\n }\n\n return {\n data: result.data as TOutput,\n usage: result.usage,\n report: result.report,\n };\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,OACd,kBACA,SAImC;CACnC,IAAI,CAAC,iBAAiB,QAAQ,OAAO,iBAAiB,SAAS,UAC7D,MAAM,IAAI,cACR,wEACF;CAGF,OAAO,gBAAqC;EAC1C,MAAM,iBAAiB;EACvB,aAAa,QAAQ,eAAe,oBAAoB,iBAAiB,KAAK;EAC9E,OAAO,QAAQ;EACf,SAAS,OAAO,OAAO,QAAQ;GAG7B,MAAM,SAAS,MAAM,iBAAiB,QACpC,OACA,KAAK,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,MACzC;GAEA,IAAI,OAAO,OAMT,MAAM,OAAO;GAGf,OAAO;IACL,MAAM,OAAO;IACb,OAAO,OAAO;IACd,QAAQ,OAAO;GACjB;EACF;CACF,CAAC;AACH"}
@@ -258,6 +258,7 @@ async function runWorkflow(params) {
258
258
  workflowName: definition.name,
259
259
  signature,
260
260
  status,
261
+ ...error ? { error } : {},
261
262
  startedAt,
262
263
  endedAt,
263
264
  duration,
@@ -1 +1 @@
1
- {"version":3,"file":"engine.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/workflow/engine.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport type { StepSnapshot } from \"../contracts/result/step-result.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport type {\n WorkflowReport,\n WorkflowResult,\n} from \"../contracts/result/workflow-result.type\";\nimport type { StepDefinition } from \"../contracts/workflow/step.contract\";\nimport type { WorkflowContext } from \"../contracts/workflow/workflow-context.type\";\nimport type { WorkflowSnapshot } from \"../contracts/workflow/workflow-snapshot.type\";\nimport type {\n WorkflowDefinition,\n WorkflowEventHandlers,\n} from \"../contracts/workflow/workflow.contract\";\nimport {\n AIError,\n MaxStepsExceededError,\n RoutingError,\n SchemaValidationError,\n WorkflowCancelledError,\n WorkflowError,\n} from \"../errors\";\nimport { stampReportLineage } from \"../utils\";\nimport { createCancelledError } from \"./cancellation\";\nimport type { WorkflowEmitter } from \"./emitter\";\nimport { mapNextStep, nextDeclaredStep, resolveNextStep } from \"./router\";\nimport { runScopedEmitter } from \"./run-scoped-emitter\";\nimport { persistSnapshot } from \"./snapshot\";\nimport { cloneState, deepFreeze } from \"./state\";\nimport { executeStep, finalizeSnapshot, toAIError } from \"./step-runner\";\n\nexport { loadSnapshotForResume } from \"./snapshot\";\n\nconst DEFAULT_MAX_STEPS = 100;\nconst DEFAULT_LOOP_WARN = 5;\nconst LOG_MODULE_BASE = \"ai.workflow\";\n\ntype EngineParams<TOutput> = {\n definition: WorkflowDefinition<any, TOutput, any, any>;\n signature: string;\n emitter: WorkflowEmitter;\n input: unknown;\n /**\n * Request-scoped envelope, frozen and exposed as `ctx.context` to\n * every step. Never persisted in snapshots; resume callers supply\n * it fresh via `WorkflowResumeOptions.context`. Defaults to a\n * frozen empty object when caller omits it.\n */\n context?: unknown;\n runId: string;\n signal?: AbortSignal;\n executionHandlers?: WorkflowEventHandlers;\n resumeFrom?: WorkflowSnapshot;\n /**\n * Opaque session identifier propagated onto every report node this\n * run produces — including agent reports from child steps. Threaded\n * from `WorkflowRunOptions.sessionId`. Omitted leaves the field\n * undefined throughout the tree.\n */\n sessionId?: string;\n};\n\n/**\n * Main workflow driver. Walks the declared steps, handling routing,\n * cancellation, retries, parallel execution, and snapshot\n * persistence. Delegates the step lifecycle to `step-runner.ts`,\n * routing to `router.ts`, persistence to `snapshot.ts`. Never throws\n * — every failure funnels into `result.error`.\n */\nexport async function runWorkflow<TOutput>(\n params: EngineParams<TOutput>,\n): Promise<WorkflowResult<TOutput>> {\n const { definition, signature, input, runId, signal } = params;\n // Bind the factory-scoped emitter to THIS run's identity. Every\n // `emitter.emit(...)` below — and the one threaded into\n // `executeStep` — now stamps `runId` / `rootRunId` automatically.\n const emitter = runScopedEmitter(params.emitter, {\n runId,\n rootRunId: runId,\n });\n\n // Freeze the envelope once at run start. Default to `{}` so step\n // code can always read `ctx.context` without an undefined guard.\n const context = Object.freeze(params.context ?? {});\n const maxSteps = definition.maxSteps ?? DEFAULT_MAX_STEPS;\n const loopWarnAfter = definition.loopWarnAfter ?? DEFAULT_LOOP_WARN;\n\n const logger = log;\n const logModule = `${LOG_MODULE_BASE}.${definition.name}`;\n\n const stepByName = new Map<string, StepDefinition>();\n for (const s of definition.steps) stepByName.set(s.name, s);\n\n const state: Record<string, unknown> = params.resumeFrom\n ? { ...params.resumeFrom.state }\n : {};\n const steps: Record<string, StepSnapshot> = params.resumeFrom\n ? { ...params.resumeFrom.steps }\n : {};\n const enteredCount = new Map<string, number>();\n const usage: Usage = { input: 0, output: 0, total: 0 };\n\n const startedAt = params.resumeFrom?.startedAt ?? new Date().toISOString();\n const startedAtDate = new Date(startedAt);\n const runStartPerf = performance.now();\n\n let error: AIError | undefined;\n let status: \"completed\" | \"failed\" | \"cancelled\" = \"completed\";\n let cancelledAt: string | undefined;\n let lastGoto: string | null = null;\n // Captured when a step throws after retries exhaust (and `onFailure`\n // didn't recover). Used to point the final snapshot's `next` at the\n // failed step so `resume()` re-runs it after the cause is fixed.\n let failedStepName: string | undefined;\n\n const buildContext = (current?: {\n state: Record<string, unknown>;\n agentResult?: unknown;\n }): WorkflowContext => ({\n input,\n context,\n steps: steps as Readonly<Record<string, StepSnapshot>>,\n state: current?.state ?? state,\n agentResult: current?.agentResult as WorkflowContext[\"agentResult\"],\n runId,\n signal,\n startedAt: startedAtDate,\n });\n\n emitter.emit(\n \"workflow.starting\",\n { workflowName: definition.name, input },\n params.executionHandlers,\n );\n logger.info(logModule, \"starting\", \"workflow starting\", { runId });\n\n let currentName: string | null = resolveInitialStep(\n definition,\n params.resumeFrom,\n );\n let stepCount = 0;\n\n try {\n while (currentName !== null) {\n if (signal?.aborted) throw createCancelledError(signal);\n\n stepCount += 1;\n if (stepCount > maxSteps) {\n throw new MaxStepsExceededError(\n `workflow \"${definition.name}\" exceeded maxSteps=${maxSteps}`,\n { maxSteps },\n );\n }\n\n const entered = (enteredCount.get(currentName) ?? 0) + 1;\n enteredCount.set(currentName, entered);\n if (entered === loopWarnAfter) {\n emitter.emit(\n \"workflow.loop.warning\",\n { step: currentName, enteredCount: entered, lastGoto },\n params.executionHandlers,\n );\n logger.warn(logModule, \"loop.warning\", \"loop warning\", {\n step: currentName,\n enteredCount: entered,\n });\n }\n\n const step = stepByName.get(currentName);\n if (!step) {\n throw new RoutingError(\n `workflow \"${definition.name}\": unknown step \"${currentName}\"`,\n { stepName: currentName },\n );\n }\n\n const snapshot = await executeStep({\n step,\n state,\n emitter,\n executionHandlers: params.executionHandlers,\n logger,\n logModule,\n signal,\n buildContext,\n usage,\n workflowDefaultRetry: definition.defaultRetry,\n });\n\n Object.assign(state, snapshot.state);\n steps[step.name] = finalizeSnapshot(snapshot);\n // Parallel children — flat-path addressing alongside nested.\n if (snapshot.steps) {\n for (const [childName, childSnap] of Object.entries(snapshot.steps)) {\n steps[childName] = childSnap;\n }\n }\n\n // Failure path: retries exhausted. Give `onFailure` a chance to\n // recover; otherwise checkpoint at the failed step (so resume\n // re-runs it) and throw — workflow halts.\n if (snapshot.status === \"failed\" && snapshot.error) {\n const failureRoute = await resolveFailureRoute({\n step,\n definition,\n error: snapshot.error,\n ctx: buildContext({ state, agentResult: snapshot.executionResult }),\n });\n\n if (failureRoute === undefined) {\n // No recovery — persist with `next: step.name` so resume\n // re-runs this step after the user fixes the cause.\n const persistOutcome = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: step.name,\n status: \"running\",\n });\n if (!persistOutcome.ok) {\n const persistErr = toAIError(persistOutcome.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(\n logModule,\n \"persist.failed\",\n \"snapshot persist failed\",\n {\n step: step.name,\n code: persistErr.code,\n message: persistErr.message,\n },\n );\n }\n failedStepName = step.name;\n throw snapshot.error;\n }\n\n // onFailure routed — workflow continues. Checkpoint at the\n // routed target (or `null` for `end`) so resume picks up there.\n const failureNext = failureRoute === \"end\" ? null : failureRoute;\n if (failureNext !== null && !stepByName.has(failureNext)) {\n throw new RoutingError(\n `workflow \"${definition.name}\": step \"${step.name}\" onFailure routed to unknown target \"${failureNext}\"`,\n { stepName: step.name, targetName: failureNext },\n );\n }\n\n const failurePersist = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: failureNext,\n status: \"running\",\n });\n if (!failurePersist.ok) {\n const persistErr = toAIError(failurePersist.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(\n logModule,\n \"persist.failed\",\n \"snapshot persist failed\",\n {\n step: step.name,\n code: persistErr.code,\n message: persistErr.message,\n },\n );\n }\n\n if (signal?.aborted) throw createCancelledError(signal);\n\n if (failureRoute === \"end\") {\n currentName = null;\n break;\n }\n\n lastGoto = failureRoute;\n currentName = failureRoute;\n continue;\n }\n\n // Resolve next step for checkpoint accuracy BEFORE routing errors\n // bubble — so the snapshot records where resume should resume from.\n const resolved = await resolveNextStep({\n step,\n definition,\n ctx: buildContext({ state, agentResult: snapshot.executionResult }),\n });\n\n const nextName =\n resolved === \"end\"\n ? null\n : typeof resolved === \"string\"\n ? resolved\n : nextDeclaredStep(definition, step.name);\n\n // Checkpoint after every step with the resolved `next`.\n const outcome = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: nextName,\n status: \"running\",\n });\n if (!outcome.ok) {\n const persistErr = toAIError(outcome.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(logModule, \"persist.failed\", \"snapshot persist failed\", {\n step: step.name,\n code: persistErr.code,\n message: persistErr.message,\n });\n }\n\n if (signal?.aborted) throw createCancelledError(signal);\n\n if (resolved === \"end\") {\n currentName = null;\n break;\n }\n\n if (typeof resolved === \"string\") {\n if (!stepByName.has(resolved)) {\n throw new RoutingError(\n `workflow \"${definition.name}\": step \"${step.name}\" goto unknown target \"${resolved}\"`,\n { stepName: step.name, targetName: resolved },\n );\n }\n lastGoto = resolved;\n currentName = resolved;\n continue;\n }\n\n currentName = nextName;\n lastGoto = currentName;\n }\n } catch (err) {\n if (err instanceof WorkflowCancelledError) {\n status = \"cancelled\";\n cancelledAt = err.cancelledAt;\n error = err;\n } else if (err instanceof AIError) {\n status = \"failed\";\n error = err;\n } else {\n status = \"failed\";\n error = new WorkflowError(\n err instanceof Error ? err.message : String(err),\n { cause: err },\n );\n }\n }\n\n // Note: a `failed` step always throws (caught above) unless its\n // `onFailure` recovered the run. A `completed` workflow may still\n // contain `failed` step snapshots — those are the recovered cases\n // and are intentionally preserved for forensic trace.\n\n const endedAt = new Date().toISOString();\n const duration = performance.now() - runStartPerf;\n\n let data: TOutput | undefined;\n if (status === \"completed\" && definition.output) {\n try {\n const extracted = await definition.output.extract(\n buildContext({ state }),\n );\n data = (await validateWorkflowOutput(\n definition.output.schema,\n extracted,\n )) as TOutput;\n } catch (err) {\n status = \"failed\";\n error =\n err instanceof AIError\n ? err\n : new WorkflowError(\n err instanceof Error ? err.message : String(err),\n { cause: err },\n );\n }\n }\n\n // Collect child executable reports from every step that ran one.\n // Today the step runner surfaces agent reports (the only executable\n // kind steps can invoke natively via `step.agent`); custom `run`\n // callbacks that call tools/workflows/supervisors can't be observed\n // here without a richer step-runner API — that's a v1.x follow-up\n // (see backlog: \"step.run executables surface in workflow tree\").\n const children: BaseReport[] = [];\n for (const stepName in steps) {\n const snap = steps[stepName];\n if (snap.agentReport) {\n children.push(snap.agentReport);\n }\n }\n\n const report: WorkflowReport = {\n runId,\n rootRunId: runId,\n name: definition.name,\n version: definition.version,\n type: \"workflow\",\n workflowName: definition.name,\n signature,\n status,\n startedAt,\n endedAt,\n duration,\n cancelledAt,\n usage,\n children,\n steps,\n state: deepFreeze(cloneState(state)),\n };\n\n // Stamp lineage on the assembled tree exactly once. Walker rewrites\n // any inner self-roots that nested agent reports brought in (each\n // agent's `buildResult` set its own runId as root), propagates\n // sessionId, and writes `reportSchemaVersion` on the root.\n stampReportLineage(report, {\n rootRunId: runId,\n sessionId: params.sessionId,\n });\n\n // On a failed run with a captured `failedStepName`, point `next` at\n // the failed step so `resume()` re-runs it. The pre-throw checkpoint\n // already wrote this value, but the final snapshot would otherwise\n // overwrite it with `null` and force resume to fall back to the\n // first non-completed step (which is the same step in practice, but\n // less informative for tooling reading the snapshot).\n const finalNext = status === \"failed\" ? failedStepName ?? null : null;\n\n const finalOutcome = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: finalNext,\n status,\n });\n if (!finalOutcome.ok) {\n const persistErr = toAIError(finalOutcome.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(logModule, \"persist.failed\", \"final snapshot persist failed\", {\n code: persistErr.code,\n message: persistErr.message,\n });\n }\n\n const result: WorkflowResult<TOutput> = {\n type: \"workflow\",\n data,\n report,\n usage,\n error,\n };\n\n if (status === \"cancelled\") {\n emitter.emit(\n \"workflow.cancelled\",\n {\n cancelledAt: cancelledAt ?? endedAt,\n reason: (error as WorkflowCancelledError | undefined)?.reason ?? \"\",\n },\n params.executionHandlers,\n );\n logger.warn(logModule, \"cancelled\", \"workflow cancelled\", { runId });\n }\n\n if (status === \"failed\" && error) {\n emitter.emit(\"workflow.error\", { error }, params.executionHandlers);\n logger.error(logModule, \"error\", \"workflow failed\", {\n runId,\n code: error.code,\n message: error.message,\n });\n }\n\n emitter.emit(\n \"workflow.completed\",\n { result: result as WorkflowResult<unknown> },\n params.executionHandlers,\n );\n logger.info(logModule, \"completed\", \"workflow completed\", {\n runId,\n status,\n duration,\n });\n\n return result;\n}\n\n/**\n * Run a failed step's `onFailure` hook (if present) and translate its\n * result into a route. Returns `undefined` when the workflow should\n * halt with the original error; `\"end\"` for clean termination; or a\n * step name to redirect to. A throw inside `onFailure` is wrapped in\n * `RoutingError` — routing is authoritative, never retried.\n */\nasync function resolveFailureRoute<T>(params: {\n step: StepDefinition;\n definition: WorkflowDefinition<any, T, any, any>;\n error: AIError;\n ctx: WorkflowContext;\n}): Promise<\"end\" | string | undefined> {\n const { step, definition, error, ctx } = params;\n if (!step.onFailure) return undefined;\n\n let outcome;\n try {\n outcome = await step.onFailure(ctx, error);\n } catch (err) {\n throw new RoutingError(\n `workflow \"${definition.name}\": step \"${step.name}\" onFailure threw`,\n { stepName: step.name, cause: err },\n );\n }\n return mapNextStep(outcome);\n}\n\nfunction resolveInitialStep<T>(\n definition: WorkflowDefinition<any, T, any, any>,\n resumeFrom: WorkflowSnapshot | undefined,\n): string | null {\n if (!resumeFrom) return definition.steps[0]?.name ?? null;\n\n // Prefer the explicitly-recorded `next` (now populated on every\n // checkpoint). Falls back to first step whose snapshot is missing\n // or not in a terminal-success state — covers older snapshots\n // written before `next` was wired.\n if (\n resumeFrom.next &&\n definition.steps.some(s => s.name === resumeFrom.next)\n ) {\n return resumeFrom.next;\n }\n\n for (const step of definition.steps) {\n const snap = resumeFrom.steps[step.name];\n if (!snap || (snap.status !== \"completed\" && snap.status !== \"skipped\")) {\n return step.name;\n }\n }\n\n return null;\n}\n\nasync function validateWorkflowOutput(\n schema: unknown,\n value: unknown,\n): Promise<unknown> {\n if (!schema) return value;\n\n const result = await (\n schema as {\n \"~standard\": { validate: (v: unknown) => Promise<unknown> | unknown };\n }\n )[\"~standard\"].validate(value);\n\n if (\n result &&\n typeof result === \"object\" &&\n \"issues\" in result &&\n (result as { issues: unknown }).issues\n ) {\n throw new SchemaValidationError(\n \"workflow output failed schema validation\",\n {\n issues: (result as { issues: any }).issues,\n },\n );\n }\n\n return (result as { value: unknown }).value;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"engine.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/workflow/engine.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport type { StepSnapshot } from \"../contracts/result/step-result.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport type {\n WorkflowReport,\n WorkflowResult,\n} from \"../contracts/result/workflow-result.type\";\nimport type { StepDefinition } from \"../contracts/workflow/step.contract\";\nimport type { WorkflowContext } from \"../contracts/workflow/workflow-context.type\";\nimport type { WorkflowSnapshot } from \"../contracts/workflow/workflow-snapshot.type\";\nimport type {\n WorkflowDefinition,\n WorkflowEventHandlers,\n} from \"../contracts/workflow/workflow.contract\";\nimport {\n AIError,\n MaxStepsExceededError,\n RoutingError,\n SchemaValidationError,\n WorkflowCancelledError,\n WorkflowError,\n} from \"../errors\";\nimport { stampReportLineage } from \"../utils\";\nimport { createCancelledError } from \"./cancellation\";\nimport type { WorkflowEmitter } from \"./emitter\";\nimport { mapNextStep, nextDeclaredStep, resolveNextStep } from \"./router\";\nimport { runScopedEmitter } from \"./run-scoped-emitter\";\nimport { persistSnapshot } from \"./snapshot\";\nimport { cloneState, deepFreeze } from \"./state\";\nimport { executeStep, finalizeSnapshot, toAIError } from \"./step-runner\";\n\nexport { loadSnapshotForResume } from \"./snapshot\";\n\nconst DEFAULT_MAX_STEPS = 100;\nconst DEFAULT_LOOP_WARN = 5;\nconst LOG_MODULE_BASE = \"ai.workflow\";\n\ntype EngineParams<TOutput> = {\n definition: WorkflowDefinition<any, TOutput, any, any>;\n signature: string;\n emitter: WorkflowEmitter;\n input: unknown;\n /**\n * Request-scoped envelope, frozen and exposed as `ctx.context` to\n * every step. Never persisted in snapshots; resume callers supply\n * it fresh via `WorkflowResumeOptions.context`. Defaults to a\n * frozen empty object when caller omits it.\n */\n context?: unknown;\n runId: string;\n signal?: AbortSignal;\n executionHandlers?: WorkflowEventHandlers;\n resumeFrom?: WorkflowSnapshot;\n /**\n * Opaque session identifier propagated onto every report node this\n * run produces — including agent reports from child steps. Threaded\n * from `WorkflowRunOptions.sessionId`. Omitted leaves the field\n * undefined throughout the tree.\n */\n sessionId?: string;\n};\n\n/**\n * Main workflow driver. Walks the declared steps, handling routing,\n * cancellation, retries, parallel execution, and snapshot\n * persistence. Delegates the step lifecycle to `step-runner.ts`,\n * routing to `router.ts`, persistence to `snapshot.ts`. Never throws\n * — every failure funnels into `result.error`.\n */\nexport async function runWorkflow<TOutput>(\n params: EngineParams<TOutput>,\n): Promise<WorkflowResult<TOutput>> {\n const { definition, signature, input, runId, signal } = params;\n // Bind the factory-scoped emitter to THIS run's identity. Every\n // `emitter.emit(...)` below — and the one threaded into\n // `executeStep` — now stamps `runId` / `rootRunId` automatically.\n const emitter = runScopedEmitter(params.emitter, {\n runId,\n rootRunId: runId,\n });\n\n // Freeze the envelope once at run start. Default to `{}` so step\n // code can always read `ctx.context` without an undefined guard.\n const context = Object.freeze(params.context ?? {});\n const maxSteps = definition.maxSteps ?? DEFAULT_MAX_STEPS;\n const loopWarnAfter = definition.loopWarnAfter ?? DEFAULT_LOOP_WARN;\n\n const logger = log;\n const logModule = `${LOG_MODULE_BASE}.${definition.name}`;\n\n const stepByName = new Map<string, StepDefinition>();\n for (const s of definition.steps) stepByName.set(s.name, s);\n\n const state: Record<string, unknown> = params.resumeFrom\n ? { ...params.resumeFrom.state }\n : {};\n const steps: Record<string, StepSnapshot> = params.resumeFrom\n ? { ...params.resumeFrom.steps }\n : {};\n const enteredCount = new Map<string, number>();\n const usage: Usage = { input: 0, output: 0, total: 0 };\n\n const startedAt = params.resumeFrom?.startedAt ?? new Date().toISOString();\n const startedAtDate = new Date(startedAt);\n const runStartPerf = performance.now();\n\n let error: AIError | undefined;\n let status: \"completed\" | \"failed\" | \"cancelled\" = \"completed\";\n let cancelledAt: string | undefined;\n let lastGoto: string | null = null;\n // Captured when a step throws after retries exhaust (and `onFailure`\n // didn't recover). Used to point the final snapshot's `next` at the\n // failed step so `resume()` re-runs it after the cause is fixed.\n let failedStepName: string | undefined;\n\n const buildContext = (current?: {\n state: Record<string, unknown>;\n agentResult?: unknown;\n }): WorkflowContext => ({\n input,\n context,\n steps: steps as Readonly<Record<string, StepSnapshot>>,\n state: current?.state ?? state,\n agentResult: current?.agentResult as WorkflowContext[\"agentResult\"],\n runId,\n signal,\n startedAt: startedAtDate,\n });\n\n emitter.emit(\n \"workflow.starting\",\n { workflowName: definition.name, input },\n params.executionHandlers,\n );\n logger.info(logModule, \"starting\", \"workflow starting\", { runId });\n\n let currentName: string | null = resolveInitialStep(\n definition,\n params.resumeFrom,\n );\n let stepCount = 0;\n\n try {\n while (currentName !== null) {\n if (signal?.aborted) throw createCancelledError(signal);\n\n stepCount += 1;\n if (stepCount > maxSteps) {\n throw new MaxStepsExceededError(\n `workflow \"${definition.name}\" exceeded maxSteps=${maxSteps}`,\n { maxSteps },\n );\n }\n\n const entered = (enteredCount.get(currentName) ?? 0) + 1;\n enteredCount.set(currentName, entered);\n if (entered === loopWarnAfter) {\n emitter.emit(\n \"workflow.loop.warning\",\n { step: currentName, enteredCount: entered, lastGoto },\n params.executionHandlers,\n );\n logger.warn(logModule, \"loop.warning\", \"loop warning\", {\n step: currentName,\n enteredCount: entered,\n });\n }\n\n const step = stepByName.get(currentName);\n if (!step) {\n throw new RoutingError(\n `workflow \"${definition.name}\": unknown step \"${currentName}\"`,\n { stepName: currentName },\n );\n }\n\n const snapshot = await executeStep({\n step,\n state,\n emitter,\n executionHandlers: params.executionHandlers,\n logger,\n logModule,\n signal,\n buildContext,\n usage,\n workflowDefaultRetry: definition.defaultRetry,\n });\n\n Object.assign(state, snapshot.state);\n steps[step.name] = finalizeSnapshot(snapshot);\n // Parallel children — flat-path addressing alongside nested.\n if (snapshot.steps) {\n for (const [childName, childSnap] of Object.entries(snapshot.steps)) {\n steps[childName] = childSnap;\n }\n }\n\n // Failure path: retries exhausted. Give `onFailure` a chance to\n // recover; otherwise checkpoint at the failed step (so resume\n // re-runs it) and throw — workflow halts.\n if (snapshot.status === \"failed\" && snapshot.error) {\n const failureRoute = await resolveFailureRoute({\n step,\n definition,\n error: snapshot.error,\n ctx: buildContext({ state, agentResult: snapshot.executionResult }),\n });\n\n if (failureRoute === undefined) {\n // No recovery — persist with `next: step.name` so resume\n // re-runs this step after the user fixes the cause.\n const persistOutcome = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: step.name,\n status: \"running\",\n });\n if (!persistOutcome.ok) {\n const persistErr = toAIError(persistOutcome.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(\n logModule,\n \"persist.failed\",\n \"snapshot persist failed\",\n {\n step: step.name,\n code: persistErr.code,\n message: persistErr.message,\n },\n );\n }\n failedStepName = step.name;\n throw snapshot.error;\n }\n\n // onFailure routed — workflow continues. Checkpoint at the\n // routed target (or `null` for `end`) so resume picks up there.\n const failureNext = failureRoute === \"end\" ? null : failureRoute;\n if (failureNext !== null && !stepByName.has(failureNext)) {\n throw new RoutingError(\n `workflow \"${definition.name}\": step \"${step.name}\" onFailure routed to unknown target \"${failureNext}\"`,\n { stepName: step.name, targetName: failureNext },\n );\n }\n\n const failurePersist = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: failureNext,\n status: \"running\",\n });\n if (!failurePersist.ok) {\n const persistErr = toAIError(failurePersist.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(\n logModule,\n \"persist.failed\",\n \"snapshot persist failed\",\n {\n step: step.name,\n code: persistErr.code,\n message: persistErr.message,\n },\n );\n }\n\n if (signal?.aborted) throw createCancelledError(signal);\n\n if (failureRoute === \"end\") {\n currentName = null;\n break;\n }\n\n lastGoto = failureRoute;\n currentName = failureRoute;\n continue;\n }\n\n // Resolve next step for checkpoint accuracy BEFORE routing errors\n // bubble — so the snapshot records where resume should resume from.\n const resolved = await resolveNextStep({\n step,\n definition,\n ctx: buildContext({ state, agentResult: snapshot.executionResult }),\n });\n\n const nextName =\n resolved === \"end\"\n ? null\n : typeof resolved === \"string\"\n ? resolved\n : nextDeclaredStep(definition, step.name);\n\n // Checkpoint after every step with the resolved `next`.\n const outcome = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: nextName,\n status: \"running\",\n });\n if (!outcome.ok) {\n const persistErr = toAIError(outcome.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(logModule, \"persist.failed\", \"snapshot persist failed\", {\n step: step.name,\n code: persistErr.code,\n message: persistErr.message,\n });\n }\n\n if (signal?.aborted) throw createCancelledError(signal);\n\n if (resolved === \"end\") {\n currentName = null;\n break;\n }\n\n if (typeof resolved === \"string\") {\n if (!stepByName.has(resolved)) {\n throw new RoutingError(\n `workflow \"${definition.name}\": step \"${step.name}\" goto unknown target \"${resolved}\"`,\n { stepName: step.name, targetName: resolved },\n );\n }\n lastGoto = resolved;\n currentName = resolved;\n continue;\n }\n\n currentName = nextName;\n lastGoto = currentName;\n }\n } catch (err) {\n if (err instanceof WorkflowCancelledError) {\n status = \"cancelled\";\n cancelledAt = err.cancelledAt;\n error = err;\n } else if (err instanceof AIError) {\n status = \"failed\";\n error = err;\n } else {\n status = \"failed\";\n error = new WorkflowError(\n err instanceof Error ? err.message : String(err),\n { cause: err },\n );\n }\n }\n\n // Note: a `failed` step always throws (caught above) unless its\n // `onFailure` recovered the run. A `completed` workflow may still\n // contain `failed` step snapshots — those are the recovered cases\n // and are intentionally preserved for forensic trace.\n\n const endedAt = new Date().toISOString();\n const duration = performance.now() - runStartPerf;\n\n let data: TOutput | undefined;\n if (status === \"completed\" && definition.output) {\n try {\n const extracted = await definition.output.extract(\n buildContext({ state }),\n );\n data = (await validateWorkflowOutput(\n definition.output.schema,\n extracted,\n )) as TOutput;\n } catch (err) {\n status = \"failed\";\n error =\n err instanceof AIError\n ? err\n : new WorkflowError(\n err instanceof Error ? err.message : String(err),\n { cause: err },\n );\n }\n }\n\n // Collect child executable reports from every step that ran one.\n // Today the step runner surfaces agent reports (the only executable\n // kind steps can invoke natively via `step.agent`); custom `run`\n // callbacks that call tools/workflows/supervisors can't be observed\n // here without a richer step-runner API — that's a v1.x follow-up\n // (see backlog: \"step.run executables surface in workflow tree\").\n const children: BaseReport[] = [];\n for (const stepName in steps) {\n const snap = steps[stepName];\n if (snap.agentReport) {\n children.push(snap.agentReport);\n }\n }\n\n const report: WorkflowReport = {\n runId,\n rootRunId: runId,\n name: definition.name,\n version: definition.version,\n type: \"workflow\",\n workflowName: definition.name,\n signature,\n status,\n // Stamp the terminal error onto the report so it travels with the tree\n // (observe path has no result envelope to fall back on). A failed `run`\n // step's cause lives in `steps[name].error`, but the workflow-level\n // error is what a consumer reads off the root span. Absent on success.\n ...(error ? { error } : {}),\n startedAt,\n endedAt,\n duration,\n cancelledAt,\n usage,\n children,\n steps,\n state: deepFreeze(cloneState(state)),\n };\n\n // Stamp lineage on the assembled tree exactly once. Walker rewrites\n // any inner self-roots that nested agent reports brought in (each\n // agent's `buildResult` set its own runId as root), propagates\n // sessionId, and writes `reportSchemaVersion` on the root.\n stampReportLineage(report, {\n rootRunId: runId,\n sessionId: params.sessionId,\n });\n\n // On a failed run with a captured `failedStepName`, point `next` at\n // the failed step so `resume()` re-runs it. The pre-throw checkpoint\n // already wrote this value, but the final snapshot would otherwise\n // overwrite it with `null` and force resume to fall back to the\n // first non-completed step (which is the same step in practice, but\n // less informative for tooling reading the snapshot).\n const finalNext = status === \"failed\" ? failedStepName ?? null : null;\n\n const finalOutcome = await persistSnapshot({\n definition,\n signature,\n runId,\n startedAt,\n input,\n state,\n steps,\n next: finalNext,\n status,\n });\n if (!finalOutcome.ok) {\n const persistErr = toAIError(finalOutcome.error);\n emitter.emit(\n \"workflow.error\",\n { error: persistErr },\n params.executionHandlers,\n );\n logger.error(logModule, \"persist.failed\", \"final snapshot persist failed\", {\n code: persistErr.code,\n message: persistErr.message,\n });\n }\n\n const result: WorkflowResult<TOutput> = {\n type: \"workflow\",\n data,\n report,\n usage,\n error,\n };\n\n if (status === \"cancelled\") {\n emitter.emit(\n \"workflow.cancelled\",\n {\n cancelledAt: cancelledAt ?? endedAt,\n reason: (error as WorkflowCancelledError | undefined)?.reason ?? \"\",\n },\n params.executionHandlers,\n );\n logger.warn(logModule, \"cancelled\", \"workflow cancelled\", { runId });\n }\n\n if (status === \"failed\" && error) {\n emitter.emit(\"workflow.error\", { error }, params.executionHandlers);\n logger.error(logModule, \"error\", \"workflow failed\", {\n runId,\n code: error.code,\n message: error.message,\n });\n }\n\n emitter.emit(\n \"workflow.completed\",\n { result: result as WorkflowResult<unknown> },\n params.executionHandlers,\n );\n logger.info(logModule, \"completed\", \"workflow completed\", {\n runId,\n status,\n duration,\n });\n\n return result;\n}\n\n/**\n * Run a failed step's `onFailure` hook (if present) and translate its\n * result into a route. Returns `undefined` when the workflow should\n * halt with the original error; `\"end\"` for clean termination; or a\n * step name to redirect to. A throw inside `onFailure` is wrapped in\n * `RoutingError` — routing is authoritative, never retried.\n */\nasync function resolveFailureRoute<T>(params: {\n step: StepDefinition;\n definition: WorkflowDefinition<any, T, any, any>;\n error: AIError;\n ctx: WorkflowContext;\n}): Promise<\"end\" | string | undefined> {\n const { step, definition, error, ctx } = params;\n if (!step.onFailure) return undefined;\n\n let outcome;\n try {\n outcome = await step.onFailure(ctx, error);\n } catch (err) {\n throw new RoutingError(\n `workflow \"${definition.name}\": step \"${step.name}\" onFailure threw`,\n { stepName: step.name, cause: err },\n );\n }\n return mapNextStep(outcome);\n}\n\nfunction resolveInitialStep<T>(\n definition: WorkflowDefinition<any, T, any, any>,\n resumeFrom: WorkflowSnapshot | undefined,\n): string | null {\n if (!resumeFrom) return definition.steps[0]?.name ?? null;\n\n // Prefer the explicitly-recorded `next` (now populated on every\n // checkpoint). Falls back to first step whose snapshot is missing\n // or not in a terminal-success state — covers older snapshots\n // written before `next` was wired.\n if (\n resumeFrom.next &&\n definition.steps.some(s => s.name === resumeFrom.next)\n ) {\n return resumeFrom.next;\n }\n\n for (const step of definition.steps) {\n const snap = resumeFrom.steps[step.name];\n if (!snap || (snap.status !== \"completed\" && snap.status !== \"skipped\")) {\n return step.name;\n }\n }\n\n return null;\n}\n\nasync function validateWorkflowOutput(\n schema: unknown,\n value: unknown,\n): Promise<unknown> {\n if (!schema) return value;\n\n const result = await (\n schema as {\n \"~standard\": { validate: (v: unknown) => Promise<unknown> | unknown };\n }\n )[\"~standard\"].validate(value);\n\n if (\n result &&\n typeof result === \"object\" &&\n \"issues\" in result &&\n (result as { issues: unknown }).issues\n ) {\n throw new SchemaValidationError(\n \"workflow output failed schema validation\",\n {\n issues: (result as { issues: any }).issues,\n },\n );\n }\n\n return (result as { value: unknown }).value;\n}\n"],"mappings":"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"}
@@ -4,6 +4,8 @@ import { SchemaValidationError } from "../errors/schema-validation-error.mjs";
4
4
  import { StepFailedError } from "../errors/step-failed-error.mjs";
5
5
  import { WorkflowCancelledError } from "../errors/workflow-cancelled-error.mjs";
6
6
  import "../errors/index.mjs";
7
+ import { mergeUsage } from "../utils/compute-cost.mjs";
8
+ import { withoutRunFrame } from "../utils/run-context.mjs";
7
9
  import { isAbortError, resolveBackoff, resolveRetryConfig } from "./retry.mjs";
8
10
  import { createCancelledError, sleep } from "./cancellation.mjs";
9
11
  import { cloneState, deepFreeze } from "./state.mjs";
@@ -78,17 +80,14 @@ async function executeStep(params) {
78
80
  const attemptState = cloneState(params.state);
79
81
  if (step.before) await step.before(params.buildContext({ state: attemptState }));
80
82
  if (step.agent) {
83
+ const agent = step.agent;
81
84
  const { prompt, ...agentOpts } = step.input ? await step.input(params.buildContext({ state: attemptState })) : { prompt: "" };
82
- const result = await step.agent.execute(prompt, {
85
+ const result = await withoutRunFrame(() => agent.execute(prompt, {
83
86
  ...agentOpts,
84
87
  signal
85
- });
88
+ }));
86
89
  executionResult = result;
87
- if (result.usage) {
88
- params.usage.input += result.usage.input ?? 0;
89
- params.usage.output += result.usage.output ?? 0;
90
- params.usage.total += result.usage.total ?? 0;
91
- }
90
+ if (result.usage) mergeUsage(params.usage, result.usage);
92
91
  if (result.error) throw result.error;
93
92
  } else if (step.run) executionResult = await step.run(params.buildContext({ state: attemptState }));
94
93
  if (step.output) {
@@ -222,25 +221,25 @@ async function runParallelStep(params) {
222
221
  const childSnapshots = {};
223
222
  let firstError;
224
223
  const results = await Promise.all((step.parallel ?? []).map(async (child) => {
225
- const snap = await executeStep({
226
- step: child,
227
- state: sharedState,
228
- emitter,
229
- executionHandlers,
230
- logger,
231
- logModule,
232
- signal,
233
- buildContext: params.buildContext,
234
- usage: params.usage
235
- });
236
- Object.assign(sharedState, snap.state);
237
224
  return {
238
225
  child,
239
- snap
226
+ snap: await executeStep({
227
+ step: child,
228
+ state: sharedState,
229
+ emitter,
230
+ executionHandlers,
231
+ logger,
232
+ logModule,
233
+ signal,
234
+ buildContext: params.buildContext,
235
+ usage: params.usage
236
+ })
240
237
  };
241
238
  }));
242
239
  for (const { child, snap } of results) {
243
240
  childSnapshots[child.name] = finalizeSnapshot(snap);
241
+ if (step.mergeState) step.mergeState(sharedState, snap.state, child.name);
242
+ else Object.assign(sharedState, snap.state);
244
243
  if (snap.status === "failed" && !firstError && snap.error) firstError = snap.error;
245
244
  }
246
245
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();