@librechat/agents 3.1.76 → 3.1.77

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 (85) hide show
  1. package/dist/cjs/graphs/Graph.cjs +9 -0
  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/llm/openai/index.cjs +317 -1
  14. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  15. package/dist/cjs/main.cjs +29 -0
  16. package/dist/cjs/main.cjs.map +1 -1
  17. package/dist/cjs/run.cjs +400 -42
  18. package/dist/cjs/run.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +551 -55
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
  22. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  23. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  24. package/dist/esm/graphs/Graph.mjs +9 -0
  25. package/dist/esm/graphs/Graph.mjs.map +1 -1
  26. package/dist/esm/hitl/askUserQuestion.mjs +65 -0
  27. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
  28. package/dist/esm/hooks/HookRegistry.mjs +54 -0
  29. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  30. package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
  31. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
  32. package/dist/esm/hooks/executeHooks.mjs +40 -1
  33. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  34. package/dist/esm/hooks/types.mjs +1 -0
  35. package/dist/esm/hooks/types.mjs.map +1 -1
  36. package/dist/esm/llm/openai/index.mjs +318 -2
  37. package/dist/esm/llm/openai/index.mjs.map +1 -1
  38. package/dist/esm/main.mjs +3 -0
  39. package/dist/esm/main.mjs.map +1 -1
  40. package/dist/esm/run.mjs +400 -42
  41. package/dist/esm/run.mjs.map +1 -1
  42. package/dist/esm/tools/ToolNode.mjs +552 -56
  43. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  44. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
  45. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  46. package/dist/esm/tools/search/tool.mjs.map +1 -1
  47. package/dist/types/graphs/Graph.d.ts +7 -0
  48. package/dist/types/hitl/askUserQuestion.d.ts +55 -0
  49. package/dist/types/hitl/index.d.ts +6 -0
  50. package/dist/types/hooks/HookRegistry.d.ts +58 -0
  51. package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
  52. package/dist/types/hooks/index.d.ts +4 -1
  53. package/dist/types/hooks/types.d.ts +109 -3
  54. package/dist/types/index.d.ts +9 -0
  55. package/dist/types/llm/openai/index.d.ts +17 -0
  56. package/dist/types/run.d.ts +117 -1
  57. package/dist/types/tools/ToolNode.d.ts +26 -1
  58. package/dist/types/types/hitl.d.ts +272 -0
  59. package/dist/types/types/index.d.ts +1 -0
  60. package/dist/types/types/run.d.ts +33 -0
  61. package/dist/types/types/tools.d.ts +19 -0
  62. package/package.json +1 -1
  63. package/src/graphs/Graph.ts +9 -0
  64. package/src/hitl/askUserQuestion.ts +72 -0
  65. package/src/hitl/index.ts +7 -0
  66. package/src/hooks/HookRegistry.ts +71 -0
  67. package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
  68. package/src/hooks/createToolPolicyHook.ts +184 -0
  69. package/src/hooks/executeHooks.ts +50 -1
  70. package/src/hooks/index.ts +6 -0
  71. package/src/hooks/types.ts +112 -0
  72. package/src/index.ts +19 -0
  73. package/src/llm/openai/deepseek.test.ts +479 -0
  74. package/src/llm/openai/index.ts +484 -1
  75. package/src/run.ts +456 -47
  76. package/src/tools/ToolNode.ts +701 -62
  77. package/src/tools/__tests__/hitl.test.ts +3593 -0
  78. package/src/tools/search/tavily-scraper.ts +4 -4
  79. package/src/tools/search/tavily-search.ts +32 -32
  80. package/src/tools/search/tool.ts +3 -3
  81. package/src/tools/search/types.ts +3 -1
  82. package/src/types/hitl.ts +303 -0
  83. package/src/types/index.ts +1 -0
  84. package/src/types/run.ts +33 -0
  85. package/src/types/tools.ts +19 -0
@@ -7,7 +7,7 @@ import type { BaseMessage } from '@langchain/core/messages';
7
7
  * `docs/hooks-design-report.md` §3.2 for the mapping to existing
8
8
  * `@librechat/agents` emission points.
9
9
  */
10
- export declare const HOOK_EVENTS: readonly ["RunStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PostToolUseFailure", "PermissionDenied", "SubagentStart", "SubagentStop", "Stop", "StopFailure", "PreCompact", "PostCompact"];
10
+ export declare const HOOK_EVENTS: readonly ["RunStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "PermissionDenied", "SubagentStart", "SubagentStop", "Stop", "StopFailure", "PreCompact", "PostCompact"];
11
11
  export type HookEvent = (typeof HOOK_EVENTS)[number];
12
12
  /** Tool-gating decision; executeHooks folds with `deny > ask > allow` precedence. */
13
13
  export type ToolDecision = 'allow' | 'deny' | 'ask';
@@ -75,6 +75,40 @@ export interface PostToolUseFailureHookInput extends BaseHookInput {
75
75
  stepId?: string;
76
76
  turn?: number;
77
77
  }
78
+ /**
79
+ * Per-tool result snapshot included in a `PostToolBatch` event. Mirrors
80
+ * the data PostToolUse / PostToolUseFailure get individually, but the
81
+ * batch view lets a single hook see the whole set so it can inject one
82
+ * consolidated convention/audit message rather than N per-tool ones.
83
+ */
84
+ export interface PostToolBatchEntry {
85
+ toolName: string;
86
+ toolInput: Record<string, unknown>;
87
+ toolUseId: string;
88
+ stepId?: string;
89
+ turn?: number;
90
+ /** Successful tool output, present only when `status === 'success'`. */
91
+ toolOutput?: unknown;
92
+ /** Error message, present only when `status === 'error'`. */
93
+ error?: string;
94
+ status: 'success' | 'error';
95
+ }
96
+ /**
97
+ * Fires once after every tool call in a single batch finishes (including
98
+ * any that were rejected via HITL). Lets a hook react to the batch as a
99
+ * whole — useful for "inject conventions once for the whole batch", batch
100
+ * audit logging, or coordinating cleanup that depends on knowing the full
101
+ * result set rather than streaming each tool's result independently.
102
+ *
103
+ * Order: fires AFTER all per-tool PostToolUse / PostToolUseFailure hooks
104
+ * for the same batch have completed, BEFORE the next model call. Pass an
105
+ * `additionalContext` to inject context for that next model turn.
106
+ */
107
+ export interface PostToolBatchHookInput extends BaseHookInput {
108
+ hook_event_name: 'PostToolBatch';
109
+ /** All tool calls (and their outcomes) from this batch, in batch order. */
110
+ entries: PostToolBatchEntry[];
111
+ }
78
112
  export interface PermissionDeniedHookInput extends BaseHookInput {
79
113
  hook_event_name: 'PermissionDenied';
80
114
  toolName: string;
@@ -128,7 +162,7 @@ export interface PostCompactHookInput extends BaseHookInput {
128
162
  messagesAfterCount: number;
129
163
  }
130
164
  /** Discriminated union of every hook input shape. */
131
- export type HookInput = RunStartHookInput | UserPromptSubmitHookInput | PreToolUseHookInput | PostToolUseHookInput | PostToolUseFailureHookInput | PermissionDeniedHookInput | SubagentStartHookInput | SubagentStopHookInput | StopHookInput | StopFailureHookInput | PreCompactHookInput | PostCompactHookInput;
165
+ export type HookInput = RunStartHookInput | UserPromptSubmitHookInput | PreToolUseHookInput | PostToolUseHookInput | PostToolUseFailureHookInput | PostToolBatchHookInput | PermissionDeniedHookInput | SubagentStartHookInput | SubagentStopHookInput | StopHookInput | StopFailureHookInput | PreCompactHookInput | PostCompactHookInput;
132
166
  /** Compile-time map from event name to its input shape. */
133
167
  export type HookInputByEvent = {
134
168
  RunStart: RunStartHookInput;
@@ -136,6 +170,7 @@ export type HookInputByEvent = {
136
170
  PreToolUse: PreToolUseHookInput;
137
171
  PostToolUse: PostToolUseHookInput;
138
172
  PostToolUseFailure: PostToolUseFailureHookInput;
173
+ PostToolBatch: PostToolBatchHookInput;
139
174
  PermissionDenied: PermissionDeniedHookInput;
140
175
  SubagentStart: SubagentStartHookInput;
141
176
  SubagentStop: SubagentStopHookInput;
@@ -155,6 +190,56 @@ export interface BaseHookOutput {
155
190
  preventContinuation?: boolean;
156
191
  /** Reason reported alongside `preventContinuation`. */
157
192
  stopReason?: string;
193
+ /**
194
+ * Marks this hook output as fire-and-forget for INFLUENCE only.
195
+ * When `true`, the SDK skips every other field on this output —
196
+ * `decision`, `additionalContext`, `updatedInput`,
197
+ * `preventContinuation`, `allowedDecisions`, `updatedOutput` are
198
+ * all ignored. The hook's return value cannot block, modify, or
199
+ * inject context, so it's safe to use for pure side effects
200
+ * (logging, metrics, webhooks).
201
+ *
202
+ * Important caveat: the hook's CALLBACK promise is still awaited
203
+ * by `executeHooks` (subject to the matcher's timeout and the
204
+ * default `DEFAULT_HOOK_TIMEOUT_MS`). The SDK does not
205
+ * speculatively detach hooks based on output shape, because the
206
+ * shape is only known after the promise resolves. For TRUE
207
+ * fire-and-forget where the agent doesn't wait at all, the hook
208
+ * body should detach its side effect itself and return
209
+ * immediately:
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * async (input) => {
214
+ * // Detach the slow work — the SDK awaits this hook's
215
+ * // returned promise, which resolves immediately because we
216
+ * // don't `await` the side effect.
217
+ * void sendToLoggingService(input).catch(console.error);
218
+ * return { async: true };
219
+ * };
220
+ * ```
221
+ *
222
+ * @example WRONG — the agent will block on the webhook
223
+ * ```ts
224
+ * async (input) => {
225
+ * await sendToLoggingService(input); // ← awaited, blocks
226
+ * return { async: true }; // returning async:true doesn't undo the await
227
+ * };
228
+ * ```
229
+ *
230
+ * Mirrors Claude Code Agent SDK's `async` output, with the same
231
+ * "detach inside the hook body" pattern.
232
+ */
233
+ async?: boolean;
234
+ /**
235
+ * Optional advisory timeout in milliseconds for the background work
236
+ * a host has detached inside an `async: true` hook body. The SDK
237
+ * does not enforce this (the hook's own AbortSignal handling does)
238
+ * but the field is preserved on the wire so downstream
239
+ * observability can surface long-running side effects. Ignored
240
+ * unless `async` is true.
241
+ */
242
+ asyncTimeout?: number;
158
243
  }
159
244
  export type RunStartHookOutput = BaseHookOutput;
160
245
  export interface UserPromptSubmitHookOutput extends BaseHookOutput {
@@ -175,6 +260,19 @@ export interface PreToolUseHookOutput extends BaseHookOutput {
175
260
  * `updatedInput` to one hook per matcher to avoid confusing precedence.
176
261
  */
177
262
  updatedInput?: Record<string, unknown>;
263
+ /**
264
+ * Restricts which decisions the host UI is allowed to surface for this
265
+ * tool call when the hook returns `decision: 'ask'`. Pass to lock a
266
+ * tool down to a subset of `'approve' | 'reject' | 'edit' | 'respond'`
267
+ * — for example, `['approve', 'reject']` to forbid the user from
268
+ * editing the tool's args or substituting a custom response.
269
+ *
270
+ * The values flow into the resulting interrupt's
271
+ * `review_configs[i].allowed_decisions`. Omitting the field keeps the
272
+ * SDK default (all four decisions advertised). Last-writer-wins in
273
+ * registration order, same precedence rules as `updatedInput`.
274
+ */
275
+ allowedDecisions?: ReadonlyArray<'approve' | 'reject' | 'edit' | 'respond'>;
178
276
  }
179
277
  export interface PostToolUseHookOutput extends BaseHookOutput {
180
278
  /**
@@ -186,6 +284,7 @@ export interface PostToolUseHookOutput extends BaseHookOutput {
186
284
  updatedOutput?: unknown;
187
285
  }
188
286
  export type PostToolUseFailureHookOutput = BaseHookOutput;
287
+ export type PostToolBatchHookOutput = BaseHookOutput;
189
288
  export type PermissionDeniedHookOutput = BaseHookOutput;
190
289
  export interface SubagentStartHookOutput extends BaseHookOutput {
191
290
  decision?: ToolDecision;
@@ -206,6 +305,7 @@ export type HookOutputByEvent = {
206
305
  PreToolUse: PreToolUseHookOutput;
207
306
  PostToolUse: PostToolUseHookOutput;
208
307
  PostToolUseFailure: PostToolUseFailureHookOutput;
308
+ PostToolBatch: PostToolBatchHookOutput;
209
309
  PermissionDenied: PermissionDeniedHookOutput;
210
310
  SubagentStart: SubagentStartHookOutput;
211
311
  SubagentStop: SubagentStopHookOutput;
@@ -215,7 +315,7 @@ export type HookOutputByEvent = {
215
315
  PostCompact: PostCompactHookOutput;
216
316
  };
217
317
  /** Superset output shape used by the executor's fold loop. */
218
- export type HookOutput = RunStartHookOutput | UserPromptSubmitHookOutput | PreToolUseHookOutput | PostToolUseHookOutput | PostToolUseFailureHookOutput | PermissionDeniedHookOutput | SubagentStartHookOutput | SubagentStopHookOutput | StopHookOutput | StopFailureHookOutput | PreCompactHookOutput | PostCompactHookOutput;
318
+ export type HookOutput = RunStartHookOutput | UserPromptSubmitHookOutput | PreToolUseHookOutput | PostToolUseHookOutput | PostToolUseFailureHookOutput | PostToolBatchHookOutput | PermissionDeniedHookOutput | SubagentStartHookOutput | SubagentStopHookOutput | StopHookOutput | StopFailureHookOutput | PreCompactHookOutput | PostCompactHookOutput;
219
319
  /**
220
320
  * A hook callback is a plain async function registered against a specific
221
321
  * event. The `signal` is always supplied by `executeHooks` and combines the
@@ -297,6 +397,12 @@ export interface AggregatedHookResult {
297
397
  * hook per matcher to avoid subtle precedence bugs.
298
398
  */
299
399
  updatedInput?: Record<string, unknown>;
400
+ /**
401
+ * Restricted decision set from a `PreToolUse` hook. Same last-writer-wins
402
+ * semantics as `updatedInput`. Surfaces to the interrupt payload's
403
+ * `review_configs[i].allowed_decisions`.
404
+ */
405
+ allowedDecisions?: ReadonlyArray<'approve' | 'reject' | 'edit' | 'respond'>;
300
406
  /**
301
407
  * Replacement tool output from a `PostToolUse` hook.
302
408
  *
@@ -23,8 +23,17 @@ export * from './tools/search';
23
23
  export * from './common';
24
24
  export * from './utils';
25
25
  export * from './hooks';
26
+ export * from './hitl';
26
27
  export type * from './types';
27
28
  export * from './langchain';
29
+ /**
30
+ * HITL primitives re-exported from `@langchain/langgraph` so hosts that
31
+ * build durable checkpoint savers, dispatch `Command({ resume })`, or
32
+ * detect interrupts can do so against the same langgraph instance the
33
+ * SDK was compiled against — avoiding accidental dual-version drift.
34
+ */
35
+ export { Command, INTERRUPT, interrupt, MemorySaver, BaseCheckpointSaver, isInterrupted, } from '@langchain/langgraph';
36
+ export type { Interrupt } from '@langchain/langgraph';
28
37
  export { CustomOpenAIClient } from './llm/openai';
29
38
  export { ChatOpenRouter } from './llm/openrouter';
30
39
  export type { OpenRouterReasoning, OpenRouterReasoningEffort, ChatOpenRouterCallOptions, } from './llm/openrouter';
@@ -1,17 +1,20 @@
1
1
  import { AzureOpenAI as AzureOpenAIClient } from 'openai';
2
2
  import { ChatXAI as OriginalChatXAI } from '@langchain/xai';
3
3
  import { ChatGenerationChunk } from '@langchain/core/outputs';
4
+ import { AIMessageChunk } from '@langchain/core/messages';
4
5
  import { ChatDeepSeek as OriginalChatDeepSeek } from '@langchain/deepseek';
5
6
  import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
6
7
  import { OpenAIClient, ChatOpenAI as OriginalChatOpenAI, AzureChatOpenAI as OriginalAzureChatOpenAI } from '@langchain/openai';
7
8
  import type { HeaderValue, HeadersLike } from './types';
8
9
  import type { BaseMessage } from '@langchain/core/messages';
9
10
  import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
11
+ import type { ChatResult } from '@langchain/core/outputs';
10
12
  import type { ChatXAIInput } from '@langchain/xai';
11
13
  import type * as t from '@langchain/openai';
12
14
  export declare function isHeaders(headers: unknown): headers is Headers;
13
15
  export declare function normalizeHeaders(headers: HeadersLike): Record<string, HeaderValue | readonly HeaderValue[]>;
14
16
  type OpenAICoreRequestOptions = OpenAIClient.RequestOptions;
17
+ type OpenAICompletionParam = OpenAIClient.Chat.Completions.ChatCompletionMessageParam;
15
18
  type LibreChatOpenAIFields = t.ChatOpenAIFields & {
16
19
  _lc_stream_delay?: number;
17
20
  includeReasoningContent?: boolean;
@@ -82,8 +85,22 @@ export declare class ChatDeepSeek extends OriginalChatDeepSeek {
82
85
  });
83
86
  get exposedClient(): CustomOpenAIClient;
84
87
  static lc_name(): 'LibreChatDeepSeek';
88
+ protected _convertDeepSeekMessages(messages: BaseMessage[]): OpenAICompletionParam[];
89
+ _generate(messages: BaseMessage[], options: this['ParsedCallOptions'], runManager?: CallbackManagerForLLMRun): Promise<ChatResult>;
85
90
  _getClientOptions(options?: OpenAICoreRequestOptions): OpenAICoreRequestOptions;
86
91
  _streamResponseChunks(messages: BaseMessage[], options: this['ParsedCallOptions'], runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
92
+ /** Parses raw `<think>` fallback tags across chunks and emits sanitized DeepSeek stream chunks. */
93
+ protected _streamResponseChunksWithReasoning(messages: BaseMessage[], options: this['ParsedCallOptions'], runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
94
+ protected _streamResponseChunksFromReasoningMessages(messages: BaseMessage[], options: this['ParsedCallOptions']): AsyncGenerator<ChatGenerationChunk>;
95
+ protected _createDeepSeekStreamChunk(chunk: ChatGenerationChunk, content: string, additionalKwargs?: AIMessageChunk['additional_kwargs'], text?: string): ChatGenerationChunk;
96
+ protected _createDeepSeekReasoningStreamChunk(chunk: ChatGenerationChunk, reasoningContent: string): ChatGenerationChunk;
97
+ protected _yieldDeepSeekReasoningText(chunk: ChatGenerationChunk, reasoningContent: string, runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
98
+ protected _yieldDeepSeekStreamChunk(chunk: ChatGenerationChunk, runManager?: CallbackManagerForLLMRun): AsyncGenerator<ChatGenerationChunk>;
99
+ protected _getDeepSeekTokenIndices(chunk: ChatGenerationChunk): {
100
+ prompt: number;
101
+ completion: number;
102
+ } | undefined;
103
+ protected _getDeepSeekPartialTagSplitIndex(text: string, tag: string): number;
87
104
  }
88
105
  /** xAI-specific usage metadata type */
89
106
  export interface XAIUsageMetadata extends OpenAIClient.Completions.CompletionUsage {
@@ -1,4 +1,5 @@
1
1
  import './instrumentation';
2
+ import { Command } from '@langchain/langgraph';
2
3
  import type { MessageContentComplex, BaseMessage } from '@langchain/core/messages';
3
4
  import type { RunnableConfig } from '@langchain/core/runnables';
4
5
  import type * as t from '@/types';
@@ -10,6 +11,7 @@ export declare class Run<_T extends t.BaseGraphState> {
10
11
  private tokenCounter?;
11
12
  private handlerRegistry?;
12
13
  private hookRegistry?;
14
+ private humanInTheLoop?;
13
15
  private toolOutputReferences?;
14
16
  private indexTokenCountMap?;
15
17
  calibrationRatio: number;
@@ -18,9 +20,70 @@ export declare class Run<_T extends t.BaseGraphState> {
18
20
  returnContent: boolean;
19
21
  private skipCleanup;
20
22
  private _streamResult;
23
+ /**
24
+ * Captured interrupt payload typed as `unknown` because the SDK
25
+ * does not validate the runtime shape — custom graph nodes can
26
+ * raise interrupts with arbitrary payloads (not just the SDK's
27
+ * `HumanInterruptPayload` union). The public `getInterrupt<T>()`
28
+ * lets callers assert the type they expect.
29
+ */
30
+ private _interrupt;
31
+ private _haltedReason;
21
32
  private constructor();
22
33
  private createLegacyGraph;
23
34
  private createMultiAgentGraph;
35
+ /**
36
+ * When the host opted into HITL via `humanInTheLoop: { enabled: true }`
37
+ * and did not supply a checkpointer, install an in-memory `MemorySaver`
38
+ * so `interrupt()` can persist checkpoints and `Command({ resume })`
39
+ * can rebuild state. The fallback is intentionally process-local:
40
+ * production hosts that need durable resumption across processes /
41
+ * restarts must provide their own checkpointer (Redis, Postgres, etc.)
42
+ * on `compileOptions.checkpointer`.
43
+ *
44
+ * No-op when HITL is off (the default — omitted, or
45
+ * `{ enabled: false }`) or the host already supplied a checkpointer
46
+ * of their own. See `HumanInTheLoopConfig` JSDoc for the rationale
47
+ * behind the default-off stance.
48
+ */
49
+ private applyHITLCheckpointerFallback;
50
+ /**
51
+ * Run RunStart + UserPromptSubmit hooks before the graph stream
52
+ * begins, accumulate any `additionalContext` strings into the input
53
+ * messages, and short-circuit when a hook signals the run should not
54
+ * proceed (deny / ask decision on the prompt, or `preventContinuation`
55
+ * on either hook).
56
+ *
57
+ * Returns `true` when the caller should bail with `undefined` (run
58
+ * was halted before any model call); returns `false` to proceed
59
+ * into the stream loop.
60
+ *
61
+ * ## Side effects
62
+ *
63
+ * On the success path:
64
+ * - Mutates `stateInputs.messages` in place to append a
65
+ * consolidated `HumanMessage` carrying any hook
66
+ * `additionalContext` strings. Safe because the host owns the
67
+ * array and `processStream` is the only consumer until LangGraph
68
+ * reads it.
69
+ *
70
+ * On the halt path (returning `true`):
71
+ * - Sets `this._haltedReason` so callers (and the eventual host)
72
+ * can distinguish a hook-driven halt from a natural completion.
73
+ * - Calls `registry.clearSession(this.id)` and
74
+ * `registry.clearHaltSignal(this.id)` because no resume is
75
+ * expected from a pre-stream halt — the run never entered the
76
+ * graph, so the session/halt state for this run would otherwise
77
+ * leak to the next `processStream` invocation on the same
78
+ * registry. Other concurrent runs on the same registry are
79
+ * untouched (halt signals are scoped per session id).
80
+ * - Sets `config.callbacks = undefined` to drop the callback
81
+ * references the caller built (langfuse handler, custom event
82
+ * handler, etc.) since they won't be exercised. Mirrors the
83
+ * equivalent cleanup the `processStream` `finally` block does
84
+ * on the natural-completion path.
85
+ */
86
+ private runPreStreamHooks;
24
87
  static create<T extends t.BaseGraphState>(config: t.RunConfig): Promise<Run<T>>;
25
88
  getRunMessages(): BaseMessage[] | undefined;
26
89
  /**
@@ -37,7 +100,60 @@ export declare class Run<_T extends t.BaseGraphState> {
37
100
  * and processes them through our handler registry instead of EventStreamCallbackHandler
38
101
  */
39
102
  private createCustomEventCallback;
40
- processStream(inputs: t.IState, callerConfig: Partial<RunnableConfig> & {
103
+ processStream(inputs: t.IState | Command, callerConfig: Partial<RunnableConfig> & {
104
+ version: 'v1' | 'v2';
105
+ run_id?: string;
106
+ }, streamOptions?: t.EventStreamOptions): Promise<MessageContentComplex[] | undefined>;
107
+ /**
108
+ * Returns the pending interrupt captured during the most recent
109
+ * `processStream` (or `resume`) invocation. `undefined` when the run
110
+ * either has not been streamed yet or completed without pausing.
111
+ *
112
+ * Hosts call this immediately after `processStream` returns to decide
113
+ * whether the run is awaiting human input. Persist the returned
114
+ * descriptor (alongside `thread_id` and the agent run config) so a
115
+ * later `resume(decisions)` can rebuild the run.
116
+ *
117
+ * The default `TPayload` is the SDK's `HumanInterruptPayload` union
118
+ * (`tool_approval` / `ask_user_question`), suitable for the common
119
+ * case where interrupts come from the built-in ToolNode or
120
+ * `askUserQuestion()` helper. Hosts that raise custom interrupts
121
+ * from custom graph nodes pass their own type — the SDK does not
122
+ * validate the runtime shape, it just transports whatever the
123
+ * `interrupt()` call carried. When in doubt, narrow with the
124
+ * `isToolApprovalInterrupt` / `isAskUserQuestionInterrupt` type
125
+ * guards (which accept `unknown`) before reading variant-specific
126
+ * fields.
127
+ */
128
+ getInterrupt<TPayload = t.HumanInterruptPayload>(): t.RunInterruptResult<TPayload> | undefined;
129
+ /**
130
+ * Returns the reason a hook halted the run via
131
+ * `preventContinuation: true`, or `undefined` if no hook halted.
132
+ *
133
+ * Hosts inspect this after `processStream` returns to distinguish a
134
+ * natural completion (`undefined`) from a hook-driven halt (a
135
+ * truthy string). Independent from `getInterrupt()` — a halted run
136
+ * has no interrupt; an interrupted run has no halt reason.
137
+ */
138
+ getHaltReason(): string | undefined;
139
+ /**
140
+ * Resume a paused HITL run with the value the user (or whatever
141
+ * decided the interrupt) supplied. The default `TResume` covers the
142
+ * `tool_approval` interrupt (the common case): an array of decisions
143
+ * in `action_requests` order, or a record keyed by `tool_call_id`.
144
+ *
145
+ * For other interrupt types (e.g., `ask_user_question` →
146
+ * `AskUserQuestionResolution`, or any custom interrupt a host raises
147
+ * from a custom node), pass the type parameter and the SDK forwards
148
+ * the value through unchanged. LangGraph delivers it as the return
149
+ * value of the original `interrupt()` call inside the paused node.
150
+ *
151
+ * The host MUST construct this Run with the same `thread_id` and the
152
+ * same checkpointer as the original paused run; LangGraph rebuilds
153
+ * graph state from the checkpoint and re-enters the interrupted node
154
+ * from the start.
155
+ */
156
+ resume<TResume = t.ToolApprovalDecision[] | t.ToolApprovalDecisionMap>(resumeValue: TResume, callerConfig: Partial<RunnableConfig> & {
41
157
  version: 'v1' | 'v2';
42
158
  run_id?: string;
43
159
  }, streamOptions?: t.EventStreamOptions): Promise<MessageContentComplex[] | undefined>;
@@ -47,6 +47,12 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
47
47
  private maxToolResultChars;
48
48
  /** Hook registry for PreToolUse/PostToolUse lifecycle hooks */
49
49
  private hookRegistry?;
50
+ /**
51
+ * Run-scoped HITL config. When `enabled`, `ask` decisions from
52
+ * PreToolUse hooks raise a LangGraph `interrupt()` instead of being
53
+ * treated as fail-closed denies.
54
+ */
55
+ private humanInTheLoop?;
50
56
  /**
51
57
  * Registry of tool outputs keyed by `tool<idx>turn<turn>`.
52
58
  *
@@ -67,7 +73,7 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
67
73
  * other's in-flight state.
68
74
  */
69
75
  private anonBatchCounter;
70
- constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, maxContextTokens, maxToolResultChars, hookRegistry, toolOutputReferences, toolOutputRegistry, }: t.ToolNodeConstructorParams);
76
+ constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, }: t.ToolNodeConstructorParams);
71
77
  /**
72
78
  * Returns the run-scoped tool output registry, or `undefined` when
73
79
  * the feature is disabled.
@@ -155,6 +161,25 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
155
161
  * ToolMessages (appended AFTER to respect provider ordering).
156
162
  */
157
163
  private dispatchToolEvents;
164
+ /**
165
+ * Fires the `PostToolBatch` hook (if registered) and appends the
166
+ * accumulated batch-level `additionalContext` strings to `injected`
167
+ * as a single `HumanMessage`. Entries are materialized in the
168
+ * original `toolCalls` order so hooks correlating outcomes by
169
+ * position (as the type docs promise) see exactly the sequence
170
+ * the model emitted, regardless of when each individual outcome
171
+ * was recorded into the map (deny synchronous, approved
172
+ * post-execution, respond on resume).
173
+ *
174
+ * The PostToolBatch hook's `additionalContexts` flow into the same
175
+ * batch accumulator per-tool hooks already use, so a single
176
+ * batch-level convention message can be injected through one path.
177
+ *
178
+ * Mutates `batchAdditionalContexts` (push from batch hook) and
179
+ * `injected` (push the consolidated HumanMessage). The caller owns
180
+ * those arrays and consumes them right after this returns.
181
+ */
182
+ private dispatchPostToolBatchAndInjectContext;
158
183
  private dispatchStepCompleted;
159
184
  /**
160
185
  * Converts InjectedMessage instances to LangChain HumanMessage objects.