@warlock.js/ai 4.4.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/CHANGELOG.md +52 -55
  2. package/cjs/index.cjs +221 -14147
  3. package/cjs/src-C02yzsLs.cjs +22991 -0
  4. package/cjs/src-C02yzsLs.cjs.map +1 -0
  5. package/cjs/src-DFibP2FQ.cjs +30 -0
  6. package/esm/agent/agent-config.type.d.mts +90 -1
  7. package/esm/agent/agent-config.type.d.mts.map +1 -1
  8. package/esm/agent/agent-input-builder.mjs +27 -6
  9. package/esm/agent/agent-input-builder.mjs.map +1 -1
  10. package/esm/agent/agent.d.mts +40 -1
  11. package/esm/agent/agent.d.mts.map +1 -1
  12. package/esm/agent/agent.mjs +203 -30
  13. package/esm/agent/agent.mjs.map +1 -1
  14. package/esm/agent/index.d.mts +2 -1
  15. package/esm/agent/index.mjs +1 -0
  16. package/esm/agent/judge-config.type.d.mts +33 -0
  17. package/esm/agent/judge-config.type.d.mts.map +1 -0
  18. package/esm/agent/judge-config.type.mjs +13 -0
  19. package/esm/agent/judge-config.type.mjs.map +1 -0
  20. package/esm/ai-openai/src/embedder.mjs +4 -0
  21. package/esm/ai-openai/src/index.mjs +4 -0
  22. package/esm/ai-openai/src/model.mjs +5 -0
  23. package/esm/ai-openai/src/sdk.mjs +6 -0
  24. package/esm/ai-openai/src/utils/index.mjs +4 -0
  25. package/esm/ai-openai/src/utils/to-openai-tools.mjs +3 -0
  26. package/esm/ai-openai/src/utils/wrap-openai-error.mjs +4 -0
  27. package/esm/ai.d.mts +119 -53
  28. package/esm/ai.d.mts.map +1 -1
  29. package/esm/ai.mjs +43 -8
  30. package/esm/ai.mjs.map +1 -1
  31. package/esm/batch/batch.d.mts.map +1 -1
  32. package/esm/batch/batch.mjs +21 -1
  33. package/esm/batch/batch.mjs.map +1 -1
  34. package/esm/batch/batch.type.d.mts +11 -4
  35. package/esm/batch/batch.type.d.mts.map +1 -1
  36. package/esm/config.d.mts +39 -3
  37. package/esm/config.d.mts.map +1 -1
  38. package/esm/config.mjs +26 -2
  39. package/esm/config.mjs.map +1 -1
  40. package/esm/contracts/agent/agent-options.type.d.mts +11 -3
  41. package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
  42. package/esm/contracts/agent/eval.type.d.mts +43 -2
  43. package/esm/contracts/agent/eval.type.d.mts.map +1 -1
  44. package/esm/contracts/attachment-policy.type.d.mts +51 -0
  45. package/esm/contracts/attachment-policy.type.d.mts.map +1 -0
  46. package/esm/contracts/attachment.type.d.mts +16 -7
  47. package/esm/contracts/attachment.type.d.mts.map +1 -1
  48. package/esm/contracts/content-part.type.d.mts +14 -5
  49. package/esm/contracts/content-part.type.d.mts.map +1 -1
  50. package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -1
  51. package/esm/contracts/index.d.mts +6 -4
  52. package/esm/contracts/model.contract.d.mts +11 -8
  53. package/esm/contracts/model.contract.d.mts.map +1 -1
  54. package/esm/contracts/orchestrator/index.d.mts +1 -0
  55. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +31 -2
  56. package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
  57. package/esm/contracts/orchestrator/session-lock.contract.d.mts +47 -0
  58. package/esm/contracts/orchestrator/session-lock.contract.d.mts.map +1 -0
  59. package/esm/contracts/planner/index.d.mts +1 -1
  60. package/esm/contracts/planner/planner-config.type.d.mts +31 -0
  61. package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
  62. package/esm/contracts/planner/planner-execute-options.type.d.mts +48 -1
  63. package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
  64. package/esm/contracts/planner/planner-result.type.d.mts +8 -0
  65. package/esm/contracts/planner/planner-result.type.d.mts.map +1 -1
  66. package/esm/contracts/planner/planner.contract.d.mts +1 -1
  67. package/esm/contracts/result/agent-result.type.d.mts +6 -4
  68. package/esm/contracts/result/agent-result.type.d.mts.map +1 -1
  69. package/esm/contracts/result/base-report.type.d.mts +21 -3
  70. package/esm/contracts/result/base-report.type.d.mts.map +1 -1
  71. package/esm/contracts/result/base-report.type.mjs.map +1 -1
  72. package/esm/contracts/result/execution-report.type.d.mts +53 -1
  73. package/esm/contracts/result/execution-report.type.d.mts.map +1 -1
  74. package/esm/contracts/result/supervisor-result.type.d.mts +14 -2
  75. package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -1
  76. package/esm/contracts/result/workflow-result.type.d.mts +2 -1
  77. package/esm/contracts/result/workflow-result.type.d.mts.map +1 -1
  78. package/esm/contracts/supervisor/supervisor-config.type.d.mts +29 -0
  79. package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
  80. package/esm/contracts/system-prompt.contract.d.mts +103 -1
  81. package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
  82. package/esm/contracts/team/index.d.mts +1 -0
  83. package/esm/contracts/team/team-config.type.d.mts +127 -0
  84. package/esm/contracts/team/team-config.type.d.mts.map +1 -0
  85. package/esm/contracts/tool.contract.d.mts +4 -2
  86. package/esm/contracts/tool.contract.d.mts.map +1 -1
  87. package/esm/contracts/workflow/step.contract.d.mts +30 -5
  88. package/esm/contracts/workflow/step.contract.d.mts.map +1 -1
  89. package/esm/contracts/workflow/workflow.contract.d.mts +16 -0
  90. package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
  91. package/esm/errors/error-code.type.d.mts +1 -1
  92. package/esm/errors/index.d.mts +1 -0
  93. package/esm/errors/index.mjs +1 -0
  94. package/esm/errors/outbound-policy-error.d.mts +27 -0
  95. package/esm/errors/outbound-policy-error.d.mts.map +1 -0
  96. package/esm/errors/outbound-policy-error.mjs +32 -0
  97. package/esm/errors/outbound-policy-error.mjs.map +1 -0
  98. package/esm/eval/dataset.d.mts +28 -0
  99. package/esm/eval/dataset.d.mts.map +1 -0
  100. package/esm/eval/dataset.mjs +112 -0
  101. package/esm/eval/dataset.mjs.map +1 -0
  102. package/esm/eval/dataset.type.d.mts +53 -0
  103. package/esm/eval/dataset.type.d.mts.map +1 -0
  104. package/esm/eval/eval-runner.d.mts.map +1 -1
  105. package/esm/eval/eval-runner.mjs +16 -2
  106. package/esm/eval/eval-runner.mjs.map +1 -1
  107. package/esm/eval/index.d.mts +20 -1
  108. package/esm/eval/index.d.mts.map +1 -1
  109. package/esm/eval/index.mjs +20 -2
  110. package/esm/eval/index.mjs.map +1 -1
  111. package/esm/eval/regression.d.mts +30 -0
  112. package/esm/eval/regression.d.mts.map +1 -0
  113. package/esm/eval/regression.mjs +51 -0
  114. package/esm/eval/regression.mjs.map +1 -0
  115. package/esm/eval/report-json.d.mts +30 -0
  116. package/esm/eval/report-json.d.mts.map +1 -0
  117. package/esm/eval/report-json.mjs +33 -0
  118. package/esm/eval/report-json.mjs.map +1 -0
  119. package/esm/eval/report-junit.d.mts +22 -0
  120. package/esm/eval/report-junit.d.mts.map +1 -0
  121. package/esm/eval/report-junit.mjs +60 -0
  122. package/esm/eval/report-junit.mjs.map +1 -0
  123. package/esm/guard/contracts/guard-options.type.d.mts +165 -0
  124. package/esm/guard/contracts/guard-options.type.d.mts.map +1 -0
  125. package/esm/guard/contracts/guardrail.contract.d.mts +78 -0
  126. package/esm/guard/contracts/guardrail.contract.d.mts.map +1 -0
  127. package/esm/guard/contracts/index.d.mts +4 -0
  128. package/esm/guard/contracts/openai-client.contract.d.mts +51 -0
  129. package/esm/guard/contracts/openai-client.contract.d.mts.map +1 -0
  130. package/esm/guard/contracts/verdict.type.d.mts +76 -0
  131. package/esm/guard/contracts/verdict.type.d.mts.map +1 -0
  132. package/esm/guard/detectors/index.d.mts +4 -0
  133. package/esm/guard/detectors/index.mjs +6 -0
  134. package/esm/guard/detectors/injection.d.mts +34 -0
  135. package/esm/guard/detectors/injection.d.mts.map +1 -0
  136. package/esm/guard/detectors/injection.mjs +254 -0
  137. package/esm/guard/detectors/injection.mjs.map +1 -0
  138. package/esm/guard/detectors/moderation.d.mts +32 -0
  139. package/esm/guard/detectors/moderation.d.mts.map +1 -0
  140. package/esm/guard/detectors/moderation.mjs +134 -0
  141. package/esm/guard/detectors/moderation.mjs.map +1 -0
  142. package/esm/guard/detectors/pii.d.mts +41 -0
  143. package/esm/guard/detectors/pii.d.mts.map +1 -0
  144. package/esm/guard/detectors/pii.mjs +199 -0
  145. package/esm/guard/detectors/pii.mjs.map +1 -0
  146. package/esm/guard/detectors/topic.d.mts +29 -0
  147. package/esm/guard/detectors/topic.d.mts.map +1 -0
  148. package/esm/guard/detectors/topic.mjs +99 -0
  149. package/esm/guard/detectors/topic.mjs.map +1 -0
  150. package/esm/guard/errors.d.mts +12 -0
  151. package/esm/guard/errors.d.mts.map +1 -0
  152. package/esm/guard/errors.mjs +18 -0
  153. package/esm/guard/errors.mjs.map +1 -0
  154. package/esm/guard/guard.d.mts +67 -0
  155. package/esm/guard/guard.d.mts.map +1 -0
  156. package/esm/guard/guard.mjs +209 -0
  157. package/esm/guard/guard.mjs.map +1 -0
  158. package/esm/guard/guardrail.d.mts +39 -0
  159. package/esm/guard/guardrail.d.mts.map +1 -0
  160. package/esm/guard/guardrail.mjs +22 -0
  161. package/esm/guard/guardrail.mjs.map +1 -0
  162. package/esm/human/contracts/approval.type.d.mts +154 -0
  163. package/esm/human/contracts/approval.type.d.mts.map +1 -0
  164. package/esm/human/contracts/human-approval.type.d.mts +38 -0
  165. package/esm/human/contracts/human-approval.type.d.mts.map +1 -0
  166. package/esm/human/contracts/index.d.mts +4 -0
  167. package/esm/human/contracts/interrupt-store.contract.d.mts +112 -0
  168. package/esm/human/contracts/interrupt-store.contract.d.mts.map +1 -0
  169. package/esm/human/contracts/resume.type.d.mts +77 -0
  170. package/esm/human/contracts/resume.type.d.mts.map +1 -0
  171. package/esm/human/errors.d.mts +84 -0
  172. package/esm/human/errors.d.mts.map +1 -0
  173. package/esm/human/errors.mjs +60 -0
  174. package/esm/human/errors.mjs.map +1 -0
  175. package/esm/human/human-approval.d.mts +57 -0
  176. package/esm/human/human-approval.d.mts.map +1 -0
  177. package/esm/human/human-approval.mjs +170 -0
  178. package/esm/human/human-approval.mjs.map +1 -0
  179. package/esm/human/policy.d.mts +55 -0
  180. package/esm/human/policy.d.mts.map +1 -0
  181. package/esm/human/policy.mjs +67 -0
  182. package/esm/human/policy.mjs.map +1 -0
  183. package/esm/human/register.mjs +37 -0
  184. package/esm/human/register.mjs.map +1 -0
  185. package/esm/human/resume-seed.mjs +53 -0
  186. package/esm/human/resume-seed.mjs.map +1 -0
  187. package/esm/human/resume.d.mts +54 -0
  188. package/esm/human/resume.d.mts.map +1 -0
  189. package/esm/human/resume.mjs +121 -0
  190. package/esm/human/resume.mjs.map +1 -0
  191. package/esm/human/stores/index.mjs +5 -0
  192. package/esm/human/stores/memory.d.mts +30 -0
  193. package/esm/human/stores/memory.d.mts.map +1 -0
  194. package/esm/human/stores/memory.mjs +91 -0
  195. package/esm/human/stores/memory.mjs.map +1 -0
  196. package/esm/human/stores/pg.d.mts +59 -0
  197. package/esm/human/stores/pg.d.mts.map +1 -0
  198. package/esm/human/stores/pg.mjs +220 -0
  199. package/esm/human/stores/pg.mjs.map +1 -0
  200. package/esm/human/stores/redis.d.mts +56 -0
  201. package/esm/human/stores/redis.d.mts.map +1 -0
  202. package/esm/human/stores/redis.mjs +201 -0
  203. package/esm/human/stores/redis.mjs.map +1 -0
  204. package/esm/index.d.mts +113 -22
  205. package/esm/index.mjs +82 -11
  206. package/esm/middleware/builtins/budget.mjs +6 -2
  207. package/esm/middleware/builtins/budget.mjs.map +1 -1
  208. package/esm/middleware/utils/extract-user-text.d.mts +8 -1
  209. package/esm/middleware/utils/extract-user-text.d.mts.map +1 -1
  210. package/esm/middleware/utils/extract-user-text.mjs +8 -1
  211. package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
  212. package/esm/object-stream/index.d.mts +2 -0
  213. package/esm/object-stream/index.mjs +4 -0
  214. package/esm/object-stream/parse-partial-json.d.mts +22 -0
  215. package/esm/object-stream/parse-partial-json.d.mts.map +1 -0
  216. package/esm/object-stream/parse-partial-json.mjs +78 -0
  217. package/esm/object-stream/parse-partial-json.mjs.map +1 -0
  218. package/esm/object-stream/stream-object.d.mts +68 -0
  219. package/esm/object-stream/stream-object.d.mts.map +1 -0
  220. package/esm/object-stream/stream-object.mjs +104 -0
  221. package/esm/object-stream/stream-object.mjs.map +1 -0
  222. package/esm/observe/index.mjs +4 -0
  223. package/esm/observe/observer-registry.d.mts +30 -0
  224. package/esm/observe/observer-registry.d.mts.map +1 -0
  225. package/esm/observe/observer-registry.mjs +51 -0
  226. package/esm/observe/observer-registry.mjs.map +1 -0
  227. package/esm/observe/observer.contract.d.mts +40 -0
  228. package/esm/observe/observer.contract.d.mts.map +1 -0
  229. package/esm/observe/resolve-observers.d.mts +40 -0
  230. package/esm/observe/resolve-observers.d.mts.map +1 -0
  231. package/esm/observe/resolve-observers.mjs +73 -0
  232. package/esm/observe/resolve-observers.mjs.map +1 -0
  233. package/esm/orchestrator/execution.d.mts.map +1 -1
  234. package/esm/orchestrator/execution.mjs +5 -2
  235. package/esm/orchestrator/execution.mjs.map +1 -1
  236. package/esm/orchestrator/index.d.mts +1 -0
  237. package/esm/orchestrator/index.mjs +1 -0
  238. package/esm/orchestrator/orchestrator.d.mts.map +1 -1
  239. package/esm/orchestrator/orchestrator.mjs +39 -6
  240. package/esm/orchestrator/orchestrator.mjs.map +1 -1
  241. package/esm/orchestrator/session-lock.d.mts +25 -0
  242. package/esm/orchestrator/session-lock.d.mts.map +1 -0
  243. package/esm/orchestrator/session-lock.mjs +83 -0
  244. package/esm/orchestrator/session-lock.mjs.map +1 -0
  245. package/esm/planner/dag-scheduler.mjs +97 -0
  246. package/esm/planner/dag-scheduler.mjs.map +1 -0
  247. package/esm/planner/plan-prompt.d.mts +1 -1
  248. package/esm/planner/plan-prompt.d.mts.map +1 -1
  249. package/esm/planner/plan-prompt.mjs +2 -1
  250. package/esm/planner/plan-prompt.mjs.map +1 -1
  251. package/esm/planner/planner-run.d.mts.map +1 -1
  252. package/esm/planner/planner-run.mjs +300 -29
  253. package/esm/planner/planner-run.mjs.map +1 -1
  254. package/esm/planner/planner.mjs +1 -1
  255. package/esm/planner/planner.mjs.map +1 -1
  256. package/esm/prompt/errors.d.mts +57 -0
  257. package/esm/prompt/errors.d.mts.map +1 -0
  258. package/esm/prompt/errors.mjs +73 -0
  259. package/esm/prompt/errors.mjs.map +1 -0
  260. package/esm/prompt/index.d.mts +3 -0
  261. package/esm/prompt/index.mjs +4 -0
  262. package/esm/prompt/prompt-langfuse-sync.mjs +104 -0
  263. package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -0
  264. package/esm/prompt/prompt-langfuse-sync.type.d.mts +32 -0
  265. package/esm/prompt/prompt-langfuse-sync.type.d.mts.map +1 -0
  266. package/esm/prompt/prompt-validate.mjs +170 -0
  267. package/esm/prompt/prompt-validate.mjs.map +1 -0
  268. package/esm/prompt/prompt.d.mts +54 -0
  269. package/esm/prompt/prompt.d.mts.map +1 -0
  270. package/esm/prompt/prompt.mjs +218 -0
  271. package/esm/prompt/prompt.mjs.map +1 -0
  272. package/esm/prompt/prompt.type.d.mts +174 -0
  273. package/esm/prompt/prompt.type.d.mts.map +1 -0
  274. package/esm/prompts/index.d.mts +3 -0
  275. package/esm/prompts/index.mjs +3 -0
  276. package/esm/prompts/prompts-manager.contract.d.mts +154 -0
  277. package/esm/prompts/prompts-manager.contract.d.mts.map +1 -0
  278. package/esm/prompts/prompts-manager.d.mts +38 -0
  279. package/esm/prompts/prompts-manager.d.mts.map +1 -0
  280. package/esm/prompts/prompts-manager.mjs +410 -0
  281. package/esm/prompts/prompts-manager.mjs.map +1 -0
  282. package/esm/prompts/prompts-manager.type.d.mts +172 -0
  283. package/esm/prompts/prompts-manager.type.d.mts.map +1 -0
  284. package/esm/prompts/prompts-validate.mjs +200 -0
  285. package/esm/prompts/prompts-validate.mjs.map +1 -0
  286. package/esm/rag/as-tool.mjs +48 -0
  287. package/esm/rag/as-tool.mjs.map +1 -0
  288. package/esm/rag/chunk/chunk.d.mts +24 -0
  289. package/esm/rag/chunk/chunk.d.mts.map +1 -0
  290. package/esm/rag/chunk/chunk.mjs +44 -0
  291. package/esm/rag/chunk/chunk.mjs.map +1 -0
  292. package/esm/rag/chunk/fixed.mjs +32 -0
  293. package/esm/rag/chunk/fixed.mjs.map +1 -0
  294. package/esm/rag/chunk/markdown.mjs +75 -0
  295. package/esm/rag/chunk/markdown.mjs.map +1 -0
  296. package/esm/rag/chunk/recursive.mjs +132 -0
  297. package/esm/rag/chunk/recursive.mjs.map +1 -0
  298. package/esm/rag/chunk/sentence.mjs +73 -0
  299. package/esm/rag/chunk/sentence.mjs.map +1 -0
  300. package/esm/rag/contracts/chunk-options.type.d.mts +35 -0
  301. package/esm/rag/contracts/chunk-options.type.d.mts.map +1 -0
  302. package/esm/rag/contracts/citation.type.d.mts +35 -0
  303. package/esm/rag/contracts/citation.type.d.mts.map +1 -0
  304. package/esm/rag/contracts/index.d.mts +4 -0
  305. package/esm/rag/contracts/rag-config.type.d.mts +68 -0
  306. package/esm/rag/contracts/rag-config.type.d.mts.map +1 -0
  307. package/esm/rag/contracts/rag-document.type.d.mts +21 -0
  308. package/esm/rag/contracts/rag-document.type.d.mts.map +1 -0
  309. package/esm/rag/hybrid/bm25.d.mts +23 -0
  310. package/esm/rag/hybrid/bm25.d.mts.map +1 -0
  311. package/esm/rag/hybrid/bm25.mjs +51 -0
  312. package/esm/rag/hybrid/bm25.mjs.map +1 -0
  313. package/esm/rag/hybrid/hybrid-rank.d.mts +33 -0
  314. package/esm/rag/hybrid/hybrid-rank.d.mts.map +1 -0
  315. package/esm/rag/hybrid/hybrid-rank.mjs +29 -0
  316. package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -0
  317. package/esm/rag/hybrid/rrf.d.mts +25 -0
  318. package/esm/rag/hybrid/rrf.d.mts.map +1 -0
  319. package/esm/rag/hybrid/rrf.mjs +30 -0
  320. package/esm/rag/hybrid/rrf.mjs.map +1 -0
  321. package/esm/rag/index.d.mts +15 -0
  322. package/esm/rag/index.mjs +11 -0
  323. package/esm/rag/rag.d.mts +38 -0
  324. package/esm/rag/rag.d.mts.map +1 -0
  325. package/esm/rag/rag.mjs +126 -0
  326. package/esm/rag/rag.mjs.map +1 -0
  327. package/esm/rag/rerank/keyword-reranker.d.mts +32 -0
  328. package/esm/rag/rerank/keyword-reranker.d.mts.map +1 -0
  329. package/esm/rag/rerank/keyword-reranker.mjs +58 -0
  330. package/esm/rag/rerank/keyword-reranker.mjs.map +1 -0
  331. package/esm/rag/rerank/llm-reranker.d.mts +36 -0
  332. package/esm/rag/rerank/llm-reranker.d.mts.map +1 -0
  333. package/esm/rag/rerank/llm-reranker.mjs +85 -0
  334. package/esm/rag/rerank/llm-reranker.mjs.map +1 -0
  335. package/esm/rag/rerank/reranker.contract.d.mts +28 -0
  336. package/esm/rag/rerank/reranker.contract.d.mts.map +1 -0
  337. package/esm/rag/retrieve.mjs +68 -0
  338. package/esm/rag/retrieve.mjs.map +1 -0
  339. package/esm/rag/store/cache-vector-store.d.mts +27 -0
  340. package/esm/rag/store/cache-vector-store.d.mts.map +1 -0
  341. package/esm/rag/store/cache-vector-store.mjs +48 -0
  342. package/esm/rag/store/cache-vector-store.mjs.map +1 -0
  343. package/esm/rag/store/vector-store.contract.d.mts +38 -0
  344. package/esm/rag/store/vector-store.contract.d.mts.map +1 -0
  345. package/esm/rag/transforms/multi-query.d.mts +27 -0
  346. package/esm/rag/transforms/multi-query.d.mts.map +1 -0
  347. package/esm/rag/transforms/multi-query.mjs +41 -0
  348. package/esm/rag/transforms/multi-query.mjs.map +1 -0
  349. package/esm/security/index.mjs +5 -0
  350. package/esm/security/outbound-policy.d.mts +46 -0
  351. package/esm/security/outbound-policy.d.mts.map +1 -0
  352. package/esm/security/outbound-policy.mjs +187 -0
  353. package/esm/security/outbound-policy.mjs.map +1 -0
  354. package/esm/security/outbound-policy.type.d.mts +74 -0
  355. package/esm/security/outbound-policy.type.d.mts.map +1 -0
  356. package/esm/security/private-ip.d.mts +15 -0
  357. package/esm/security/private-ip.d.mts.map +1 -0
  358. package/esm/security/private-ip.mjs +48 -0
  359. package/esm/security/private-ip.mjs.map +1 -0
  360. package/esm/security/redact.d.mts +59 -0
  361. package/esm/security/redact.d.mts.map +1 -0
  362. package/esm/security/redact.mjs +122 -0
  363. package/esm/security/redact.mjs.map +1 -0
  364. package/esm/serve/serve.d.mts +50 -0
  365. package/esm/serve/serve.d.mts.map +1 -0
  366. package/esm/serve/serve.mjs +90 -0
  367. package/esm/serve/serve.mjs.map +1 -0
  368. package/esm/serve/sse.d.mts +20 -0
  369. package/esm/serve/sse.d.mts.map +1 -0
  370. package/esm/serve/sse.mjs +25 -0
  371. package/esm/serve/sse.mjs.map +1 -0
  372. package/esm/serve/stream-to-sse.d.mts +29 -0
  373. package/esm/serve/stream-to-sse.d.mts.map +1 -0
  374. package/esm/serve/stream-to-sse.mjs +37 -0
  375. package/esm/serve/stream-to-sse.mjs.map +1 -0
  376. package/esm/skills/catalog.d.mts +49 -0
  377. package/esm/skills/catalog.d.mts.map +1 -0
  378. package/esm/skills/catalog.mjs +140 -0
  379. package/esm/skills/catalog.mjs.map +1 -0
  380. package/esm/skills/contracts/skill-record.type.d.mts +37 -0
  381. package/esm/skills/contracts/skill-record.type.d.mts.map +1 -0
  382. package/esm/skills/contracts/skills-config.type.d.mts +108 -0
  383. package/esm/skills/contracts/skills-config.type.d.mts.map +1 -0
  384. package/esm/skills/contracts/skills-store.contract.d.mts +28 -0
  385. package/esm/skills/contracts/skills-store.contract.d.mts.map +1 -0
  386. package/esm/skills/contracts/skills.contract.d.mts +43 -0
  387. package/esm/skills/contracts/skills.contract.d.mts.map +1 -0
  388. package/esm/skills/index.d.mts +16 -0
  389. package/esm/skills/index.mjs +14 -0
  390. package/esm/skills/load-skill-tool.d.mts +38 -0
  391. package/esm/skills/load-skill-tool.d.mts.map +1 -0
  392. package/esm/skills/load-skill-tool.mjs +65 -0
  393. package/esm/skills/load-skill-tool.mjs.map +1 -0
  394. package/esm/skills/review-gate.d.mts +33 -0
  395. package/esm/skills/review-gate.d.mts.map +1 -0
  396. package/esm/skills/review-gate.mjs +60 -0
  397. package/esm/skills/review-gate.mjs.map +1 -0
  398. package/esm/skills/save-skill-tool.d.mts +39 -0
  399. package/esm/skills/save-skill-tool.d.mts.map +1 -0
  400. package/esm/skills/save-skill-tool.mjs +65 -0
  401. package/esm/skills/save-skill-tool.mjs.map +1 -0
  402. package/esm/skills/skills.d.mts +33 -0
  403. package/esm/skills/skills.d.mts.map +1 -0
  404. package/esm/skills/skills.mjs +109 -0
  405. package/esm/skills/skills.mjs.map +1 -0
  406. package/esm/skills/sources/directory-source.d.mts +19 -0
  407. package/esm/skills/sources/directory-source.d.mts.map +1 -0
  408. package/esm/skills/sources/directory-source.mjs +108 -0
  409. package/esm/skills/sources/directory-source.mjs.map +1 -0
  410. package/esm/skills/sources/index.d.mts +18 -0
  411. package/esm/skills/sources/index.d.mts.map +1 -0
  412. package/esm/skills/sources/index.mjs +27 -0
  413. package/esm/skills/sources/index.mjs.map +1 -0
  414. package/esm/skills/sources/parse-frontmatter.d.mts +27 -0
  415. package/esm/skills/sources/parse-frontmatter.d.mts.map +1 -0
  416. package/esm/skills/sources/parse-frontmatter.mjs +46 -0
  417. package/esm/skills/sources/parse-frontmatter.mjs.map +1 -0
  418. package/esm/skills/sources/store-source.d.mts +14 -0
  419. package/esm/skills/sources/store-source.d.mts.map +1 -0
  420. package/esm/skills/sources/store-source.mjs +15 -0
  421. package/esm/skills/sources/store-source.mjs.map +1 -0
  422. package/esm/skills/sources/url-source.d.mts +29 -0
  423. package/esm/skills/sources/url-source.d.mts.map +1 -0
  424. package/esm/skills/sources/url-source.mjs +117 -0
  425. package/esm/skills/sources/url-source.mjs.map +1 -0
  426. package/esm/skills/store/mock-skills-store.d.mts +57 -0
  427. package/esm/skills/store/mock-skills-store.d.mts.map +1 -0
  428. package/esm/skills/store/mock-skills-store.mjs +100 -0
  429. package/esm/skills/store/mock-skills-store.mjs.map +1 -0
  430. package/esm/skills/store/procedural-skill-store.d.mts +30 -0
  431. package/esm/skills/store/procedural-skill-store.d.mts.map +1 -0
  432. package/esm/skills/store/procedural-skill-store.mjs +125 -0
  433. package/esm/skills/store/procedural-skill-store.mjs.map +1 -0
  434. package/esm/supervisor/as-tool.mjs +2 -2
  435. package/esm/supervisor/as-tool.mjs.map +1 -1
  436. package/esm/supervisor/execution.d.mts.map +1 -1
  437. package/esm/supervisor/execution.mjs +31 -28
  438. package/esm/supervisor/execution.mjs.map +1 -1
  439. package/esm/supervisor/supervisor.d.mts.map +1 -1
  440. package/esm/supervisor/supervisor.mjs +8 -3
  441. package/esm/supervisor/supervisor.mjs.map +1 -1
  442. package/esm/system-prompt/index.d.mts +4 -0
  443. package/esm/system-prompt/system-prompt.d.mts +68 -4
  444. package/esm/system-prompt/system-prompt.d.mts.map +1 -1
  445. package/esm/system-prompt/system-prompt.mjs +89 -5
  446. package/esm/system-prompt/system-prompt.mjs.map +1 -1
  447. package/esm/team/gates.mjs +48 -0
  448. package/esm/team/gates.mjs.map +1 -0
  449. package/esm/team/index.d.mts +1 -0
  450. package/esm/team/index.mjs +3 -0
  451. package/esm/team/team.d.mts +42 -0
  452. package/esm/team/team.d.mts.map +1 -0
  453. package/esm/team/team.mjs +94 -0
  454. package/esm/team/team.mjs.map +1 -0
  455. package/esm/tool/executable-as-tool.d.mts.map +1 -1
  456. package/esm/tool/executable-as-tool.mjs +2 -2
  457. package/esm/tool/executable-as-tool.mjs.map +1 -1
  458. package/esm/tool/tool.d.mts.map +1 -1
  459. package/esm/tool/tool.mjs +2 -2
  460. package/esm/tool/tool.mjs.map +1 -1
  461. package/esm/utils/compute-cost.d.mts +17 -1
  462. package/esm/utils/compute-cost.d.mts.map +1 -1
  463. package/esm/utils/compute-cost.mjs +26 -1
  464. package/esm/utils/compute-cost.mjs.map +1 -1
  465. package/esm/utils/extract-json-lenient.d.mts +42 -0
  466. package/esm/utils/extract-json-lenient.d.mts.map +1 -0
  467. package/esm/utils/extract-json-lenient.mjs +97 -0
  468. package/esm/utils/extract-json-lenient.mjs.map +1 -0
  469. package/esm/utils/index.d.mts +4 -2
  470. package/esm/utils/index.mjs +3 -1
  471. package/esm/utils/json-schema.d.mts +1 -1
  472. package/esm/utils/prepare-attachment-part.d.mts +10 -1
  473. package/esm/utils/prepare-attachment-part.d.mts.map +1 -1
  474. package/esm/utils/prepare-attachment-part.mjs +103 -11
  475. package/esm/utils/prepare-attachment-part.mjs.map +1 -1
  476. package/esm/utils/resolve-attachment.d.mts +4 -3
  477. package/esm/utils/resolve-attachment.d.mts.map +1 -1
  478. package/esm/utils/resolve-attachment.mjs +4 -3
  479. package/esm/utils/resolve-attachment.mjs.map +1 -1
  480. package/esm/utils/run-context.d.mts +94 -0
  481. package/esm/utils/run-context.d.mts.map +1 -0
  482. package/esm/utils/run-context.mjs +98 -0
  483. package/esm/utils/run-context.mjs.map +1 -0
  484. package/esm/vcr/cassette-io.mjs +57 -0
  485. package/esm/vcr/cassette-io.mjs.map +1 -0
  486. package/esm/vcr/errors.d.mts +42 -0
  487. package/esm/vcr/errors.d.mts.map +1 -0
  488. package/esm/vcr/errors.mjs +37 -0
  489. package/esm/vcr/errors.mjs.map +1 -0
  490. package/esm/vcr/hash-request.d.mts +28 -0
  491. package/esm/vcr/hash-request.d.mts.map +1 -0
  492. package/esm/vcr/hash-request.mjs +118 -0
  493. package/esm/vcr/hash-request.mjs.map +1 -0
  494. package/esm/vcr/index.d.mts +4 -0
  495. package/esm/vcr/index.mjs +5 -0
  496. package/esm/vcr/vcr.d.mts +32 -0
  497. package/esm/vcr/vcr.d.mts.map +1 -0
  498. package/esm/vcr/vcr.mjs +248 -0
  499. package/esm/vcr/vcr.mjs.map +1 -0
  500. package/esm/vcr/vcr.type.d.mts +118 -0
  501. package/esm/vcr/vcr.type.d.mts.map +1 -0
  502. package/esm/workflow/as-tool.mjs +2 -2
  503. package/esm/workflow/as-tool.mjs.map +1 -1
  504. package/esm/workflow/engine.mjs +1 -0
  505. package/esm/workflow/engine.mjs.map +1 -1
  506. package/esm/workflow/step-runner.mjs +19 -20
  507. package/esm/workflow/step-runner.mjs.map +1 -1
  508. package/esm/workflow/workflow.d.mts.map +1 -1
  509. package/esm/workflow/workflow.mjs +8 -3
  510. package/esm/workflow/workflow.mjs.map +1 -1
  511. package/llms-full.txt +1683 -68
  512. package/llms.txt +16 -4
  513. package/package.json +7 -3
  514. package/skills/README.md +40 -4
  515. package/skills/ai-dx-helpers/SKILL.md +2 -2
  516. package/skills/approve-tool-calls/SKILL.md +134 -0
  517. package/skills/attach-ai-middleware/SKILL.md +1 -1
  518. package/skills/detect-and-redact-pii/SKILL.md +104 -0
  519. package/skills/durable-resume/SKILL.md +128 -0
  520. package/skills/escalate-block-to-human/SKILL.md +85 -0
  521. package/skills/eval-datasets-and-ci/SKILL.md +117 -0
  522. package/skills/guard-input-output/SKILL.md +117 -0
  523. package/skills/manage-prompts/SKILL.md +186 -0
  524. package/skills/observe-ai-flows/SKILL.md +94 -0
  525. package/skills/record-replay-llm/SKILL.md +92 -0
  526. package/skills/run-ai-agent/SKILL.md +26 -1
  527. package/skills/run-ai-rag/SKILL.md +139 -0
  528. package/skills/run-ai-team/SKILL.md +107 -0
  529. package/skills/run-orchestrator/SKILL.md +2 -0
  530. package/skills/run-planner/SKILL.md +73 -8
  531. package/skills/run-supervisor/SKILL.md +20 -1
  532. package/skills/use-runtime-skills/SKILL.md +106 -0
  533. package/skills/write-system-prompt/SKILL.md +30 -1
  534. package/cjs/index.cjs.map +0 -1
  535. package/esm/contracts/result/index.d.mts +0 -15
@@ -0,0 +1,134 @@
1
+ import { OPENAI_INSTALL_INSTRUCTIONS } from "../errors.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/guard/detectors/moderation.ts
4
+ const DETECTOR_NAME = "moderation.openai";
5
+ const DEFAULT_MODEL = "omni-moderation-latest";
6
+ let OpenAiSdk;
7
+ let isModuleExists;
8
+ let loadingPromise;
9
+ /**
10
+ * Settle the lazy import of `openai` once, concurrency-safe. Only needed
11
+ * when the caller did not pass a ready `client`. A bare `catch` flips the
12
+ * flag to `false`; the curated {@link OPENAI_INSTALL_INSTRUCTIONS} surfaces
13
+ * at first `check()`, never a raw module-resolution stack trace. Mirrors
14
+ * ai-panoptic's `loadLangfuse`.
15
+ */
16
+ function loadOpenAi() {
17
+ if (isModuleExists !== void 0) return Promise.resolve();
18
+ if (loadingPromise) return loadingPromise;
19
+ loadingPromise = (async () => {
20
+ try {
21
+ OpenAiSdk = await import("openai");
22
+ isModuleExists = true;
23
+ } catch {
24
+ isModuleExists = false;
25
+ }
26
+ })();
27
+ return loadingPromise;
28
+ }
29
+ /**
30
+ * The optional OpenAI-backed moderation detector — the internal class behind
31
+ * the {@link moderation} factory. Sends the inspected text to OpenAI's
32
+ * moderation endpoint and maps the flagged categories to a verdict: any
33
+ * category in `blockOn` → `block`; any other flagged category → `flag`;
34
+ * nothing flagged → `allow`.
35
+ *
36
+ * The `openai` SDK is resolved lazily on the FIRST `check()` (not at
37
+ * construction) so importing `@warlock.js/ai` never forces the peer to
38
+ * be installed. When a `client` is supplied it is used verbatim and the SDK
39
+ * is never imported.
40
+ */
41
+ var OpenAiModerationDetector = class {
42
+ constructor(options = {}) {
43
+ this.name = DETECTOR_NAME;
44
+ this.client = options.client;
45
+ this.apiKey = options.apiKey;
46
+ this.model = options.model ?? DEFAULT_MODEL;
47
+ this.blockOn = new Set(options.blockOn ?? []);
48
+ if (!this.client) loadOpenAi();
49
+ }
50
+ /**
51
+ * Moderate `text` and fold the response into a verdict. `allow` when the
52
+ * model flags nothing; `block` when any flagged category is in `blockOn`;
53
+ * otherwise `flag` listing every flagged category. Resolving the client
54
+ * throws the curated install string when the `openai` peer is absent.
55
+ */
56
+ async check(text) {
57
+ const result = (await (await this.resolveClient()).moderations.create({
58
+ model: this.model,
59
+ input: text
60
+ })).results[0];
61
+ if (result === void 0 || !result.flagged) return { type: "allow" };
62
+ return this.toVerdict(result);
63
+ }
64
+ /**
65
+ * Return the supplied client, or construct one lazily from the resolved
66
+ * SDK. Throws {@link OPENAI_INSTALL_INSTRUCTIONS} (a plain `Error` — a
67
+ * missing optional peer is an infrastructure fault, not a content
68
+ * violation) when `openai` could not be imported.
69
+ */
70
+ async resolveClient() {
71
+ if (this.client) return this.client;
72
+ await loadOpenAi();
73
+ if (!isModuleExists) throw new Error(OPENAI_INSTALL_INSTRUCTIONS);
74
+ this.client = new OpenAiSdk.default({ apiKey: this.apiKey });
75
+ return this.client;
76
+ }
77
+ /**
78
+ * Fold a flagged moderation result into a `block` or `flag` verdict. Every
79
+ * `true` category becomes a {@link GuardrailMatch} (`moderation.<category>`);
80
+ * the verdict is `block` when any flagged category is in `blockOn`,
81
+ * otherwise `flag`.
82
+ */
83
+ toVerdict(result) {
84
+ const flagged = Object.entries(result.categories).filter(([, tripped]) => tripped).map(([category]) => category);
85
+ const matches = flagged.map((category) => ({
86
+ rule: `moderation.${category}`,
87
+ label: category
88
+ }));
89
+ const shouldBlock = flagged.some((category) => this.blockOn.has(category));
90
+ const list = flagged.join(", ");
91
+ if (shouldBlock) return {
92
+ type: "block",
93
+ reason: `OpenAI moderation flagged blocked category(ies): ${list}.`,
94
+ matches
95
+ };
96
+ return {
97
+ type: "flag",
98
+ reason: `OpenAI moderation flagged category(ies): ${list}.`,
99
+ matches
100
+ };
101
+ }
102
+ };
103
+ /**
104
+ * Build the optional `moderation` detector (surfaced as
105
+ * `ai.guardrail.moderation(options?)`), backed by OpenAI's moderation
106
+ * endpoint. The `openai` SDK is an **optional lazy peer**: importing
107
+ * `@warlock.js/ai` never forces it to resolve, and the detector throws
108
+ * a curated install string ({@link OPENAI_INSTALL_INSTRUCTIONS}) on first
109
+ * `check()` when the peer is absent — mirroring ai-panoptic's lazy Langfuse
110
+ * exporter.
111
+ *
112
+ * On a moderation hit, every flagged category becomes a
113
+ * {@link GuardrailMatch}; the verdict is `block` when any flagged category is
114
+ * listed in `blockOn`, otherwise `flag`. A clean result is `allow`.
115
+ *
116
+ * @param options - `apiKey` (defaults to `OPENAI_API_KEY`), `model`
117
+ * (defaults to `"omni-moderation-latest"`), `blockOn` (categories that
118
+ * escalate to `block`), or a pre-built `client` to bypass the lazy import.
119
+ * @returns A {@link GuardrailDetector} for the guard's `input` / `output` / `tool` arrays.
120
+ *
121
+ * @example
122
+ * const guard = ai.guardrail({
123
+ * output: [
124
+ * ai.guardrail.moderation({ blockOn: ["violence", "sexual/minors"] }),
125
+ * ],
126
+ * });
127
+ */
128
+ function moderation(options) {
129
+ return new OpenAiModerationDetector(options);
130
+ }
131
+
132
+ //#endregion
133
+ export { moderation };
134
+ //# sourceMappingURL=moderation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moderation.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/guard/detectors/moderation.ts"],"sourcesContent":["import type {\n GuardrailDetector,\n GuardrailMatch,\n GuardrailVerdict,\n OpenAiClientLike,\n OpenAiModerationOptions,\n OpenAiModerationResult,\n} from \"../contracts\";\nimport { OPENAI_INSTALL_INSTRUCTIONS } from \"../errors\";\n\nconst DETECTOR_NAME = \"moderation.openai\";\n\nconst DEFAULT_MODEL = \"omni-moderation-latest\";\n\n// ============================================================\n// Lazily-loaded openai SDK (OPTIONAL peer)\n// ============================================================\n\nlet OpenAiSdk: typeof import(\"openai\");\nlet isModuleExists: boolean | undefined;\nlet loadingPromise: Promise<void> | undefined;\n\n/**\n * Settle the lazy import of `openai` once, concurrency-safe. Only needed\n * when the caller did not pass a ready `client`. A bare `catch` flips the\n * flag to `false`; the curated {@link OPENAI_INSTALL_INSTRUCTIONS} surfaces\n * at first `check()`, never a raw module-resolution stack trace. Mirrors\n * ai-panoptic's `loadLangfuse`.\n */\nfunction loadOpenAi(): Promise<void> {\n if (isModuleExists !== undefined) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n OpenAiSdk = await import(\"openai\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * The optional OpenAI-backed moderation detector — the internal class behind\n * the {@link moderation} factory. Sends the inspected text to OpenAI's\n * moderation endpoint and maps the flagged categories to a verdict: any\n * category in `blockOn` → `block`; any other flagged category → `flag`;\n * nothing flagged → `allow`.\n *\n * The `openai` SDK is resolved lazily on the FIRST `check()` (not at\n * construction) so importing `@warlock.js/ai` never forces the peer to\n * be installed. When a `client` is supplied it is used verbatim and the SDK\n * is never imported.\n */\nclass OpenAiModerationDetector implements GuardrailDetector {\n public readonly name = DETECTOR_NAME;\n\n /** A pre-built client, or `undefined` until the lazy SDK constructs one. */\n private client: OpenAiClientLike | undefined;\n\n private readonly apiKey: string | undefined;\n\n private readonly model: string;\n\n /** Categories that escalate to `block`; empty means \"flag on any\". */\n private readonly blockOn: ReadonlySet<string>;\n\n public constructor(options: OpenAiModerationOptions = {}) {\n this.client = options.client;\n this.apiKey = options.apiKey;\n this.model = options.model ?? DEFAULT_MODEL;\n this.blockOn = new Set(options.blockOn ?? []);\n\n // Kick off the lazy import eagerly when no client was supplied, so the\n // first `check()` does not pay the resolution latency. Errors are\n // swallowed by `loadOpenAi`; the curated install string surfaces at use.\n if (!this.client) {\n loadOpenAi();\n }\n }\n\n /**\n * Moderate `text` and fold the response into a verdict. `allow` when the\n * model flags nothing; `block` when any flagged category is in `blockOn`;\n * otherwise `flag` listing every flagged category. Resolving the client\n * throws the curated install string when the `openai` peer is absent.\n */\n public async check(text: string): Promise<GuardrailVerdict> {\n const client = await this.resolveClient();\n\n const response = await client.moderations.create({\n model: this.model,\n input: text,\n });\n\n const result = response.results[0];\n\n if (result === undefined || !result.flagged) {\n return { type: \"allow\" };\n }\n\n return this.toVerdict(result);\n }\n\n /**\n * Return the supplied client, or construct one lazily from the resolved\n * SDK. Throws {@link OPENAI_INSTALL_INSTRUCTIONS} (a plain `Error` — a\n * missing optional peer is an infrastructure fault, not a content\n * violation) when `openai` could not be imported.\n */\n private async resolveClient(): Promise<OpenAiClientLike> {\n if (this.client) {\n return this.client;\n }\n\n await loadOpenAi();\n\n if (!isModuleExists) {\n throw new Error(OPENAI_INSTALL_INSTRUCTIONS);\n }\n\n this.client = new OpenAiSdk.default({\n apiKey: this.apiKey,\n }) as unknown as OpenAiClientLike;\n\n return this.client;\n }\n\n /**\n * Fold a flagged moderation result into a `block` or `flag` verdict. Every\n * `true` category becomes a {@link GuardrailMatch} (`moderation.<category>`);\n * the verdict is `block` when any flagged category is in `blockOn`,\n * otherwise `flag`.\n */\n private toVerdict(result: OpenAiModerationResult): GuardrailVerdict {\n const flagged = Object.entries(result.categories)\n .filter(([, tripped]) => tripped)\n .map(([category]) => category);\n\n const matches: GuardrailMatch[] = flagged.map((category) => ({\n rule: `moderation.${category}`,\n label: category,\n }));\n\n const shouldBlock = flagged.some((category) => this.blockOn.has(category));\n const list = flagged.join(\", \");\n\n if (shouldBlock) {\n return {\n type: \"block\",\n reason: `OpenAI moderation flagged blocked category(ies): ${list}.`,\n matches,\n };\n }\n\n return {\n type: \"flag\",\n reason: `OpenAI moderation flagged category(ies): ${list}.`,\n matches,\n };\n }\n}\n\n/**\n * Build the optional `moderation` detector (surfaced as\n * `ai.guardrail.moderation(options?)`), backed by OpenAI's moderation\n * endpoint. The `openai` SDK is an **optional lazy peer**: importing\n * `@warlock.js/ai` never forces it to resolve, and the detector throws\n * a curated install string ({@link OPENAI_INSTALL_INSTRUCTIONS}) on first\n * `check()` when the peer is absent — mirroring ai-panoptic's lazy Langfuse\n * exporter.\n *\n * On a moderation hit, every flagged category becomes a\n * {@link GuardrailMatch}; the verdict is `block` when any flagged category is\n * listed in `blockOn`, otherwise `flag`. A clean result is `allow`.\n *\n * @param options - `apiKey` (defaults to `OPENAI_API_KEY`), `model`\n * (defaults to `\"omni-moderation-latest\"`), `blockOn` (categories that\n * escalate to `block`), or a pre-built `client` to bypass the lazy import.\n * @returns A {@link GuardrailDetector} for the guard's `input` / `output` / `tool` arrays.\n *\n * @example\n * const guard = ai.guardrail({\n * output: [\n * ai.guardrail.moderation({ blockOn: [\"violence\", \"sexual/minors\"] }),\n * ],\n * });\n */\nexport function moderation(\n options?: OpenAiModerationOptions,\n): GuardrailDetector {\n return new OpenAiModerationDetector(options);\n}\n"],"mappings":";;;AAUA,MAAM,gBAAgB;AAEtB,MAAM,gBAAgB;AAMtB,IAAI;AACJ,IAAI;AACJ,IAAI;;;;;;;;AASJ,SAAS,aAA4B;CACnC,IAAI,mBAAmB,QACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GACF,YAAY,MAAM,OAAO;GACzB,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;;;;;;;;AAcA,IAAM,2BAAN,MAA4D;CAa1D,AAAO,YAAY,UAAmC,CAAC,GAAG;cAZnC;EAarB,KAAK,SAAS,QAAQ;EACtB,KAAK,SAAS,QAAQ;EACtB,KAAK,QAAQ,QAAQ,SAAS;EAC9B,KAAK,UAAU,IAAI,IAAI,QAAQ,WAAW,CAAC,CAAC;EAK5C,IAAI,CAAC,KAAK,QACR,WAAW;CAEf;;;;;;;CAQA,MAAa,MAAM,MAAyC;EAQ1D,MAAM,UAAS,OALQ,MAFF,KAAK,cAAc,EAEX,CAAC,YAAY,OAAO;GAC/C,OAAO,KAAK;GACZ,OAAO;EACT,CAAC,EAEsB,CAAC,QAAQ;EAEhC,IAAI,WAAW,UAAa,CAAC,OAAO,SAClC,OAAO,EAAE,MAAM,QAAQ;EAGzB,OAAO,KAAK,UAAU,MAAM;CAC9B;;;;;;;CAQA,MAAc,gBAA2C;EACvD,IAAI,KAAK,QACP,OAAO,KAAK;EAGd,MAAM,WAAW;EAEjB,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,2BAA2B;EAG7C,KAAK,SAAS,IAAI,UAAU,QAAQ,EAClC,QAAQ,KAAK,OACf,CAAC;EAED,OAAO,KAAK;CACd;;;;;;;CAQA,AAAQ,UAAU,QAAkD;EAClE,MAAM,UAAU,OAAO,QAAQ,OAAO,UAAU,CAAC,CAC9C,QAAQ,GAAG,aAAa,OAAO,CAAC,CAChC,KAAK,CAAC,cAAc,QAAQ;EAE/B,MAAM,UAA4B,QAAQ,KAAK,cAAc;GAC3D,MAAM,cAAc;GACpB,OAAO;EACT,EAAE;EAEF,MAAM,cAAc,QAAQ,MAAM,aAAa,KAAK,QAAQ,IAAI,QAAQ,CAAC;EACzE,MAAM,OAAO,QAAQ,KAAK,IAAI;EAE9B,IAAI,aACF,OAAO;GACL,MAAM;GACN,QAAQ,oDAAoD,KAAK;GACjE;EACF;EAGF,OAAO;GACL,MAAM;GACN,QAAQ,4CAA4C,KAAK;GACzD;EACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,WACd,SACmB;CACnB,OAAO,IAAI,yBAAyB,OAAO;AAC7C"}
@@ -0,0 +1,41 @@
1
+ import { SyncGuardrailDetector } from "../contracts/guardrail.contract.mjs";
2
+ import { PiiDetectorOptions } from "../contracts/guard-options.type.mjs";
3
+ //#region ../@warlock.js/ai/src/guard/detectors/pii.d.ts
4
+ /**
5
+ * Build the built-in **PII detector** (`ai.guardrail.pii`) — a zero-runtime-
6
+ * dependency {@link GuardrailDetector} that scans text for personally
7
+ * identifiable information via a curated set of linear regexes plus an
8
+ * optional exact-string dictionary.
9
+ *
10
+ * Categories (`detect`, default: all): `ssn`, `email`, `phone`,
11
+ * `credit-card`, `ipv4`. `dictionary` adds extra exact terms matched
12
+ * case-insensitively as literal strings (regex metacharacters escaped).
13
+ *
14
+ * On a hit the verdict follows `onMatch` (default `"redact"`):
15
+ *
16
+ * - **`redact`** — every matched span is replaced by the `mask` template
17
+ * (`{label}` → the matched category, default `"[REDACTED]"`) and the
18
+ * rewritten text is returned for the factory to substitute. Output and
19
+ * tool phases honour the rewrite; on the input phase the factory treats a
20
+ * `redact` verdict as a `block` (the core `trip.before` hook can only
21
+ * short-circuit, not rewrite-and-continue — see {@link PiiDetectorOptions}).
22
+ * - **`block`** — a hard stop carrying the matches.
23
+ * - **`flag`** — the content passes but the matches are recorded.
24
+ *
25
+ * Clean text returns `{ type: "allow" }`.
26
+ *
27
+ * @example
28
+ * ai.guardrail({ output: [ai.guardrail.pii()] }); // redact, default mask
29
+ *
30
+ * @example
31
+ * ai.guardrail.pii({
32
+ * detect: ["ssn", "credit-card"],
33
+ * onMatch: "redact",
34
+ * mask: "[PII:{label}]",
35
+ * dictionary: ["Project Aurora"],
36
+ * });
37
+ */
38
+ declare function pii(options?: PiiDetectorOptions): SyncGuardrailDetector;
39
+ //#endregion
40
+ export { pii };
41
+ //# sourceMappingURL=pii.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/guard/detectors/pii.ts"],"mappings":";;;;;;;AAqOA;;;;;;;;AAA4E;;;;;;;;;;;;;;;;;;;;;;iBAA5D,GAAA,CAAI,OAAA,GAAS,kBAAA,GAA0B,qBAAqB"}
@@ -0,0 +1,199 @@
1
+ //#region ../@warlock.js/ai/src/guard/detectors/pii.ts
2
+ /** Detector name, used as the namespace prefix on every {@link GuardrailMatch.rule}. */
3
+ const DETECTOR_NAME = "pii";
4
+ /** Placeholder substituted for a matched span when the caller supplies no `mask`. */
5
+ const DEFAULT_MASK = "[REDACTED]";
6
+ /**
7
+ * The built-in PII category regexes. Each is linear (anchored alternations,
8
+ * no nested quantifiers) so it is safe against catastrophic backtracking on
9
+ * adversarial input. All carry the global flag so a single pass over the
10
+ * text yields every occurrence; `lastIndex` is reset per use so a shared
11
+ * instance never leaks state across calls.
12
+ *
13
+ * - `ssn` — US Social Security number, `123-45-6789` / `123 45 6789`.
14
+ * - `email` — a pragmatic address shape, not full RFC 5322.
15
+ * - `phone` — North-American style, optional `+1`, separators, parens.
16
+ * - `credit-card` — 13–16 digit runs, optional space / hyphen grouping.
17
+ * - `ipv4` — four dotted octets (loosely; out-of-range octets still match).
18
+ */
19
+ const CATEGORY_PATTERNS = {
20
+ ssn: /\b\d{3}[-\s]\d{2}[-\s]\d{4}\b/g,
21
+ email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
22
+ phone: /(?:\+?1[-.\s]?)?(?:\(\d{3}\)|\d{3})[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
23
+ "credit-card": /\b(?:\d[ -]?){13,16}\b/g,
24
+ ipv4: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g
25
+ };
26
+ /** Every built-in category, in a stable scan order. */
27
+ const ALL_CATEGORIES = [
28
+ "ssn",
29
+ "email",
30
+ "phone",
31
+ "credit-card",
32
+ "ipv4"
33
+ ];
34
+ /**
35
+ * Escape a string for safe interpolation into a `RegExp` source, so an
36
+ * extra dictionary term containing regex metacharacters (`.`, `+`, `(`, …)
37
+ * matches literally rather than as a pattern.
38
+ */
39
+ function escapeRegExp(term) {
40
+ return term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
41
+ }
42
+ /**
43
+ * Build the `{label}` mask for a hit. The template's `{label}` token is
44
+ * substituted with the hit's category; a template without the token is used
45
+ * verbatim. Falls back to {@link DEFAULT_MASK} when no template is given.
46
+ */
47
+ function applyMask(template, label) {
48
+ if (template === void 0) return DEFAULT_MASK;
49
+ return template.replace(/\{label\}/g, label);
50
+ }
51
+ /**
52
+ * Collect every built-in-category hit in `text` for the requested
53
+ * categories, in document order per category.
54
+ */
55
+ function scanCategories(text, categories) {
56
+ const hits = [];
57
+ for (const category of categories) {
58
+ const pattern = CATEGORY_PATTERNS[category];
59
+ pattern.lastIndex = 0;
60
+ let match = pattern.exec(text);
61
+ while (match !== null) {
62
+ hits.push({
63
+ label: category,
64
+ start: match.index,
65
+ end: match.index + match[0].length - 1
66
+ });
67
+ if (match[0].length === 0) pattern.lastIndex += 1;
68
+ match = pattern.exec(text);
69
+ }
70
+ }
71
+ return hits;
72
+ }
73
+ /**
74
+ * Collect every occurrence of each extra dictionary term in `text`,
75
+ * case-insensitively, as `"dictionary"`-labelled hits.
76
+ */
77
+ function scanDictionary(text, dictionary) {
78
+ const hits = [];
79
+ for (const term of dictionary) {
80
+ if (term.length === 0) continue;
81
+ const pattern = new RegExp(escapeRegExp(term), "gi");
82
+ let match = pattern.exec(text);
83
+ while (match !== null) {
84
+ hits.push({
85
+ label: "dictionary",
86
+ start: match.index,
87
+ end: match.index + match[0].length - 1
88
+ });
89
+ match = pattern.exec(text);
90
+ }
91
+ }
92
+ return hits;
93
+ }
94
+ /**
95
+ * Sort hits by start offset, then drop any hit fully contained in (or
96
+ * duplicating) an already-kept span. Different category regexes can overlap
97
+ * on the same characters (e.g. a credit-card run inside a phone-shaped
98
+ * span); keeping the earliest, widest span makes redaction deterministic
99
+ * and avoids masking a sub-span twice.
100
+ */
101
+ function dedupeHits(hits) {
102
+ const sorted = [...hits].sort((a, b) => {
103
+ if (a.start !== b.start) return a.start - b.start;
104
+ return b.end - a.end;
105
+ });
106
+ const kept = [];
107
+ for (const hit of sorted) if (!kept.some((existing) => hit.start <= existing.end && hit.end >= existing.start)) kept.push(hit);
108
+ return kept;
109
+ }
110
+ /**
111
+ * Rewrite `text`, replacing every kept hit's span with its mask. Applied
112
+ * right-to-left so earlier offsets stay valid as later spans are spliced.
113
+ */
114
+ function redactText(text, hits, mask) {
115
+ const ordered = [...hits].sort((a, b) => b.start - a.start);
116
+ let result = text;
117
+ for (const hit of ordered) {
118
+ const replacement = applyMask(mask, hit.label);
119
+ result = result.slice(0, hit.start) + replacement + result.slice(hit.end + 1);
120
+ }
121
+ return result;
122
+ }
123
+ /** Fold a {@link RawHit} into the public {@link GuardrailMatch} shape. */
124
+ function toMatch(hit) {
125
+ return {
126
+ rule: `${DETECTOR_NAME}.${hit.label}`,
127
+ span: [hit.start, hit.end],
128
+ label: hit.label
129
+ };
130
+ }
131
+ /**
132
+ * Build the built-in **PII detector** (`ai.guardrail.pii`) — a zero-runtime-
133
+ * dependency {@link GuardrailDetector} that scans text for personally
134
+ * identifiable information via a curated set of linear regexes plus an
135
+ * optional exact-string dictionary.
136
+ *
137
+ * Categories (`detect`, default: all): `ssn`, `email`, `phone`,
138
+ * `credit-card`, `ipv4`. `dictionary` adds extra exact terms matched
139
+ * case-insensitively as literal strings (regex metacharacters escaped).
140
+ *
141
+ * On a hit the verdict follows `onMatch` (default `"redact"`):
142
+ *
143
+ * - **`redact`** — every matched span is replaced by the `mask` template
144
+ * (`{label}` → the matched category, default `"[REDACTED]"`) and the
145
+ * rewritten text is returned for the factory to substitute. Output and
146
+ * tool phases honour the rewrite; on the input phase the factory treats a
147
+ * `redact` verdict as a `block` (the core `trip.before` hook can only
148
+ * short-circuit, not rewrite-and-continue — see {@link PiiDetectorOptions}).
149
+ * - **`block`** — a hard stop carrying the matches.
150
+ * - **`flag`** — the content passes but the matches are recorded.
151
+ *
152
+ * Clean text returns `{ type: "allow" }`.
153
+ *
154
+ * @example
155
+ * ai.guardrail({ output: [ai.guardrail.pii()] }); // redact, default mask
156
+ *
157
+ * @example
158
+ * ai.guardrail.pii({
159
+ * detect: ["ssn", "credit-card"],
160
+ * onMatch: "redact",
161
+ * mask: "[PII:{label}]",
162
+ * dictionary: ["Project Aurora"],
163
+ * });
164
+ */
165
+ function pii(options = {}) {
166
+ const categories = options.detect ?? ALL_CATEGORIES;
167
+ const onMatch = options.onMatch ?? "redact";
168
+ const dictionary = options.dictionary ?? [];
169
+ return {
170
+ name: DETECTOR_NAME,
171
+ check(text) {
172
+ const rawHits = [...scanCategories(text, categories), ...scanDictionary(text, dictionary)];
173
+ if (rawHits.length === 0) return { type: "allow" };
174
+ const hits = dedupeHits(rawHits);
175
+ const matches = hits.map(toMatch);
176
+ const labels = [...new Set(hits.map((hit) => hit.label))].join(", ");
177
+ if (onMatch === "block") return {
178
+ type: "block",
179
+ reason: `PII detected: ${labels}.`,
180
+ matches
181
+ };
182
+ if (onMatch === "flag") return {
183
+ type: "flag",
184
+ reason: `PII detected: ${labels}.`,
185
+ matches
186
+ };
187
+ return {
188
+ type: "redact",
189
+ text: redactText(text, hits, options.mask),
190
+ reason: `Redacted PII: ${labels}.`,
191
+ matches
192
+ };
193
+ }
194
+ };
195
+ }
196
+
197
+ //#endregion
198
+ export { pii };
199
+ //# sourceMappingURL=pii.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/guard/detectors/pii.ts"],"sourcesContent":["import type {\n GuardrailMatch,\n GuardrailVerdict,\n PiiCategory,\n PiiDetectorOptions,\n SyncGuardrailDetector,\n} from \"../contracts\";\n\n/** Detector name, used as the namespace prefix on every {@link GuardrailMatch.rule}. */\nconst DETECTOR_NAME = \"pii\";\n\n/** Placeholder substituted for a matched span when the caller supplies no `mask`. */\nconst DEFAULT_MASK = \"[REDACTED]\";\n\n/**\n * The built-in PII category regexes. Each is linear (anchored alternations,\n * no nested quantifiers) so it is safe against catastrophic backtracking on\n * adversarial input. All carry the global flag so a single pass over the\n * text yields every occurrence; `lastIndex` is reset per use so a shared\n * instance never leaks state across calls.\n *\n * - `ssn` — US Social Security number, `123-45-6789` / `123 45 6789`.\n * - `email` — a pragmatic address shape, not full RFC 5322.\n * - `phone` — North-American style, optional `+1`, separators, parens.\n * - `credit-card` — 13–16 digit runs, optional space / hyphen grouping.\n * - `ipv4` — four dotted octets (loosely; out-of-range octets still match).\n */\nconst CATEGORY_PATTERNS: Record<PiiCategory, RegExp> = {\n ssn: /\\b\\d{3}[-\\s]\\d{2}[-\\s]\\d{4}\\b/g,\n email: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g,\n phone: /(?:\\+?1[-.\\s]?)?(?:\\(\\d{3}\\)|\\d{3})[-.\\s]?\\d{3}[-.\\s]?\\d{4}\\b/g,\n \"credit-card\": /\\b(?:\\d[ -]?){13,16}\\b/g,\n ipv4: /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g,\n};\n\n/** Every built-in category, in a stable scan order. */\nconst ALL_CATEGORIES: readonly PiiCategory[] = [\n \"ssn\",\n \"email\",\n \"phone\",\n \"credit-card\",\n \"ipv4\",\n];\n\n/**\n * A raw hit located inside the inspected text, before it is folded into a\n * {@link GuardrailMatch}. `label` is the category (built-in) or\n * `\"dictionary\"` (an extra term); `start` / `end` are inclusive offsets.\n */\ninterface RawHit {\n readonly label: string;\n readonly start: number;\n readonly end: number;\n}\n\n/**\n * Escape a string for safe interpolation into a `RegExp` source, so an\n * extra dictionary term containing regex metacharacters (`.`, `+`, `(`, …)\n * matches literally rather than as a pattern.\n */\nfunction escapeRegExp(term: string): string {\n return term.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Build the `{label}` mask for a hit. The template's `{label}` token is\n * substituted with the hit's category; a template without the token is used\n * verbatim. Falls back to {@link DEFAULT_MASK} when no template is given.\n */\nfunction applyMask(template: string | undefined, label: string): string {\n if (template === undefined) {\n return DEFAULT_MASK;\n }\n\n return template.replace(/\\{label\\}/g, label);\n}\n\n/**\n * Collect every built-in-category hit in `text` for the requested\n * categories, in document order per category.\n */\nfunction scanCategories(text: string, categories: readonly PiiCategory[]): RawHit[] {\n const hits: RawHit[] = [];\n\n for (const category of categories) {\n const pattern = CATEGORY_PATTERNS[category];\n pattern.lastIndex = 0;\n\n let match = pattern.exec(text);\n\n while (match !== null) {\n hits.push({\n label: category,\n start: match.index,\n end: match.index + match[0].length - 1,\n });\n\n // Guard the zero-length-match case so `exec` can never spin forever.\n if (match[0].length === 0) {\n pattern.lastIndex += 1;\n }\n\n match = pattern.exec(text);\n }\n }\n\n return hits;\n}\n\n/**\n * Collect every occurrence of each extra dictionary term in `text`,\n * case-insensitively, as `\"dictionary\"`-labelled hits.\n */\nfunction scanDictionary(text: string, dictionary: readonly string[]): RawHit[] {\n const hits: RawHit[] = [];\n\n for (const term of dictionary) {\n if (term.length === 0) {\n continue;\n }\n\n const pattern = new RegExp(escapeRegExp(term), \"gi\");\n let match = pattern.exec(text);\n\n while (match !== null) {\n hits.push({\n label: \"dictionary\",\n start: match.index,\n end: match.index + match[0].length - 1,\n });\n\n match = pattern.exec(text);\n }\n }\n\n return hits;\n}\n\n/**\n * Sort hits by start offset, then drop any hit fully contained in (or\n * duplicating) an already-kept span. Different category regexes can overlap\n * on the same characters (e.g. a credit-card run inside a phone-shaped\n * span); keeping the earliest, widest span makes redaction deterministic\n * and avoids masking a sub-span twice.\n */\nfunction dedupeHits(hits: RawHit[]): RawHit[] {\n const sorted = [...hits].sort((a, b) => {\n if (a.start !== b.start) {\n return a.start - b.start;\n }\n\n // Same start: keep the wider span first so the narrower one is absorbed.\n return b.end - a.end;\n });\n\n const kept: RawHit[] = [];\n\n for (const hit of sorted) {\n const overlaps = kept.some(\n existing => hit.start <= existing.end && hit.end >= existing.start,\n );\n\n if (!overlaps) {\n kept.push(hit);\n }\n }\n\n return kept;\n}\n\n/**\n * Rewrite `text`, replacing every kept hit's span with its mask. Applied\n * right-to-left so earlier offsets stay valid as later spans are spliced.\n */\nfunction redactText(text: string, hits: RawHit[], mask: string | undefined): string {\n const ordered = [...hits].sort((a, b) => b.start - a.start);\n let result = text;\n\n for (const hit of ordered) {\n const replacement = applyMask(mask, hit.label);\n result = result.slice(0, hit.start) + replacement + result.slice(hit.end + 1);\n }\n\n return result;\n}\n\n/** Fold a {@link RawHit} into the public {@link GuardrailMatch} shape. */\nfunction toMatch(hit: RawHit): GuardrailMatch {\n return {\n rule: `${DETECTOR_NAME}.${hit.label}`,\n span: [hit.start, hit.end],\n label: hit.label,\n };\n}\n\n/**\n * Build the built-in **PII detector** (`ai.guardrail.pii`) — a zero-runtime-\n * dependency {@link GuardrailDetector} that scans text for personally\n * identifiable information via a curated set of linear regexes plus an\n * optional exact-string dictionary.\n *\n * Categories (`detect`, default: all): `ssn`, `email`, `phone`,\n * `credit-card`, `ipv4`. `dictionary` adds extra exact terms matched\n * case-insensitively as literal strings (regex metacharacters escaped).\n *\n * On a hit the verdict follows `onMatch` (default `\"redact\"`):\n *\n * - **`redact`** — every matched span is replaced by the `mask` template\n * (`{label}` → the matched category, default `\"[REDACTED]\"`) and the\n * rewritten text is returned for the factory to substitute. Output and\n * tool phases honour the rewrite; on the input phase the factory treats a\n * `redact` verdict as a `block` (the core `trip.before` hook can only\n * short-circuit, not rewrite-and-continue — see {@link PiiDetectorOptions}).\n * - **`block`** — a hard stop carrying the matches.\n * - **`flag`** — the content passes but the matches are recorded.\n *\n * Clean text returns `{ type: \"allow\" }`.\n *\n * @example\n * ai.guardrail({ output: [ai.guardrail.pii()] }); // redact, default mask\n *\n * @example\n * ai.guardrail.pii({\n * detect: [\"ssn\", \"credit-card\"],\n * onMatch: \"redact\",\n * mask: \"[PII:{label}]\",\n * dictionary: [\"Project Aurora\"],\n * });\n */\nexport function pii(options: PiiDetectorOptions = {}): SyncGuardrailDetector {\n const categories = options.detect ?? ALL_CATEGORIES;\n const onMatch = options.onMatch ?? \"redact\";\n const dictionary = options.dictionary ?? [];\n\n return {\n name: DETECTOR_NAME,\n check(text: string): GuardrailVerdict {\n const rawHits = [\n ...scanCategories(text, categories),\n ...scanDictionary(text, dictionary),\n ];\n\n if (rawHits.length === 0) {\n return { type: \"allow\" };\n }\n\n const hits = dedupeHits(rawHits);\n const matches = hits.map(toMatch);\n const labels = [...new Set(hits.map(hit => hit.label))].join(\", \");\n\n if (onMatch === \"block\") {\n return {\n type: \"block\",\n reason: `PII detected: ${labels}.`,\n matches,\n };\n }\n\n if (onMatch === \"flag\") {\n return {\n type: \"flag\",\n reason: `PII detected: ${labels}.`,\n matches,\n };\n }\n\n return {\n type: \"redact\",\n text: redactText(text, hits, options.mask),\n reason: `Redacted PII: ${labels}.`,\n matches,\n };\n },\n };\n}\n"],"mappings":";;AASA,MAAM,gBAAgB;;AAGtB,MAAM,eAAe;;;;;;;;;;;;;;AAerB,MAAM,oBAAiD;CACrD,KAAK;CACL,OAAO;CACP,OAAO;CACP,eAAe;CACf,MAAM;AACR;;AAGA,MAAM,iBAAyC;CAC7C;CACA;CACA;CACA;CACA;AACF;;;;;;AAkBA,SAAS,aAAa,MAAsB;CAC1C,OAAO,KAAK,QAAQ,uBAAuB,MAAM;AACnD;;;;;;AAOA,SAAS,UAAU,UAA8B,OAAuB;CACtE,IAAI,aAAa,QACf,OAAO;CAGT,OAAO,SAAS,QAAQ,cAAc,KAAK;AAC7C;;;;;AAMA,SAAS,eAAe,MAAc,YAA8C;CAClF,MAAM,OAAiB,CAAC;CAExB,KAAK,MAAM,YAAY,YAAY;EACjC,MAAM,UAAU,kBAAkB;EAClC,QAAQ,YAAY;EAEpB,IAAI,QAAQ,QAAQ,KAAK,IAAI;EAE7B,OAAO,UAAU,MAAM;GACrB,KAAK,KAAK;IACR,OAAO;IACP,OAAO,MAAM;IACb,KAAK,MAAM,QAAQ,MAAM,EAAE,CAAC,SAAS;GACvC,CAAC;GAGD,IAAI,MAAM,EAAE,CAAC,WAAW,GACtB,QAAQ,aAAa;GAGvB,QAAQ,QAAQ,KAAK,IAAI;EAC3B;CACF;CAEA,OAAO;AACT;;;;;AAMA,SAAS,eAAe,MAAc,YAAyC;CAC7E,MAAM,OAAiB,CAAC;CAExB,KAAK,MAAM,QAAQ,YAAY;EAC7B,IAAI,KAAK,WAAW,GAClB;EAGF,MAAM,UAAU,IAAI,OAAO,aAAa,IAAI,GAAG,IAAI;EACnD,IAAI,QAAQ,QAAQ,KAAK,IAAI;EAE7B,OAAO,UAAU,MAAM;GACrB,KAAK,KAAK;IACR,OAAO;IACP,OAAO,MAAM;IACb,KAAK,MAAM,QAAQ,MAAM,EAAE,CAAC,SAAS;GACvC,CAAC;GAED,QAAQ,QAAQ,KAAK,IAAI;EAC3B;CACF;CAEA,OAAO;AACT;;;;;;;;AASA,SAAS,WAAW,MAA0B;CAC5C,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM;EACtC,IAAI,EAAE,UAAU,EAAE,OAChB,OAAO,EAAE,QAAQ,EAAE;EAIrB,OAAO,EAAE,MAAM,EAAE;CACnB,CAAC;CAED,MAAM,OAAiB,CAAC;CAExB,KAAK,MAAM,OAAO,QAKhB,IAAI,CAJa,KAAK,MACpB,aAAY,IAAI,SAAS,SAAS,OAAO,IAAI,OAAO,SAAS,KAGnD,GACV,KAAK,KAAK,GAAG;CAIjB,OAAO;AACT;;;;;AAMA,SAAS,WAAW,MAAc,MAAgB,MAAkC;CAClF,MAAM,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAC1D,IAAI,SAAS;CAEb,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,cAAc,UAAU,MAAM,IAAI,KAAK;EAC7C,SAAS,OAAO,MAAM,GAAG,IAAI,KAAK,IAAI,cAAc,OAAO,MAAM,IAAI,MAAM,CAAC;CAC9E;CAEA,OAAO;AACT;;AAGA,SAAS,QAAQ,KAA6B;CAC5C,OAAO;EACL,MAAM,GAAG,cAAc,GAAG,IAAI;EAC9B,MAAM,CAAC,IAAI,OAAO,IAAI,GAAG;EACzB,OAAO,IAAI;CACb;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,IAAI,UAA8B,CAAC,GAA0B;CAC3E,MAAM,aAAa,QAAQ,UAAU;CACrC,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,aAAa,QAAQ,cAAc,CAAC;CAE1C,OAAO;EACL,MAAM;EACN,MAAM,MAAgC;GACpC,MAAM,UAAU,CACd,GAAG,eAAe,MAAM,UAAU,GAClC,GAAG,eAAe,MAAM,UAAU,CACpC;GAEA,IAAI,QAAQ,WAAW,GACrB,OAAO,EAAE,MAAM,QAAQ;GAGzB,MAAM,OAAO,WAAW,OAAO;GAC/B,MAAM,UAAU,KAAK,IAAI,OAAO;GAChC,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,KAAK,KAAI,QAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;GAEjE,IAAI,YAAY,SACd,OAAO;IACL,MAAM;IACN,QAAQ,iBAAiB,OAAO;IAChC;GACF;GAGF,IAAI,YAAY,QACd,OAAO;IACL,MAAM;IACN,QAAQ,iBAAiB,OAAO;IAChC;GACF;GAGF,OAAO;IACL,MAAM;IACN,MAAM,WAAW,MAAM,MAAM,QAAQ,IAAI;IACzC,QAAQ,iBAAiB,OAAO;IAChC;GACF;EACF;CACF;AACF"}
@@ -0,0 +1,29 @@
1
+ import { SyncGuardrailDetector } from "../contracts/guardrail.contract.mjs";
2
+ import { TopicFilterOptions } from "../contracts/guard-options.type.mjs";
3
+ //#region ../@warlock.js/ai/src/guard/detectors/topic.d.ts
4
+ /**
5
+ * Build the built-in **topic filter** (`ai.guardrail.topic`) — a
6
+ * zero-runtime-dependency {@link GuardrailDetector} that gates text against a
7
+ * deny list, an allow list, or both.
8
+ *
9
+ * - **`deny`** — any term that appears triggers `onMatch`. A `string`
10
+ * matches case-insensitively as a substring; a `RegExp` is tested as-is.
11
+ * The deny list is checked first; the first hit decides the verdict.
12
+ * - **`allow`** — when set, text matching **none** of the allow terms
13
+ * triggers `onMatch` (an allow-list miss). Text matching at least one
14
+ * allow term passes the allow gate.
15
+ *
16
+ * `onMatch` is `"block"` (default) or `"flag"`. With neither list supplied
17
+ * the detector is a no-op that always allows.
18
+ *
19
+ * @example
20
+ * ai.guardrail.topic({ deny: ["medical advice", /diagnos\w+/i] });
21
+ *
22
+ * @example
23
+ * // Stay on-topic: anything not about billing is flagged.
24
+ * ai.guardrail.topic({ allow: ["billing", "invoice", "refund"], onMatch: "flag" });
25
+ */
26
+ declare function topic(options: TopicFilterOptions): SyncGuardrailDetector;
27
+ //#endregion
28
+ export { topic };
29
+ //# sourceMappingURL=topic.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/guard/detectors/topic.ts"],"mappings":";;;;;;;AAsEA;;;;;;;;AAAyE;;;;;;;;;;iBAAzD,KAAA,CAAM,OAAA,EAAS,kBAAA,GAAqB,qBAAqB"}
@@ -0,0 +1,99 @@
1
+ //#region ../@warlock.js/ai/src/guard/detectors/topic.ts
2
+ /** Detector name, used as the namespace prefix on every {@link GuardrailMatch.rule}. */
3
+ const DETECTOR_NAME = "topic";
4
+ /**
5
+ * Locate the first occurrence of `term` in `text`. A `string` matches
6
+ * case-insensitively as a substring; a `RegExp` is tested as-is (its own
7
+ * flags decide case-sensitivity). Returns the inclusive `[start, end]`
8
+ * span on a hit, or `undefined` when the term is absent.
9
+ */
10
+ function locate(text, term) {
11
+ if (typeof term === "string") {
12
+ if (term.length === 0) return;
13
+ const index = text.toLowerCase().indexOf(term.toLowerCase());
14
+ if (index === -1) return;
15
+ return [index, index + term.length - 1];
16
+ }
17
+ const match = new RegExp(term.source, term.flags.replace(/[gy]/g, "")).exec(text);
18
+ if (match === null) return;
19
+ return [match.index, match.index + match[0].length - 1];
20
+ }
21
+ /** A human-readable label for a deny/allow term, used in the match rule + reason. */
22
+ function describeTerm(term) {
23
+ return typeof term === "string" ? term : term.source;
24
+ }
25
+ /**
26
+ * Build the built-in **topic filter** (`ai.guardrail.topic`) — a
27
+ * zero-runtime-dependency {@link GuardrailDetector} that gates text against a
28
+ * deny list, an allow list, or both.
29
+ *
30
+ * - **`deny`** — any term that appears triggers `onMatch`. A `string`
31
+ * matches case-insensitively as a substring; a `RegExp` is tested as-is.
32
+ * The deny list is checked first; the first hit decides the verdict.
33
+ * - **`allow`** — when set, text matching **none** of the allow terms
34
+ * triggers `onMatch` (an allow-list miss). Text matching at least one
35
+ * allow term passes the allow gate.
36
+ *
37
+ * `onMatch` is `"block"` (default) or `"flag"`. With neither list supplied
38
+ * the detector is a no-op that always allows.
39
+ *
40
+ * @example
41
+ * ai.guardrail.topic({ deny: ["medical advice", /diagnos\w+/i] });
42
+ *
43
+ * @example
44
+ * // Stay on-topic: anything not about billing is flagged.
45
+ * ai.guardrail.topic({ allow: ["billing", "invoice", "refund"], onMatch: "flag" });
46
+ */
47
+ function topic(options) {
48
+ const deny = options.deny ?? [];
49
+ const allow = options.allow ?? [];
50
+ const onMatch = options.onMatch ?? "block";
51
+ return {
52
+ name: DETECTOR_NAME,
53
+ check(text) {
54
+ for (const term of deny) {
55
+ const span = locate(text, term);
56
+ if (span !== void 0) {
57
+ const label = describeTerm(term);
58
+ const match = {
59
+ rule: `${DETECTOR_NAME}.deny.${label}`,
60
+ span,
61
+ label
62
+ };
63
+ return verdict(onMatch, options.reason ?? `Denied topic matched: ${label}.`, [match]);
64
+ }
65
+ }
66
+ if (allow.length > 0) {
67
+ if (!allow.some((term) => locate(text, term) !== void 0)) {
68
+ const match = {
69
+ rule: `${DETECTOR_NAME}.allow.miss`,
70
+ label: "allow-miss"
71
+ };
72
+ return verdict(onMatch, options.reason ?? "Text matched none of the allowed topics.", [match]);
73
+ }
74
+ }
75
+ return { type: "allow" };
76
+ }
77
+ };
78
+ }
79
+ /**
80
+ * Fold the resolved action into a `block` or `flag` verdict. Topic never
81
+ * redacts — it cannot meaningfully rewrite a whole-text policy miss — so the
82
+ * action is constrained to `"block" | "flag"` at the type level.
83
+ */
84
+ function verdict(action, reason, matches) {
85
+ if (action === "block") return {
86
+ type: "block",
87
+ reason,
88
+ matches
89
+ };
90
+ return {
91
+ type: "flag",
92
+ reason,
93
+ matches
94
+ };
95
+ }
96
+
97
+ //#endregion
98
+ export { topic };
99
+ //# sourceMappingURL=topic.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/guard/detectors/topic.ts"],"sourcesContent":["import type {\n GuardrailMatch,\n GuardrailVerdict,\n SyncGuardrailDetector,\n TopicFilterOptions,\n} from \"../contracts\";\n\n/** Detector name, used as the namespace prefix on every {@link GuardrailMatch.rule}. */\nconst DETECTOR_NAME = \"topic\";\n\n/**\n * Locate the first occurrence of `term` in `text`. A `string` matches\n * case-insensitively as a substring; a `RegExp` is tested as-is (its own\n * flags decide case-sensitivity). Returns the inclusive `[start, end]`\n * span on a hit, or `undefined` when the term is absent.\n */\nfunction locate(text: string, term: string | RegExp): readonly [number, number] | undefined {\n if (typeof term === \"string\") {\n if (term.length === 0) {\n return undefined;\n }\n\n const index = text.toLowerCase().indexOf(term.toLowerCase());\n\n if (index === -1) {\n return undefined;\n }\n\n return [index, index + term.length - 1];\n }\n\n // RegExp: run a non-global copy so a caller-supplied `/g` term cannot leak\n // `lastIndex` between calls and so `.exec` reports a deterministic first hit.\n const probe = new RegExp(term.source, term.flags.replace(/[gy]/g, \"\"));\n const match = probe.exec(text);\n\n if (match === null) {\n return undefined;\n }\n\n return [match.index, match.index + match[0].length - 1];\n}\n\n/** A human-readable label for a deny/allow term, used in the match rule + reason. */\nfunction describeTerm(term: string | RegExp): string {\n return typeof term === \"string\" ? term : term.source;\n}\n\n/**\n * Build the built-in **topic filter** (`ai.guardrail.topic`) — a\n * zero-runtime-dependency {@link GuardrailDetector} that gates text against a\n * deny list, an allow list, or both.\n *\n * - **`deny`** — any term that appears triggers `onMatch`. A `string`\n * matches case-insensitively as a substring; a `RegExp` is tested as-is.\n * The deny list is checked first; the first hit decides the verdict.\n * - **`allow`** — when set, text matching **none** of the allow terms\n * triggers `onMatch` (an allow-list miss). Text matching at least one\n * allow term passes the allow gate.\n *\n * `onMatch` is `\"block\"` (default) or `\"flag\"`. With neither list supplied\n * the detector is a no-op that always allows.\n *\n * @example\n * ai.guardrail.topic({ deny: [\"medical advice\", /diagnos\\w+/i] });\n *\n * @example\n * // Stay on-topic: anything not about billing is flagged.\n * ai.guardrail.topic({ allow: [\"billing\", \"invoice\", \"refund\"], onMatch: \"flag\" });\n */\nexport function topic(options: TopicFilterOptions): SyncGuardrailDetector {\n const deny = options.deny ?? [];\n const allow = options.allow ?? [];\n const onMatch = options.onMatch ?? \"block\";\n\n return {\n name: DETECTOR_NAME,\n check(text: string): GuardrailVerdict {\n // Deny list: the first present term decides the verdict.\n for (const term of deny) {\n const span = locate(text, term);\n\n if (span !== undefined) {\n const label = describeTerm(term);\n const match: GuardrailMatch = {\n rule: `${DETECTOR_NAME}.deny.${label}`,\n span,\n label,\n };\n const reason = options.reason ?? `Denied topic matched: ${label}.`;\n\n return verdict(onMatch, reason, [match]);\n }\n }\n\n // Allow list: matching NONE of the terms is a miss → trigger onMatch.\n if (allow.length > 0) {\n const matchedAny = allow.some(term => locate(text, term) !== undefined);\n\n if (!matchedAny) {\n const match: GuardrailMatch = {\n rule: `${DETECTOR_NAME}.allow.miss`,\n label: \"allow-miss\",\n };\n const reason =\n options.reason ?? \"Text matched none of the allowed topics.\";\n\n return verdict(onMatch, reason, [match]);\n }\n }\n\n return { type: \"allow\" };\n },\n };\n}\n\n/**\n * Fold the resolved action into a `block` or `flag` verdict. Topic never\n * redacts — it cannot meaningfully rewrite a whole-text policy miss — so the\n * action is constrained to `\"block\" | \"flag\"` at the type level.\n */\nfunction verdict(\n action: \"block\" | \"flag\",\n reason: string,\n matches: readonly GuardrailMatch[],\n): GuardrailVerdict {\n if (action === \"block\") {\n return { type: \"block\", reason, matches };\n }\n\n return { type: \"flag\", reason, matches };\n}\n"],"mappings":";;AAQA,MAAM,gBAAgB;;;;;;;AAQtB,SAAS,OAAO,MAAc,MAA8D;CAC1F,IAAI,OAAO,SAAS,UAAU;EAC5B,IAAI,KAAK,WAAW,GAClB;EAGF,MAAM,QAAQ,KAAK,YAAY,CAAC,CAAC,QAAQ,KAAK,YAAY,CAAC;EAE3D,IAAI,UAAU,IACZ;EAGF,OAAO,CAAC,OAAO,QAAQ,KAAK,SAAS,CAAC;CACxC;CAKA,MAAM,QAAQ,IADI,OAAO,KAAK,QAAQ,KAAK,MAAM,QAAQ,SAAS,EAAE,CAClD,CAAC,CAAC,KAAK,IAAI;CAE7B,IAAI,UAAU,MACZ;CAGF,OAAO,CAAC,MAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC,SAAS,CAAC;AACxD;;AAGA,SAAS,aAAa,MAA+B;CACnD,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK;AAChD;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,MAAM,SAAoD;CACxE,MAAM,OAAO,QAAQ,QAAQ,CAAC;CAC9B,MAAM,QAAQ,QAAQ,SAAS,CAAC;CAChC,MAAM,UAAU,QAAQ,WAAW;CAEnC,OAAO;EACL,MAAM;EACN,MAAM,MAAgC;GAEpC,KAAK,MAAM,QAAQ,MAAM;IACvB,MAAM,OAAO,OAAO,MAAM,IAAI;IAE9B,IAAI,SAAS,QAAW;KACtB,MAAM,QAAQ,aAAa,IAAI;KAC/B,MAAM,QAAwB;MAC5B,MAAM,GAAG,cAAc,QAAQ;MAC/B;MACA;KACF;KAGA,OAAO,QAAQ,SAFA,QAAQ,UAAU,yBAAyB,MAAM,IAEhC,CAAC,KAAK,CAAC;IACzC;GACF;GAGA,IAAI,MAAM,SAAS,GAGjB;QAAI,CAFe,MAAM,MAAK,SAAQ,OAAO,MAAM,IAAI,MAAM,MAE/C,GAAG;KACf,MAAM,QAAwB;MAC5B,MAAM,GAAG,cAAc;MACvB,OAAO;KACT;KAIA,OAAO,QAAQ,SAFb,QAAQ,UAAU,4CAEY,CAAC,KAAK,CAAC;IACzC;;GAGF,OAAO,EAAE,MAAM,QAAQ;EACzB;CACF;AACF;;;;;;AAOA,SAAS,QACP,QACA,QACA,SACkB;CAClB,IAAI,WAAW,SACb,OAAO;EAAE,MAAM;EAAS;EAAQ;CAAQ;CAG1C,OAAO;EAAE,MAAM;EAAQ;EAAQ;CAAQ;AACzC"}
@@ -0,0 +1,12 @@
1
+ import { GuardrailViolationError, GuardrailViolationErrorOptions } from "../errors/guardrail-violation-error.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/guard/errors.d.ts
4
+ /**
5
+ * Curated install string thrown by the optional `moderation` detector on
6
+ * first `check()` when the `openai` peer is absent. Mirrors ai-panoptic's
7
+ * `LANGFUSE_INSTALL_INSTRUCTIONS`.
8
+ */
9
+ declare const OPENAI_INSTALL_INSTRUCTIONS: string;
10
+ //#endregion
11
+ export { OPENAI_INSTALL_INSTRUCTIONS };
12
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai/src/guard/errors.ts"],"mappings":";;;;;;;;cAuBa,2BAAA"}
@@ -0,0 +1,18 @@
1
+ import { GuardrailViolationError } from "../errors/guardrail-violation-error.mjs";
2
+
3
+ //#region ../@warlock.js/ai/src/guard/errors.ts
4
+ /**
5
+ * Curated install string thrown by the optional `moderation` detector on
6
+ * first `check()` when the `openai` peer is absent. Mirrors ai-panoptic's
7
+ * `LANGFUSE_INSTALL_INSTRUCTIONS`.
8
+ */
9
+ const OPENAI_INSTALL_INSTRUCTIONS = `
10
+ The @warlock.js/ai moderation detector requires the optional "openai" peer.
11
+ Install it with:
12
+
13
+ npm install openai
14
+ `.trim();
15
+
16
+ //#endregion
17
+ export { OPENAI_INSTALL_INSTRUCTIONS };
18
+ //# sourceMappingURL=errors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/guard/errors.ts"],"sourcesContent":["/**\n * Error surface for `@warlock.js/ai`.\n *\n * **No new error class.** A `block` verdict reuses the existing\n * `@warlock.js/ai` {@link GuardrailViolationError} verbatim — its category\n * (`\"guardrail\"`) and `phase` field already model exactly what a guard\n * needs, and `phase` is widened by this package to include `\"tool\"` (a\n * source-compatible third value). Re-exported here so the future `guard()`\n * factory has one import site for the typed abort it throws.\n *\n * The optional `moderation` detector's missing-peer failure is an\n * *infrastructure* fault, not a content violation, so it throws a plain\n * `Error` carrying {@link OPENAI_INSTALL_INSTRUCTIONS} (the langfuse-style\n * lazy-import pattern) rather than an `AIError`.\n */\nexport { GuardrailViolationError } from \"../errors/guardrail-violation-error\";\nexport type { GuardrailViolationErrorOptions } from \"../errors/guardrail-violation-error\";\n\n/**\n * Curated install string thrown by the optional `moderation` detector on\n * first `check()` when the `openai` peer is absent. Mirrors ai-panoptic's\n * `LANGFUSE_INSTALL_INSTRUCTIONS`.\n */\nexport const OPENAI_INSTALL_INSTRUCTIONS = `\nThe @warlock.js/ai moderation detector requires the optional \"openai\" peer.\nInstall it with:\n\n npm install openai\n`.trim();\n"],"mappings":";;;;;;;;AAuBA,MAAa,8BAA8B;;;;;EAKzC,KAAK"}