@librechat/agents 3.1.75 → 3.1.77-dev.1

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 (272) hide show
  1. package/dist/cjs/graphs/Graph.cjs +22 -3
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hitl/askUserQuestion.cjs +67 -0
  4. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -0
  5. package/dist/cjs/hooks/HookRegistry.cjs +54 -0
  6. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  7. package/dist/cjs/hooks/createToolPolicyHook.cjs +115 -0
  8. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -0
  9. package/dist/cjs/hooks/executeHooks.cjs +40 -1
  10. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  11. package/dist/cjs/hooks/types.cjs +1 -0
  12. package/dist/cjs/hooks/types.cjs.map +1 -1
  13. package/dist/cjs/langchain/google-common.cjs +3 -0
  14. package/dist/cjs/langchain/google-common.cjs.map +1 -0
  15. package/dist/cjs/langchain/index.cjs +86 -0
  16. package/dist/cjs/langchain/index.cjs.map +1 -0
  17. package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
  18. package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
  19. package/dist/cjs/langchain/messages/tool.cjs +3 -0
  20. package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
  21. package/dist/cjs/langchain/messages.cjs +51 -0
  22. package/dist/cjs/langchain/messages.cjs.map +1 -0
  23. package/dist/cjs/langchain/openai.cjs +3 -0
  24. package/dist/cjs/langchain/openai.cjs.map +1 -0
  25. package/dist/cjs/langchain/prompts.cjs +11 -0
  26. package/dist/cjs/langchain/prompts.cjs.map +1 -0
  27. package/dist/cjs/langchain/runnables.cjs +19 -0
  28. package/dist/cjs/langchain/runnables.cjs.map +1 -0
  29. package/dist/cjs/langchain/tools.cjs +23 -0
  30. package/dist/cjs/langchain/tools.cjs.map +1 -0
  31. package/dist/cjs/langchain/utils/env.cjs +11 -0
  32. package/dist/cjs/langchain/utils/env.cjs.map +1 -0
  33. package/dist/cjs/llm/anthropic/index.cjs +145 -52
  34. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  35. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  36. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +21 -14
  37. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  38. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
  39. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  40. package/dist/cjs/llm/bedrock/index.cjs +1 -1
  41. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  42. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
  43. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  44. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
  45. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
  46. package/dist/cjs/llm/google/utils/common.cjs +5 -4
  47. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  48. package/dist/cjs/llm/openai/index.cjs +519 -655
  49. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  50. package/dist/cjs/llm/openai/utils/index.cjs +20 -458
  51. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  52. package/dist/cjs/llm/openrouter/index.cjs +57 -175
  53. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  54. package/dist/cjs/llm/vertexai/index.cjs +5 -3
  55. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  56. package/dist/cjs/main.cjs +112 -3
  57. package/dist/cjs/main.cjs.map +1 -1
  58. package/dist/cjs/messages/cache.cjs +2 -1
  59. package/dist/cjs/messages/cache.cjs.map +1 -1
  60. package/dist/cjs/messages/core.cjs +7 -6
  61. package/dist/cjs/messages/core.cjs.map +1 -1
  62. package/dist/cjs/messages/format.cjs +73 -15
  63. package/dist/cjs/messages/format.cjs.map +1 -1
  64. package/dist/cjs/messages/langchain.cjs +26 -0
  65. package/dist/cjs/messages/langchain.cjs.map +1 -0
  66. package/dist/cjs/messages/prune.cjs +7 -6
  67. package/dist/cjs/messages/prune.cjs.map +1 -1
  68. package/dist/cjs/run.cjs +400 -42
  69. package/dist/cjs/run.cjs.map +1 -1
  70. package/dist/cjs/tools/ToolNode.cjs +556 -56
  71. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  72. package/dist/cjs/tools/search/search.cjs +55 -66
  73. package/dist/cjs/tools/search/search.cjs.map +1 -1
  74. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  75. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  76. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  77. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  78. package/dist/cjs/tools/search/tool.cjs +26 -4
  79. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  80. package/dist/cjs/tools/search/utils.cjs +10 -3
  81. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  82. package/dist/esm/graphs/Graph.mjs +22 -3
  83. package/dist/esm/graphs/Graph.mjs.map +1 -1
  84. package/dist/esm/hitl/askUserQuestion.mjs +65 -0
  85. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
  86. package/dist/esm/hooks/HookRegistry.mjs +54 -0
  87. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  88. package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
  89. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
  90. package/dist/esm/hooks/executeHooks.mjs +40 -1
  91. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  92. package/dist/esm/hooks/types.mjs +1 -0
  93. package/dist/esm/hooks/types.mjs.map +1 -1
  94. package/dist/esm/langchain/google-common.mjs +2 -0
  95. package/dist/esm/langchain/google-common.mjs.map +1 -0
  96. package/dist/esm/langchain/index.mjs +5 -0
  97. package/dist/esm/langchain/index.mjs.map +1 -0
  98. package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
  99. package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
  100. package/dist/esm/langchain/messages/tool.mjs +2 -0
  101. package/dist/esm/langchain/messages/tool.mjs.map +1 -0
  102. package/dist/esm/langchain/messages.mjs +2 -0
  103. package/dist/esm/langchain/messages.mjs.map +1 -0
  104. package/dist/esm/langchain/openai.mjs +2 -0
  105. package/dist/esm/langchain/openai.mjs.map +1 -0
  106. package/dist/esm/langchain/prompts.mjs +2 -0
  107. package/dist/esm/langchain/prompts.mjs.map +1 -0
  108. package/dist/esm/langchain/runnables.mjs +2 -0
  109. package/dist/esm/langchain/runnables.mjs.map +1 -0
  110. package/dist/esm/langchain/tools.mjs +2 -0
  111. package/dist/esm/langchain/tools.mjs.map +1 -0
  112. package/dist/esm/langchain/utils/env.mjs +2 -0
  113. package/dist/esm/langchain/utils/env.mjs.map +1 -0
  114. package/dist/esm/llm/anthropic/index.mjs +146 -54
  115. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  116. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  117. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +21 -14
  118. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  119. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
  120. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  121. package/dist/esm/llm/bedrock/index.mjs +1 -1
  122. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  123. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
  124. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  125. package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
  126. package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
  127. package/dist/esm/llm/google/utils/common.mjs +5 -4
  128. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  129. package/dist/esm/llm/openai/index.mjs +520 -656
  130. package/dist/esm/llm/openai/index.mjs.map +1 -1
  131. package/dist/esm/llm/openai/utils/index.mjs +23 -459
  132. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  133. package/dist/esm/llm/openrouter/index.mjs +57 -175
  134. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  135. package/dist/esm/llm/vertexai/index.mjs +5 -3
  136. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  137. package/dist/esm/main.mjs +7 -0
  138. package/dist/esm/main.mjs.map +1 -1
  139. package/dist/esm/messages/cache.mjs +2 -1
  140. package/dist/esm/messages/cache.mjs.map +1 -1
  141. package/dist/esm/messages/core.mjs +7 -6
  142. package/dist/esm/messages/core.mjs.map +1 -1
  143. package/dist/esm/messages/format.mjs +73 -15
  144. package/dist/esm/messages/format.mjs.map +1 -1
  145. package/dist/esm/messages/langchain.mjs +23 -0
  146. package/dist/esm/messages/langchain.mjs.map +1 -0
  147. package/dist/esm/messages/prune.mjs +7 -6
  148. package/dist/esm/messages/prune.mjs.map +1 -1
  149. package/dist/esm/run.mjs +400 -42
  150. package/dist/esm/run.mjs.map +1 -1
  151. package/dist/esm/tools/ToolNode.mjs +557 -57
  152. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  153. package/dist/esm/tools/search/search.mjs +55 -66
  154. package/dist/esm/tools/search/search.mjs.map +1 -1
  155. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  156. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  157. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  158. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  159. package/dist/esm/tools/search/tool.mjs +26 -4
  160. package/dist/esm/tools/search/tool.mjs.map +1 -1
  161. package/dist/esm/tools/search/utils.mjs +10 -3
  162. package/dist/esm/tools/search/utils.mjs.map +1 -1
  163. package/dist/types/graphs/Graph.d.ts +7 -0
  164. package/dist/types/hitl/askUserQuestion.d.ts +55 -0
  165. package/dist/types/hitl/index.d.ts +6 -0
  166. package/dist/types/hooks/HookRegistry.d.ts +58 -0
  167. package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
  168. package/dist/types/hooks/index.d.ts +4 -1
  169. package/dist/types/hooks/types.d.ts +109 -3
  170. package/dist/types/index.d.ts +10 -0
  171. package/dist/types/langchain/google-common.d.ts +1 -0
  172. package/dist/types/langchain/index.d.ts +8 -0
  173. package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
  174. package/dist/types/langchain/messages/tool.d.ts +1 -0
  175. package/dist/types/langchain/messages.d.ts +2 -0
  176. package/dist/types/langchain/openai.d.ts +1 -0
  177. package/dist/types/langchain/prompts.d.ts +1 -0
  178. package/dist/types/langchain/runnables.d.ts +2 -0
  179. package/dist/types/langchain/tools.d.ts +2 -0
  180. package/dist/types/langchain/utils/env.d.ts +1 -0
  181. package/dist/types/llm/anthropic/index.d.ts +22 -9
  182. package/dist/types/llm/anthropic/types.d.ts +5 -1
  183. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
  184. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
  185. package/dist/types/llm/openai/index.d.ts +21 -24
  186. package/dist/types/llm/openrouter/index.d.ts +11 -9
  187. package/dist/types/llm/vertexai/index.d.ts +1 -0
  188. package/dist/types/messages/cache.d.ts +4 -1
  189. package/dist/types/messages/format.d.ts +4 -1
  190. package/dist/types/messages/langchain.d.ts +27 -0
  191. package/dist/types/run.d.ts +117 -1
  192. package/dist/types/tools/ToolNode.d.ts +26 -1
  193. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  194. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  195. package/dist/types/tools/search/types.d.ts +99 -5
  196. package/dist/types/tools/search/utils.d.ts +2 -2
  197. package/dist/types/types/graph.d.ts +23 -37
  198. package/dist/types/types/hitl.d.ts +272 -0
  199. package/dist/types/types/index.d.ts +1 -0
  200. package/dist/types/types/llm.d.ts +3 -3
  201. package/dist/types/types/run.d.ts +33 -0
  202. package/dist/types/types/stream.d.ts +1 -1
  203. package/dist/types/types/tools.d.ts +19 -0
  204. package/package.json +80 -17
  205. package/src/graphs/Graph.ts +33 -4
  206. package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
  207. package/src/hitl/askUserQuestion.ts +72 -0
  208. package/src/hitl/index.ts +7 -0
  209. package/src/hooks/HookRegistry.ts +71 -0
  210. package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
  211. package/src/hooks/createToolPolicyHook.ts +184 -0
  212. package/src/hooks/executeHooks.ts +50 -1
  213. package/src/hooks/index.ts +6 -0
  214. package/src/hooks/types.ts +112 -0
  215. package/src/index.ts +22 -0
  216. package/src/langchain/google-common.ts +1 -0
  217. package/src/langchain/index.ts +8 -0
  218. package/src/langchain/language_models/chat_models.ts +1 -0
  219. package/src/langchain/messages/tool.ts +5 -0
  220. package/src/langchain/messages.ts +21 -0
  221. package/src/langchain/openai.ts +1 -0
  222. package/src/langchain/prompts.ts +1 -0
  223. package/src/langchain/runnables.ts +7 -0
  224. package/src/langchain/tools.ts +8 -0
  225. package/src/langchain/utils/env.ts +1 -0
  226. package/src/llm/anthropic/index.ts +252 -84
  227. package/src/llm/anthropic/llm.spec.ts +751 -102
  228. package/src/llm/anthropic/types.ts +9 -1
  229. package/src/llm/anthropic/utils/message_inputs.ts +37 -19
  230. package/src/llm/anthropic/utils/message_outputs.ts +119 -101
  231. package/src/llm/bedrock/index.ts +2 -2
  232. package/src/llm/bedrock/llm.spec.ts +341 -0
  233. package/src/llm/bedrock/utils/message_inputs.ts +303 -4
  234. package/src/llm/bedrock/utils/message_outputs.ts +2 -1
  235. package/src/llm/custom-chat-models.smoke.test.ts +836 -0
  236. package/src/llm/google/llm.spec.ts +339 -57
  237. package/src/llm/google/utils/common.ts +53 -48
  238. package/src/llm/openai/contentBlocks.test.ts +346 -0
  239. package/src/llm/openai/index.ts +856 -833
  240. package/src/llm/openai/utils/index.ts +107 -78
  241. package/src/llm/openai/utils/messages.test.ts +159 -0
  242. package/src/llm/openrouter/index.ts +124 -247
  243. package/src/llm/openrouter/reasoning.test.ts +8 -1
  244. package/src/llm/vertexai/index.ts +11 -5
  245. package/src/llm/vertexai/llm.spec.ts +28 -1
  246. package/src/messages/cache.test.ts +4 -3
  247. package/src/messages/cache.ts +3 -2
  248. package/src/messages/core.ts +16 -9
  249. package/src/messages/format.ts +96 -16
  250. package/src/messages/formatAgentMessages.test.ts +166 -1
  251. package/src/messages/langchain.ts +39 -0
  252. package/src/messages/prune.ts +12 -8
  253. package/src/run.ts +456 -47
  254. package/src/scripts/caching.ts +2 -3
  255. package/src/specs/summarization.test.ts +51 -58
  256. package/src/tools/ToolNode.ts +706 -63
  257. package/src/tools/__tests__/hitl.test.ts +3593 -0
  258. package/src/tools/search/search.ts +83 -73
  259. package/src/tools/search/tavily-scraper.ts +235 -0
  260. package/src/tools/search/tavily-search.ts +424 -0
  261. package/src/tools/search/tavily.test.ts +965 -0
  262. package/src/tools/search/tool.ts +36 -26
  263. package/src/tools/search/types.ts +133 -8
  264. package/src/tools/search/utils.ts +13 -5
  265. package/src/types/graph.ts +32 -87
  266. package/src/types/hitl.ts +303 -0
  267. package/src/types/index.ts +1 -0
  268. package/src/types/llm.ts +3 -3
  269. package/src/types/run.ts +33 -0
  270. package/src/types/stream.ts +1 -1
  271. package/src/types/tools.ts +19 -0
  272. package/src/utils/llmConfig.ts +1 -6
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Declarative `PreToolUse` hook factory. Lets hosts express common
3
+ * permission policies (allow / deny / ask lists + a global mode) without
4
+ * hand-rolling matching, precedence, and decision logic per-host.
5
+ *
6
+ * Maps directly to the Claude Code Agent SDK permission vocabulary
7
+ * (`allowed_tools` / `disallowed_tools` / `permissionMode`) so users of
8
+ * either SDK can think in the same terms. See the README's HITL section
9
+ * for the cross-walk and `docs/hooks-design-report.md` for the broader
10
+ * hook system context.
11
+ */
12
+
13
+ import type { HookCallback, PreToolUseHookOutput, ToolDecision } from './types';
14
+
15
+ /**
16
+ * Permission mode controlling how tool calls that match no rule are
17
+ * resolved. Mirrors Claude Code's `permissionMode`.
18
+ *
19
+ * - `default` — unmatched tools fall through to `'ask'` (interrupt).
20
+ * - `dontAsk` — unmatched tools are denied; the human is never
21
+ * prompted. Useful for headless / API agents where a
22
+ * silent denial is preferable to a hung interrupt.
23
+ * - `bypass` — every tool is approved, except those matching `deny`
24
+ * patterns. The kill switch you flip when you trust
25
+ * the agent and want to stop being asked. Equivalent to
26
+ * Claude Code's `bypassPermissions`.
27
+ */
28
+ export type ToolPolicyMode = 'default' | 'dontAsk' | 'bypass';
29
+
30
+ export interface ToolPolicyConfig {
31
+ /**
32
+ * Global mode applied to tools that don't match any rule.
33
+ * Defaults to `'default'` (ask the human).
34
+ */
35
+ mode?: ToolPolicyMode;
36
+ /**
37
+ * Tool name patterns that are auto-approved without a prompt.
38
+ * Patterns support glob `*` wildcards: `read_file`, `mcp:github:*`,
39
+ * `*search*`. Match is anchored (`^pattern$`).
40
+ */
41
+ allow?: readonly string[];
42
+ /**
43
+ * Tool name patterns that are blocked outright. Wins over `allow`
44
+ * and `ask`, and overrides `mode: 'bypass'` — a deny rule always
45
+ * holds, matching Claude Code's "deny rules are checked first" guarantee.
46
+ */
47
+ deny?: readonly string[];
48
+ /**
49
+ * Tool name patterns that always trigger human approval, regardless
50
+ * of `mode: 'default'` vs `'dontAsk'`. In `mode: 'bypass'` these are
51
+ * still bypassed (because that's what bypass means).
52
+ */
53
+ ask?: readonly string[];
54
+ /**
55
+ * Optional reason attached to the resulting `ask` / `deny` hook
56
+ * decision so the host UI can render why approval is required.
57
+ * The literal token `{tool}` is replaced with the tool name.
58
+ */
59
+ reason?: string;
60
+ }
61
+
62
+ /**
63
+ * Compile a glob string with `*` wildcards into a single anchored
64
+ * `RegExp`. Other regex metacharacters are escaped, so `read_file.md`
65
+ * matches the literal dot. Patterns are short (tool names), so we do
66
+ * not cache here — the registry's `matchesQuery` already caches its own
67
+ * regex compilations and our patterns are evaluated once per ToolNode
68
+ * batch, not once per stream chunk.
69
+ */
70
+ function globToRegex(pattern: string): RegExp {
71
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
72
+ return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
73
+ }
74
+
75
+ /** Pre-compile a list of glob patterns into a single match function. */
76
+ function compileMatchers(
77
+ patterns: readonly string[] | undefined
78
+ ): (toolName: string) => boolean {
79
+ if (patterns == null || patterns.length === 0) {
80
+ return () => false;
81
+ }
82
+ const regexes = patterns.map(globToRegex);
83
+ return (toolName: string): boolean => {
84
+ for (const regex of regexes) {
85
+ if (regex.test(toolName)) {
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ };
91
+ }
92
+
93
+ function formatReason(
94
+ template: string | undefined,
95
+ toolName: string
96
+ ): string | undefined {
97
+ if (template == null) {
98
+ return undefined;
99
+ }
100
+ return template.replace(/\{tool\}/g, toolName);
101
+ }
102
+
103
+ /**
104
+ * Build a `PreToolUse` hook callback that applies a declarative tool
105
+ * permission policy. Register it with a `HookRegistry` and the SDK's
106
+ * `humanInTheLoop` machinery handles the rest:
107
+ *
108
+ * ```ts
109
+ * const policyHook = createToolPolicyHook({
110
+ * mode: 'default',
111
+ * allow: ['read_*', 'grep', 'glob'],
112
+ * deny: ['delete_*'],
113
+ * ask: ['execute_*', 'mcp:*'],
114
+ * });
115
+ * registry.register('PreToolUse', { hooks: [policyHook] });
116
+ * ```
117
+ *
118
+ * Evaluation order matches Claude Code's permission flow:
119
+ *
120
+ * 1. `deny` rule match → `'deny'` (always wins, even in `bypass`).
121
+ * 2. `mode === 'bypass'` → `'allow'`.
122
+ * 3. `allow` rule match → `'allow'`.
123
+ * 4. `ask` rule match → `'ask'`.
124
+ * 5. `mode === 'dontAsk'` → `'deny'`.
125
+ * 6. fallthrough → `'ask'`.
126
+ *
127
+ * The returned callback is a single `HookCallback`, not a `HookMatcher` —
128
+ * register it under the matcher with the pattern you want (omit the
129
+ * pattern to fire on every tool call, which is the typical case since
130
+ * the policy itself does the filtering).
131
+ */
132
+ export function createToolPolicyHook(
133
+ config: ToolPolicyConfig
134
+ ): HookCallback<'PreToolUse'> {
135
+ const denyMatcher = compileMatchers(config.deny);
136
+ const allowMatcher = compileMatchers(config.allow);
137
+ const askMatcher = compileMatchers(config.ask);
138
+ const mode: ToolPolicyMode = config.mode ?? 'default';
139
+ const reasonTemplate = config.reason;
140
+
141
+ return async (input): Promise<PreToolUseHookOutput> => {
142
+ const toolName = input.toolName;
143
+ const decision = decide(
144
+ toolName,
145
+ mode,
146
+ denyMatcher,
147
+ allowMatcher,
148
+ askMatcher
149
+ );
150
+ if (decision === 'allow') {
151
+ return { decision };
152
+ }
153
+ const reason = formatReason(reasonTemplate, toolName);
154
+ if (reason != null) {
155
+ return { decision, reason };
156
+ }
157
+ return { decision };
158
+ };
159
+ }
160
+
161
+ function decide(
162
+ toolName: string,
163
+ mode: ToolPolicyMode,
164
+ denyMatch: (n: string) => boolean,
165
+ allowMatch: (n: string) => boolean,
166
+ askMatch: (n: string) => boolean
167
+ ): ToolDecision {
168
+ if (denyMatch(toolName)) {
169
+ return 'deny';
170
+ }
171
+ if (mode === 'bypass') {
172
+ return 'allow';
173
+ }
174
+ if (allowMatch(toolName)) {
175
+ return 'allow';
176
+ }
177
+ if (askMatch(toolName)) {
178
+ return 'ask';
179
+ }
180
+ if (mode === 'dontAsk') {
181
+ return 'deny';
182
+ }
183
+ return 'ask';
184
+ }
@@ -255,6 +255,19 @@ function applyUpdatedOutput(
255
255
  agg.updatedOutput = output.updatedOutput;
256
256
  }
257
257
 
258
+ function applyAllowedDecisions(
259
+ agg: AggregatedHookResult,
260
+ output: HookOutput
261
+ ): void {
262
+ if (
263
+ !('allowedDecisions' in output) ||
264
+ output.allowedDecisions === undefined
265
+ ) {
266
+ return;
267
+ }
268
+ agg.allowedDecisions = output.allowedDecisions;
269
+ }
270
+
258
271
  function fold(outcomes: readonly HookOutcome[]): AggregatedHookResult {
259
272
  const agg = freshResult();
260
273
  for (const outcome of outcomes) {
@@ -268,11 +281,21 @@ function fold(outcomes: readonly HookOutcome[]): AggregatedHookResult {
268
281
  if (output === null) {
269
282
  continue;
270
283
  }
284
+ /**
285
+ * Skip fire-and-forget outputs entirely: the agent has already
286
+ * moved on, so an async hook cannot influence the run. Background
287
+ * work inside the hook body still runs (we don't cancel it), it
288
+ * just doesn't fold into the aggregate result.
289
+ */
290
+ if (output.async === true) {
291
+ continue;
292
+ }
271
293
  applyContext(agg, output);
272
294
  applyStopFlag(agg, output);
273
295
  applyDecision(agg, output);
274
296
  applyUpdatedInput(agg, output);
275
297
  applyUpdatedOutput(agg, output);
298
+ applyAllowedDecisions(agg, output);
276
299
  }
277
300
  return agg;
278
301
  }
@@ -371,5 +394,31 @@ export async function executeHooks(
371
394
 
372
395
  const outcomes = await Promise.all(tasks);
373
396
  reportErrors(outcomes, event, logger);
374
- return fold(outcomes);
397
+ const aggregated = fold(outcomes);
398
+ /**
399
+ * Centralized `preventContinuation` propagation: when any hook (across
400
+ * any callsite — RunStart, PreToolUse, PostToolBatch, SubagentStop,
401
+ * etc.) returns `preventContinuation: true`, raise a halt signal on
402
+ * the registry scoped to the run's `sessionId`. `Run.processStream`
403
+ * polls the signal between stream events using its own id and exits
404
+ * cleanly, skipping the `Stop` hook (since the run is being halted,
405
+ * not naturally completing).
406
+ *
407
+ * First-write-wins per session inside the registry — a halt already
408
+ * raised by an earlier hook in the same run is preserved so the
409
+ * original `reason` / `source` are not clobbered. Hooks fired
410
+ * without a `sessionId` cannot raise a halt (there's no run for the
411
+ * loop to poll under), which is fine: every in-tree callsite passes
412
+ * `sessionId: runId`. Pre-stream callsites in `Run.processStream`
413
+ * still read `preventContinuation` directly off the result for an
414
+ * early return because they have not yet entered the stream loop.
415
+ */
416
+ if (aggregated.preventContinuation === true && sessionId !== undefined) {
417
+ registry.haltRun(
418
+ sessionId,
419
+ aggregated.stopReason ?? 'preventContinuation',
420
+ event
421
+ );
422
+ }
423
+ return aggregated;
375
424
  }
@@ -7,6 +7,7 @@
7
7
  // `createSummarizeNode` (PreCompact, PostCompact), and
8
8
  // `SubagentExecutor.execute` (SubagentStart, SubagentStop).
9
9
  export { HookRegistry } from './HookRegistry';
10
+ export type { HookHaltSignal } from './HookRegistry';
10
11
  export { executeHooks, DEFAULT_HOOK_TIMEOUT_MS } from './executeHooks';
11
12
  export {
12
13
  matchesQuery,
@@ -14,6 +15,8 @@ export {
14
15
  MAX_PATTERN_LENGTH,
15
16
  MAX_CACHE_SIZE,
16
17
  } from './matchers';
18
+ export { createToolPolicyHook } from './createToolPolicyHook';
19
+ export type { ToolPolicyMode, ToolPolicyConfig } from './createToolPolicyHook';
17
20
  export { HOOK_EVENTS } from './types';
18
21
  export type {
19
22
  HookEvent,
@@ -34,6 +37,8 @@ export type {
34
37
  PreToolUseHookInput,
35
38
  PostToolUseHookInput,
36
39
  PostToolUseFailureHookInput,
40
+ PostToolBatchHookInput,
41
+ PostToolBatchEntry,
37
42
  PermissionDeniedHookInput,
38
43
  SubagentStartHookInput,
39
44
  SubagentStopHookInput,
@@ -46,6 +51,7 @@ export type {
46
51
  PreToolUseHookOutput,
47
52
  PostToolUseHookOutput,
48
53
  PostToolUseFailureHookOutput,
54
+ PostToolBatchHookOutput,
49
55
  PermissionDeniedHookOutput,
50
56
  SubagentStartHookOutput,
51
57
  SubagentStopHookOutput,
@@ -15,6 +15,7 @@ export const HOOK_EVENTS = [
15
15
  'PreToolUse',
16
16
  'PostToolUse',
17
17
  'PostToolUseFailure',
18
+ 'PostToolBatch',
18
19
  'PermissionDenied',
19
20
  'SubagentStart',
20
21
  'SubagentStop',
@@ -100,6 +101,42 @@ export interface PostToolUseFailureHookInput extends BaseHookInput {
100
101
  turn?: number;
101
102
  }
102
103
 
104
+ /**
105
+ * Per-tool result snapshot included in a `PostToolBatch` event. Mirrors
106
+ * the data PostToolUse / PostToolUseFailure get individually, but the
107
+ * batch view lets a single hook see the whole set so it can inject one
108
+ * consolidated convention/audit message rather than N per-tool ones.
109
+ */
110
+ export interface PostToolBatchEntry {
111
+ toolName: string;
112
+ toolInput: Record<string, unknown>;
113
+ toolUseId: string;
114
+ stepId?: string;
115
+ turn?: number;
116
+ /** Successful tool output, present only when `status === 'success'`. */
117
+ toolOutput?: unknown;
118
+ /** Error message, present only when `status === 'error'`. */
119
+ error?: string;
120
+ status: 'success' | 'error';
121
+ }
122
+
123
+ /**
124
+ * Fires once after every tool call in a single batch finishes (including
125
+ * any that were rejected via HITL). Lets a hook react to the batch as a
126
+ * whole — useful for "inject conventions once for the whole batch", batch
127
+ * audit logging, or coordinating cleanup that depends on knowing the full
128
+ * result set rather than streaming each tool's result independently.
129
+ *
130
+ * Order: fires AFTER all per-tool PostToolUse / PostToolUseFailure hooks
131
+ * for the same batch have completed, BEFORE the next model call. Pass an
132
+ * `additionalContext` to inject context for that next model turn.
133
+ */
134
+ export interface PostToolBatchHookInput extends BaseHookInput {
135
+ hook_event_name: 'PostToolBatch';
136
+ /** All tool calls (and their outcomes) from this batch, in batch order. */
137
+ entries: PostToolBatchEntry[];
138
+ }
139
+
103
140
  export interface PermissionDeniedHookInput extends BaseHookInput {
104
141
  hook_event_name: 'PermissionDenied';
105
142
  toolName: string;
@@ -171,6 +208,7 @@ export type HookInput =
171
208
  | PreToolUseHookInput
172
209
  | PostToolUseHookInput
173
210
  | PostToolUseFailureHookInput
211
+ | PostToolBatchHookInput
174
212
  | PermissionDeniedHookInput
175
213
  | SubagentStartHookInput
176
214
  | SubagentStopHookInput
@@ -186,6 +224,7 @@ export type HookInputByEvent = {
186
224
  PreToolUse: PreToolUseHookInput;
187
225
  PostToolUse: PostToolUseHookInput;
188
226
  PostToolUseFailure: PostToolUseFailureHookInput;
227
+ PostToolBatch: PostToolBatchHookInput;
189
228
  PermissionDenied: PermissionDeniedHookInput;
190
229
  SubagentStart: SubagentStartHookInput;
191
230
  SubagentStop: SubagentStopHookInput;
@@ -206,6 +245,56 @@ export interface BaseHookOutput {
206
245
  preventContinuation?: boolean;
207
246
  /** Reason reported alongside `preventContinuation`. */
208
247
  stopReason?: string;
248
+ /**
249
+ * Marks this hook output as fire-and-forget for INFLUENCE only.
250
+ * When `true`, the SDK skips every other field on this output —
251
+ * `decision`, `additionalContext`, `updatedInput`,
252
+ * `preventContinuation`, `allowedDecisions`, `updatedOutput` are
253
+ * all ignored. The hook's return value cannot block, modify, or
254
+ * inject context, so it's safe to use for pure side effects
255
+ * (logging, metrics, webhooks).
256
+ *
257
+ * Important caveat: the hook's CALLBACK promise is still awaited
258
+ * by `executeHooks` (subject to the matcher's timeout and the
259
+ * default `DEFAULT_HOOK_TIMEOUT_MS`). The SDK does not
260
+ * speculatively detach hooks based on output shape, because the
261
+ * shape is only known after the promise resolves. For TRUE
262
+ * fire-and-forget where the agent doesn't wait at all, the hook
263
+ * body should detach its side effect itself and return
264
+ * immediately:
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * async (input) => {
269
+ * // Detach the slow work — the SDK awaits this hook's
270
+ * // returned promise, which resolves immediately because we
271
+ * // don't `await` the side effect.
272
+ * void sendToLoggingService(input).catch(console.error);
273
+ * return { async: true };
274
+ * };
275
+ * ```
276
+ *
277
+ * @example WRONG — the agent will block on the webhook
278
+ * ```ts
279
+ * async (input) => {
280
+ * await sendToLoggingService(input); // ← awaited, blocks
281
+ * return { async: true }; // returning async:true doesn't undo the await
282
+ * };
283
+ * ```
284
+ *
285
+ * Mirrors Claude Code Agent SDK's `async` output, with the same
286
+ * "detach inside the hook body" pattern.
287
+ */
288
+ async?: boolean;
289
+ /**
290
+ * Optional advisory timeout in milliseconds for the background work
291
+ * a host has detached inside an `async: true` hook body. The SDK
292
+ * does not enforce this (the hook's own AbortSignal handling does)
293
+ * but the field is preserved on the wire so downstream
294
+ * observability can surface long-running side effects. Ignored
295
+ * unless `async` is true.
296
+ */
297
+ asyncTimeout?: number;
209
298
  }
210
299
 
211
300
  export type RunStartHookOutput = BaseHookOutput;
@@ -229,6 +318,19 @@ export interface PreToolUseHookOutput extends BaseHookOutput {
229
318
  * `updatedInput` to one hook per matcher to avoid confusing precedence.
230
319
  */
231
320
  updatedInput?: Record<string, unknown>;
321
+ /**
322
+ * Restricts which decisions the host UI is allowed to surface for this
323
+ * tool call when the hook returns `decision: 'ask'`. Pass to lock a
324
+ * tool down to a subset of `'approve' | 'reject' | 'edit' | 'respond'`
325
+ * — for example, `['approve', 'reject']` to forbid the user from
326
+ * editing the tool's args or substituting a custom response.
327
+ *
328
+ * The values flow into the resulting interrupt's
329
+ * `review_configs[i].allowed_decisions`. Omitting the field keeps the
330
+ * SDK default (all four decisions advertised). Last-writer-wins in
331
+ * registration order, same precedence rules as `updatedInput`.
332
+ */
333
+ allowedDecisions?: ReadonlyArray<'approve' | 'reject' | 'edit' | 'respond'>;
232
334
  }
233
335
 
234
336
  export interface PostToolUseHookOutput extends BaseHookOutput {
@@ -243,6 +345,8 @@ export interface PostToolUseHookOutput extends BaseHookOutput {
243
345
 
244
346
  export type PostToolUseFailureHookOutput = BaseHookOutput;
245
347
 
348
+ export type PostToolBatchHookOutput = BaseHookOutput;
349
+
246
350
  export type PermissionDeniedHookOutput = BaseHookOutput;
247
351
 
248
352
  export interface SubagentStartHookOutput extends BaseHookOutput {
@@ -270,6 +374,7 @@ export type HookOutputByEvent = {
270
374
  PreToolUse: PreToolUseHookOutput;
271
375
  PostToolUse: PostToolUseHookOutput;
272
376
  PostToolUseFailure: PostToolUseFailureHookOutput;
377
+ PostToolBatch: PostToolBatchHookOutput;
273
378
  PermissionDenied: PermissionDeniedHookOutput;
274
379
  SubagentStart: SubagentStartHookOutput;
275
380
  SubagentStop: SubagentStopHookOutput;
@@ -286,6 +391,7 @@ export type HookOutput =
286
391
  | PreToolUseHookOutput
287
392
  | PostToolUseHookOutput
288
393
  | PostToolUseFailureHookOutput
394
+ | PostToolBatchHookOutput
289
395
  | PermissionDeniedHookOutput
290
396
  | SubagentStartHookOutput
291
397
  | SubagentStopHookOutput
@@ -381,6 +487,12 @@ export interface AggregatedHookResult {
381
487
  * hook per matcher to avoid subtle precedence bugs.
382
488
  */
383
489
  updatedInput?: Record<string, unknown>;
490
+ /**
491
+ * Restricted decision set from a `PreToolUse` hook. Same last-writer-wins
492
+ * semantics as `updatedInput`. Surfaces to the interrupt payload's
493
+ * `review_configs[i].allowed_decisions`.
494
+ */
495
+ allowedDecisions?: ReadonlyArray<'approve' | 'reject' | 'edit' | 'respond'>;
384
496
  /**
385
497
  * Replacement tool output from a `PostToolUse` hook.
386
498
  *
package/src/index.ts CHANGED
@@ -35,9 +35,31 @@ export * from './utils';
35
35
  /* Hooks */
36
36
  export * from './hooks';
37
37
 
38
+ /* HITL helpers */
39
+ export * from './hitl';
40
+
38
41
  /* Types */
39
42
  export type * from './types';
40
43
 
44
+ /* LangChain compatibility facade */
45
+ export * from './langchain';
46
+
47
+ /**
48
+ * HITL primitives re-exported from `@langchain/langgraph` so hosts that
49
+ * build durable checkpoint savers, dispatch `Command({ resume })`, or
50
+ * detect interrupts can do so against the same langgraph instance the
51
+ * SDK was compiled against — avoiding accidental dual-version drift.
52
+ */
53
+ export {
54
+ Command,
55
+ INTERRUPT,
56
+ interrupt,
57
+ MemorySaver,
58
+ BaseCheckpointSaver,
59
+ isInterrupted,
60
+ } from '@langchain/langgraph';
61
+ export type { Interrupt } from '@langchain/langgraph';
62
+
41
63
  /* LLM */
42
64
  export { CustomOpenAIClient } from './llm/openai';
43
65
  export { ChatOpenRouter } from './llm/openrouter';
@@ -0,0 +1 @@
1
+ export type { GoogleAIToolType } from '@langchain/google-common';
@@ -0,0 +1,8 @@
1
+ export * from './messages';
2
+ export * from './prompts';
3
+ export * from './runnables';
4
+ export * from './tools';
5
+ export * from './google-common';
6
+ export * from './language_models/chat_models';
7
+ export * from './messages/tool';
8
+ export * from './openai';
@@ -0,0 +1 @@
1
+ export type { BindToolsInput } from '@langchain/core/language_models/chat_models';
@@ -0,0 +1,5 @@
1
+ export type {
2
+ InvalidToolCall,
3
+ ToolCall,
4
+ ToolCallChunk,
5
+ } from '@langchain/core/messages/tool';
@@ -0,0 +1,21 @@
1
+ export {
2
+ AIMessage,
3
+ AIMessageChunk,
4
+ BaseMessage,
5
+ BaseMessageChunk,
6
+ HumanMessage,
7
+ SystemMessage,
8
+ ToolMessage,
9
+ getBufferString,
10
+ isAIMessage,
11
+ isBaseMessage,
12
+ isToolMessage,
13
+ } from '@langchain/core/messages';
14
+
15
+ export type {
16
+ BaseMessageFields,
17
+ MessageContent,
18
+ MessageContentText,
19
+ MessageContentImageUrl,
20
+ UsageMetadata,
21
+ } from '@langchain/core/messages';
@@ -0,0 +1 @@
1
+ export type { AzureOpenAIInput } from '@langchain/openai';
@@ -0,0 +1 @@
1
+ export { PromptTemplate } from '@langchain/core/prompts';
@@ -0,0 +1,7 @@
1
+ export {
2
+ Runnable,
3
+ RunnableLambda,
4
+ RunnableSequence,
5
+ } from '@langchain/core/runnables';
6
+
7
+ export type { RunnableConfig } from '@langchain/core/runnables';
@@ -0,0 +1,8 @@
1
+ export {
2
+ DynamicStructuredTool,
3
+ StructuredTool,
4
+ Tool,
5
+ tool,
6
+ } from '@langchain/core/tools';
7
+
8
+ export type { StructuredToolInterface } from '@langchain/core/tools';
@@ -0,0 +1 @@
1
+ export { getEnvironmentVariable } from '@langchain/core/utils/env';