@librechat/agents 3.1.76 → 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.
- package/dist/cjs/graphs/Graph.cjs +9 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestion.cjs +67 -0
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -0
- package/dist/cjs/hooks/HookRegistry.cjs +54 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/createToolPolicyHook.cjs +115 -0
- package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -0
- package/dist/cjs/hooks/executeHooks.cjs +40 -1
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/main.cjs +29 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/run.cjs +400 -42
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +551 -55
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +9 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestion.mjs +65 -0
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
- package/dist/esm/hooks/HookRegistry.mjs +54 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
- package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
- package/dist/esm/hooks/executeHooks.mjs +40 -1
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/run.mjs +400 -42
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +552 -56
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -0
- package/dist/types/hitl/askUserQuestion.d.ts +55 -0
- package/dist/types/hitl/index.d.ts +6 -0
- package/dist/types/hooks/HookRegistry.d.ts +58 -0
- package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
- package/dist/types/hooks/index.d.ts +4 -1
- package/dist/types/hooks/types.d.ts +109 -3
- package/dist/types/index.d.ts +9 -0
- package/dist/types/run.d.ts +117 -1
- package/dist/types/tools/ToolNode.d.ts +26 -1
- package/dist/types/types/hitl.d.ts +272 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +33 -0
- package/dist/types/types/tools.d.ts +19 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +9 -0
- package/src/hitl/askUserQuestion.ts +72 -0
- package/src/hitl/index.ts +7 -0
- package/src/hooks/HookRegistry.ts +71 -0
- package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
- package/src/hooks/createToolPolicyHook.ts +184 -0
- package/src/hooks/executeHooks.ts +50 -1
- package/src/hooks/index.ts +6 -0
- package/src/hooks/types.ts +112 -0
- package/src/index.ts +19 -0
- package/src/run.ts +456 -47
- package/src/tools/ToolNode.ts +701 -62
- package/src/tools/__tests__/hitl.test.ts +3593 -0
- package/src/tools/search/tavily-scraper.ts +4 -4
- package/src/tools/search/tavily-search.ts +32 -32
- package/src/tools/search/tool.ts +3 -3
- package/src/tools/search/types.ts +3 -1
- package/src/types/hitl.ts +303 -0
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +33 -0
- 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
|
*
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/types/run.d.ts
CHANGED
|
@@ -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.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-class human-in-the-loop (HITL) types for `@librechat/agents`.
|
|
3
|
+
* Surfaces the interrupt payload that `ToolNode` raises when a `PreToolUse`
|
|
4
|
+
* hook returns `decision: 'ask'` and HITL is enabled on the run, plus the
|
|
5
|
+
* resume-decision shape the host returns to continue or reject the tool.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the LangChain HITL middleware shape (action_requests /
|
|
8
|
+
* review_configs) so hosts and clients can share rendering/UI semantics
|
|
9
|
+
* across the langchain ecosystem.
|
|
10
|
+
*/
|
|
11
|
+
/** Per-tool approval request emitted inside an interrupt payload. */
|
|
12
|
+
export interface ToolApprovalRequest {
|
|
13
|
+
/** Stable id of the tool call (matches LangGraph `ToolCall.id`). */
|
|
14
|
+
tool_call_id: string;
|
|
15
|
+
/** Tool name being invoked. */
|
|
16
|
+
name: string;
|
|
17
|
+
/**
|
|
18
|
+
* Arguments the tool is about to be invoked with — already resolved by
|
|
19
|
+
* any `{{tool<i>turn<n>}}` references and any `updatedInput` returned
|
|
20
|
+
* by the firing PreToolUse hook.
|
|
21
|
+
*/
|
|
22
|
+
arguments: Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Optional reason the hook supplied for asking (e.g., "destructive
|
|
25
|
+
* filesystem write"). Hosts can render this verbatim.
|
|
26
|
+
*/
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Allowed host-side decisions for a `tool_approval` interrupt. */
|
|
30
|
+
export type ToolApprovalDecisionType = 'approve' | 'reject' | 'edit' | 'respond';
|
|
31
|
+
/** Per-action review configuration paired with each action_request. */
|
|
32
|
+
export interface ToolApprovalReviewConfig {
|
|
33
|
+
/** Tool name (matches the `name` field on the corresponding action_request). */
|
|
34
|
+
action_name: string;
|
|
35
|
+
/**
|
|
36
|
+
* Stable id of the tool call this review_config applies to (matches
|
|
37
|
+
* the `tool_call_id` of the corresponding action_request). Lets a UI
|
|
38
|
+
* map review_configs → action_requests directly when a batch
|
|
39
|
+
* contains the same tool called more than once — by-position
|
|
40
|
+
* mapping breaks down with duplicates.
|
|
41
|
+
*/
|
|
42
|
+
tool_call_id: string;
|
|
43
|
+
/** Decisions the host UI is allowed to surface for this action. */
|
|
44
|
+
allowed_decisions: ToolApprovalDecisionType[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resume value the host returns through `Run.resume(decisions)` after a
|
|
48
|
+
* `tool_approval` interrupt. One entry per action_request, in the same
|
|
49
|
+
* order. Hosts may also return a record keyed by `tool_call_id`; the SDK
|
|
50
|
+
* handles either shape.
|
|
51
|
+
*
|
|
52
|
+
* Variants:
|
|
53
|
+
* - `approve`: run the tool with its original (or hook-rewritten) args.
|
|
54
|
+
* - `reject`: skip the tool, emit a blocked error `ToolMessage` with
|
|
55
|
+
* `reason` surfaced to the model.
|
|
56
|
+
* - `edit`: replace the tool's args with `updatedInput` (re-resolves
|
|
57
|
+
* any `{{tool<i>turn<n>}}` placeholders) and run the tool.
|
|
58
|
+
* - `respond`: skip the tool entirely and emit `responseText` as a
|
|
59
|
+
* successful `ToolMessage`. Mirrors LangChain HITL middleware's
|
|
60
|
+
* `respond` semantic — the human supplies the result the model sees,
|
|
61
|
+
* bypassing tool execution. Useful when the user wants to short-circuit
|
|
62
|
+
* a tool call with a hand-written answer (e.g., "don't actually run
|
|
63
|
+
* the search, just tell the model 'no relevant results'").
|
|
64
|
+
*
|
|
65
|
+
* Note on hook semantics: `respond` does NOT fire the per-tool
|
|
66
|
+
* `PostToolUse` hook (no real tool execution happened, so the
|
|
67
|
+
* "post-tool" semantic doesn't apply). It DOES appear in the
|
|
68
|
+
* `PostToolBatch` entry array with `status: 'success'` and the
|
|
69
|
+
* user-supplied text as `toolOutput`, so batch-level audit /
|
|
70
|
+
* convention hooks see the full set of outcomes.
|
|
71
|
+
*/
|
|
72
|
+
export type ToolApprovalDecision = {
|
|
73
|
+
type: 'approve';
|
|
74
|
+
} | {
|
|
75
|
+
type: 'reject';
|
|
76
|
+
reason?: string;
|
|
77
|
+
} | {
|
|
78
|
+
type: 'edit';
|
|
79
|
+
updatedInput: Record<string, unknown>;
|
|
80
|
+
} | {
|
|
81
|
+
type: 'respond';
|
|
82
|
+
responseText: string;
|
|
83
|
+
};
|
|
84
|
+
/** Map form of resume decisions, keyed by tool call id. */
|
|
85
|
+
export type ToolApprovalDecisionMap = Record<string, ToolApprovalDecision>;
|
|
86
|
+
/**
|
|
87
|
+
* Categories of human-in-the-loop interrupts the SDK can raise. Hosts
|
|
88
|
+
* narrow on `HumanInterruptPayload.type` to determine which payload
|
|
89
|
+
* shape they're handling and which resume value to send back through
|
|
90
|
+
* `Run.resume()`.
|
|
91
|
+
*
|
|
92
|
+
* Exported as a discrete type so downstream consumers (notably
|
|
93
|
+
* LibreChat's wire types in `librechat-data-provider`) can mirror
|
|
94
|
+
* the discriminator alongside their own host-side `PendingAction`
|
|
95
|
+
* record without re-declaring the union themselves. Internal SDK
|
|
96
|
+
* code narrows directly on the literal strings via the type guards
|
|
97
|
+
* below; this type alias is primarily an integration-layer contract.
|
|
98
|
+
*/
|
|
99
|
+
export type HumanInterruptType = 'tool_approval' | 'ask_user_question';
|
|
100
|
+
/**
|
|
101
|
+
* Structured payload the SDK passes to `interrupt()` when one or more
|
|
102
|
+
* pending tool calls require host approval. All `ask`-decision tool calls
|
|
103
|
+
* from a single ToolNode batch are bundled into one interrupt so the host
|
|
104
|
+
* can render and resolve them together.
|
|
105
|
+
*
|
|
106
|
+
* Resume value: `ToolApprovalDecision[]` (in `action_requests` order) or
|
|
107
|
+
* `ToolApprovalDecisionMap` (keyed by `tool_call_id`).
|
|
108
|
+
*/
|
|
109
|
+
export interface ToolApprovalInterruptPayload {
|
|
110
|
+
type: 'tool_approval';
|
|
111
|
+
action_requests: ToolApprovalRequest[];
|
|
112
|
+
review_configs: ToolApprovalReviewConfig[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Pre-defined option the user can pick when answering an
|
|
116
|
+
* `ask_user_question` interrupt. The selected option's `value` becomes
|
|
117
|
+
* the resume value's `answer` field.
|
|
118
|
+
*/
|
|
119
|
+
export interface AskUserQuestionOption {
|
|
120
|
+
/** Human-readable label rendered in the host UI. */
|
|
121
|
+
label: string;
|
|
122
|
+
/** Value returned via `AskUserQuestionResolution.answer` if picked. */
|
|
123
|
+
value: string;
|
|
124
|
+
}
|
|
125
|
+
/** Question request emitted inside an `ask_user_question` interrupt. */
|
|
126
|
+
export interface AskUserQuestionRequest {
|
|
127
|
+
/** The question to ask the human. */
|
|
128
|
+
question: string;
|
|
129
|
+
/** Optional context / description rendered alongside the question. */
|
|
130
|
+
description?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Optional pre-defined response options. When present, hosts can render
|
|
133
|
+
* a picker; the user may still type a free-form answer when the host
|
|
134
|
+
* UI allows it. Omit to require a free-form answer.
|
|
135
|
+
*/
|
|
136
|
+
options?: AskUserQuestionOption[];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Structured payload the SDK passes to `interrupt()` when an agent (or
|
|
140
|
+
* a custom node) needs to ask the user a clarifying question. Mirrors
|
|
141
|
+
* Claude Code's `AskUserQuestion` semantic. Resume value:
|
|
142
|
+
* `AskUserQuestionResolution`.
|
|
143
|
+
*/
|
|
144
|
+
export interface AskUserQuestionInterruptPayload {
|
|
145
|
+
type: 'ask_user_question';
|
|
146
|
+
question: AskUserQuestionRequest;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Discriminated union of every interrupt payload the SDK raises. New
|
|
150
|
+
* variants can be added without breaking existing handlers as long as
|
|
151
|
+
* those handlers check `payload.type` before reading variant-specific
|
|
152
|
+
* fields. Use the `isToolApprovalInterrupt` / `isAskUserQuestionInterrupt`
|
|
153
|
+
* type guards for ergonomic narrowing.
|
|
154
|
+
*/
|
|
155
|
+
export type HumanInterruptPayload = ToolApprovalInterruptPayload | AskUserQuestionInterruptPayload;
|
|
156
|
+
/** Resume value the host returns for an `ask_user_question` interrupt. */
|
|
157
|
+
export interface AskUserQuestionResolution {
|
|
158
|
+
/**
|
|
159
|
+
* The human's answer. Free-form text, or — when `options` were
|
|
160
|
+
* provided — one of the option `value`s. Hosts may also send any
|
|
161
|
+
* structured object their custom UI defines; see the host docs for
|
|
162
|
+
* what your downstream consumer expects.
|
|
163
|
+
*/
|
|
164
|
+
answer: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Type guard narrowing an arbitrary value to a `ToolApprovalInterruptPayload`.
|
|
168
|
+
* Accepts `unknown` (not just `HumanInterruptPayload`) because hosts can
|
|
169
|
+
* raise custom interrupt payloads from custom nodes — `getInterrupt()`
|
|
170
|
+
* surfaces them as-is, and downstream code must validate the shape at
|
|
171
|
+
* runtime before reading variant-specific fields.
|
|
172
|
+
*/
|
|
173
|
+
export declare function isToolApprovalInterrupt(payload: unknown): payload is ToolApprovalInterruptPayload;
|
|
174
|
+
/**
|
|
175
|
+
* Type guard narrowing an arbitrary value to an
|
|
176
|
+
* `AskUserQuestionInterruptPayload`. Same `unknown`-tolerant contract
|
|
177
|
+
* as `isToolApprovalInterrupt`.
|
|
178
|
+
*/
|
|
179
|
+
export declare function isAskUserQuestionInterrupt(payload: unknown): payload is AskUserQuestionInterruptPayload;
|
|
180
|
+
/**
|
|
181
|
+
* Run-level configuration controlling HITL semantics. **HITL is OFF by
|
|
182
|
+
* default** for now — the SDK ships the interrupt machinery, but the
|
|
183
|
+
* default stays opt-in until host UIs (notably LibreChat) ship the
|
|
184
|
+
* approval-rendering affordances needed to surface interrupts to end
|
|
185
|
+
* users. Without that UI, an interrupt with no resolver looks like a
|
|
186
|
+
* hung tool-call card. Hosts opt in explicitly with
|
|
187
|
+
* `{ enabled: true }`. The intent is to flip this default to ON in a
|
|
188
|
+
* future minor once the consumer ecosystem is ready to render
|
|
189
|
+
* interrupts end-to-end.
|
|
190
|
+
*
|
|
191
|
+
* When enabled (`{ enabled: true }`):
|
|
192
|
+
*
|
|
193
|
+
* - `PreToolUse` hooks returning `decision: 'ask'` raise a real
|
|
194
|
+
* LangGraph `interrupt()` instead of being treated as a synchronous
|
|
195
|
+
* deny.
|
|
196
|
+
* - `Run.create` installs a `MemorySaver` checkpointer fallback on the
|
|
197
|
+
* run's compile options if the host did not provide one, since
|
|
198
|
+
* LangGraph requires a checkpointer to suspend and resume.
|
|
199
|
+
*
|
|
200
|
+
* When disabled (the default — omitted, or `{ enabled: false }`):
|
|
201
|
+
* `ask` decisions are fail-closed (blocked with an error
|
|
202
|
+
* `ToolMessage`) and no checkpointer is implicitly attached. This
|
|
203
|
+
* matches the pre-HITL behavior so existing hosts upgrading the SDK
|
|
204
|
+
* see no change until they're ready to wire the resume UI.
|
|
205
|
+
*
|
|
206
|
+
* ## Scope: event-driven tools only
|
|
207
|
+
*
|
|
208
|
+
* The interrupt path is wired into `ToolNode.dispatchToolEvents`, which
|
|
209
|
+
* runs when the agent uses event-driven tool dispatch (the path
|
|
210
|
+
* LibreChat and most production hosts take). Tools that execute via
|
|
211
|
+
* the direct path — i.e. tools listed in `directToolNames` (the
|
|
212
|
+
* graph-managed handoff and subagent tools) or tools on agents
|
|
213
|
+
* configured WITHOUT `eventDrivenMode` — bypass the hook system
|
|
214
|
+
* entirely. `PreToolUse` hooks do not fire for those tools and HITL
|
|
215
|
+
* approval does not gate them.
|
|
216
|
+
*
|
|
217
|
+
* Practical implications:
|
|
218
|
+
* - LibreChat-style hosts using event-driven dispatch get the full
|
|
219
|
+
* HITL surface across every tool the model calls.
|
|
220
|
+
* - Hosts using `AgentInputs.tools` directly without event-driven
|
|
221
|
+
* mode get policy enforcement for nothing — the hooks register
|
|
222
|
+
* but never fire. Either switch to event-driven mode or accept
|
|
223
|
+
* that direct tools are not approval-gated. This is documented
|
|
224
|
+
* also on `ToolNodeOptions.hookRegistry`.
|
|
225
|
+
* - Mixed direct + event batches (e.g. a handoff tool sharing an
|
|
226
|
+
* LLM turn with a regular tool) currently re-execute the direct
|
|
227
|
+
* half on resume, since LangGraph rolls back the entire ToolNode
|
|
228
|
+
* on `interrupt()` throw. Hosts whose direct tools have side
|
|
229
|
+
* effects (subagents that invoke models, handoffs that trigger
|
|
230
|
+
* downstream work) should avoid mixing those tools into the same
|
|
231
|
+
* batch as approval-gated event tools.
|
|
232
|
+
*
|
|
233
|
+
* ## Note on idempotency
|
|
234
|
+
*
|
|
235
|
+
* When an interrupt fires, LangGraph re-runs the interrupted node
|
|
236
|
+
* from the start on resume, which fires `PreToolUse` hooks again.
|
|
237
|
+
* Hooks that produce side effects (logging, external calls) will see
|
|
238
|
+
* two invocations per paused turn.
|
|
239
|
+
*/
|
|
240
|
+
export interface HumanInTheLoopConfig {
|
|
241
|
+
/**
|
|
242
|
+
* Master switch. Defaults to `false` — omit the field (or pass
|
|
243
|
+
* `false`) to keep HITL off, or set `true` to opt in once the host
|
|
244
|
+
* UI is ready to render and resolve `tool_approval` interrupts.
|
|
245
|
+
*/
|
|
246
|
+
enabled?: boolean;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Snapshot of an in-flight interrupt surfaced from `Run.processStream`
|
|
250
|
+
* via `run.getInterrupt()`. Hosts persist this alongside their job
|
|
251
|
+
* record so they can later call `Run.resume(decisions)` against a Run
|
|
252
|
+
* compiled with the same `thread_id` / checkpointer.
|
|
253
|
+
*
|
|
254
|
+
* The `payload` type defaults to `HumanInterruptPayload` (the SDK's
|
|
255
|
+
* built-in `tool_approval` / `ask_user_question` discriminated union)
|
|
256
|
+
* for ergonomic narrowing in the common case. Hosts that raise custom
|
|
257
|
+
* interrupt payloads from custom graph nodes can pass the type
|
|
258
|
+
* parameter (`run.getInterrupt<MyCustom>()` or
|
|
259
|
+
* `RunInterruptResult<MyCustom>`) — the SDK does not validate the
|
|
260
|
+
* runtime shape, it just transports whatever the node passed to
|
|
261
|
+
* `interrupt()`. Use the `isToolApprovalInterrupt` /
|
|
262
|
+
* `isAskUserQuestionInterrupt` guards (which accept `unknown`) when
|
|
263
|
+
* the source of the interrupt isn't statically known.
|
|
264
|
+
*/
|
|
265
|
+
export interface RunInterruptResult<TPayload = HumanInterruptPayload> {
|
|
266
|
+
/** Stable id of the LangGraph interrupt (from `Interrupt.id`). */
|
|
267
|
+
interruptId: string;
|
|
268
|
+
/** `thread_id` the run was bound to — required to resume. */
|
|
269
|
+
threadId?: string;
|
|
270
|
+
/** Structured payload describing what needs human input. */
|
|
271
|
+
payload: TPayload;
|
|
272
|
+
}
|