@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.
- package/dist/cjs/graphs/Graph.cjs +22 -3
- 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/langchain/google-common.cjs +3 -0
- package/dist/cjs/langchain/google-common.cjs.map +1 -0
- package/dist/cjs/langchain/index.cjs +86 -0
- package/dist/cjs/langchain/index.cjs.map +1 -0
- package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
- package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
- package/dist/cjs/langchain/messages/tool.cjs +3 -0
- package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
- package/dist/cjs/langchain/messages.cjs +51 -0
- package/dist/cjs/langchain/messages.cjs.map +1 -0
- package/dist/cjs/langchain/openai.cjs +3 -0
- package/dist/cjs/langchain/openai.cjs.map +1 -0
- package/dist/cjs/langchain/prompts.cjs +11 -0
- package/dist/cjs/langchain/prompts.cjs.map +1 -0
- package/dist/cjs/langchain/runnables.cjs +19 -0
- package/dist/cjs/langchain/runnables.cjs.map +1 -0
- package/dist/cjs/langchain/tools.cjs +23 -0
- package/dist/cjs/langchain/tools.cjs.map +1 -0
- package/dist/cjs/langchain/utils/env.cjs +11 -0
- package/dist/cjs/langchain/utils/env.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/index.cjs +145 -52
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +21 -14
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +5 -4
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +519 -655
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +20 -458
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +57 -175
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +5 -3
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +112 -3
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +2 -1
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +7 -6
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +73 -15
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/langchain.cjs +26 -0
- package/dist/cjs/messages/langchain.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +7 -6
- package/dist/cjs/messages/prune.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 +556 -56
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +55 -66
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tavily-search.cjs +372 -0
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +26 -4
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +10 -3
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +22 -3
- 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/langchain/google-common.mjs +2 -0
- package/dist/esm/langchain/google-common.mjs.map +1 -0
- package/dist/esm/langchain/index.mjs +5 -0
- package/dist/esm/langchain/index.mjs.map +1 -0
- package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
- package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
- package/dist/esm/langchain/messages/tool.mjs +2 -0
- package/dist/esm/langchain/messages/tool.mjs.map +1 -0
- package/dist/esm/langchain/messages.mjs +2 -0
- package/dist/esm/langchain/messages.mjs.map +1 -0
- package/dist/esm/langchain/openai.mjs +2 -0
- package/dist/esm/langchain/openai.mjs.map +1 -0
- package/dist/esm/langchain/prompts.mjs +2 -0
- package/dist/esm/langchain/prompts.mjs.map +1 -0
- package/dist/esm/langchain/runnables.mjs +2 -0
- package/dist/esm/langchain/runnables.mjs.map +1 -0
- package/dist/esm/langchain/tools.mjs +2 -0
- package/dist/esm/langchain/tools.mjs.map +1 -0
- package/dist/esm/langchain/utils/env.mjs +2 -0
- package/dist/esm/langchain/utils/env.mjs.map +1 -0
- package/dist/esm/llm/anthropic/index.mjs +146 -54
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +21 -14
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +5 -4
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +520 -656
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +23 -459
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +57 -175
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +5 -3
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +2 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +7 -6
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +73 -15
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/langchain.mjs +23 -0
- package/dist/esm/messages/langchain.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +7 -6
- package/dist/esm/messages/prune.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 +557 -57
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +55 -66
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tavily-search.mjs +370 -0
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +26 -4
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +10 -3
- package/dist/esm/tools/search/utils.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 +10 -0
- package/dist/types/langchain/google-common.d.ts +1 -0
- package/dist/types/langchain/index.d.ts +8 -0
- package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
- package/dist/types/langchain/messages/tool.d.ts +1 -0
- package/dist/types/langchain/messages.d.ts +2 -0
- package/dist/types/langchain/openai.d.ts +1 -0
- package/dist/types/langchain/prompts.d.ts +1 -0
- package/dist/types/langchain/runnables.d.ts +2 -0
- package/dist/types/langchain/tools.d.ts +2 -0
- package/dist/types/langchain/utils/env.d.ts +1 -0
- package/dist/types/llm/anthropic/index.d.ts +22 -9
- package/dist/types/llm/anthropic/types.d.ts +5 -1
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +21 -24
- package/dist/types/llm/openrouter/index.d.ts +11 -9
- package/dist/types/llm/vertexai/index.d.ts +1 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/messages/format.d.ts +4 -1
- package/dist/types/messages/langchain.d.ts +27 -0
- package/dist/types/run.d.ts +117 -1
- package/dist/types/tools/ToolNode.d.ts +26 -1
- package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
- package/dist/types/tools/search/tavily-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +99 -5
- package/dist/types/tools/search/utils.d.ts +2 -2
- package/dist/types/types/graph.d.ts +23 -37
- package/dist/types/types/hitl.d.ts +272 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/llm.d.ts +3 -3
- package/dist/types/types/run.d.ts +33 -0
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/types/tools.d.ts +19 -0
- package/package.json +80 -17
- package/src/graphs/Graph.ts +33 -4
- package/src/graphs/__tests__/composition.smoke.test.ts +188 -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 +22 -0
- package/src/langchain/google-common.ts +1 -0
- package/src/langchain/index.ts +8 -0
- package/src/langchain/language_models/chat_models.ts +1 -0
- package/src/langchain/messages/tool.ts +5 -0
- package/src/langchain/messages.ts +21 -0
- package/src/langchain/openai.ts +1 -0
- package/src/langchain/prompts.ts +1 -0
- package/src/langchain/runnables.ts +7 -0
- package/src/langchain/tools.ts +8 -0
- package/src/langchain/utils/env.ts +1 -0
- package/src/llm/anthropic/index.ts +252 -84
- package/src/llm/anthropic/llm.spec.ts +751 -102
- package/src/llm/anthropic/types.ts +9 -1
- package/src/llm/anthropic/utils/message_inputs.ts +37 -19
- package/src/llm/anthropic/utils/message_outputs.ts +119 -101
- package/src/llm/bedrock/index.ts +2 -2
- package/src/llm/bedrock/llm.spec.ts +341 -0
- package/src/llm/bedrock/utils/message_inputs.ts +303 -4
- package/src/llm/bedrock/utils/message_outputs.ts +2 -1
- package/src/llm/custom-chat-models.smoke.test.ts +836 -0
- package/src/llm/google/llm.spec.ts +339 -57
- package/src/llm/google/utils/common.ts +53 -48
- package/src/llm/openai/contentBlocks.test.ts +346 -0
- package/src/llm/openai/index.ts +856 -833
- package/src/llm/openai/utils/index.ts +107 -78
- package/src/llm/openai/utils/messages.test.ts +159 -0
- package/src/llm/openrouter/index.ts +124 -247
- package/src/llm/openrouter/reasoning.test.ts +8 -1
- package/src/llm/vertexai/index.ts +11 -5
- package/src/llm/vertexai/llm.spec.ts +28 -1
- package/src/messages/cache.test.ts +4 -3
- package/src/messages/cache.ts +3 -2
- package/src/messages/core.ts +16 -9
- package/src/messages/format.ts +96 -16
- package/src/messages/formatAgentMessages.test.ts +166 -1
- package/src/messages/langchain.ts +39 -0
- package/src/messages/prune.ts +12 -8
- package/src/run.ts +456 -47
- package/src/scripts/caching.ts +2 -3
- package/src/specs/summarization.test.ts +51 -58
- package/src/tools/ToolNode.ts +706 -63
- package/src/tools/__tests__/hitl.test.ts +3593 -0
- package/src/tools/search/search.ts +83 -73
- package/src/tools/search/tavily-scraper.ts +235 -0
- package/src/tools/search/tavily-search.ts +424 -0
- package/src/tools/search/tavily.test.ts +965 -0
- package/src/tools/search/tool.ts +36 -26
- package/src/tools/search/types.ts +133 -8
- package/src/tools/search/utils.ts +13 -5
- package/src/types/graph.ts +32 -87
- package/src/types/hitl.ts +303 -0
- package/src/types/index.ts +1 -0
- package/src/types/llm.ts +3 -3
- package/src/types/run.ts +33 -0
- package/src/types/stream.ts +1 -1
- package/src/types/tools.ts +19 -0
- package/src/utils/llmConfig.ts +1 -6
package/src/run.ts
CHANGED
|
@@ -4,6 +4,13 @@ import { CallbackHandler } from '@langfuse/langchain';
|
|
|
4
4
|
import { PromptTemplate } from '@langchain/core/prompts';
|
|
5
5
|
import { RunnableLambda } from '@langchain/core/runnables';
|
|
6
6
|
import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
|
|
7
|
+
import {
|
|
8
|
+
Command,
|
|
9
|
+
INTERRUPT,
|
|
10
|
+
MemorySaver,
|
|
11
|
+
isInterrupted,
|
|
12
|
+
} from '@langchain/langgraph';
|
|
13
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
7
14
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
8
15
|
import type {
|
|
9
16
|
MessageContentComplex,
|
|
@@ -45,6 +52,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
45
52
|
private tokenCounter?: t.TokenCounter;
|
|
46
53
|
private handlerRegistry?: HandlerRegistry;
|
|
47
54
|
private hookRegistry?: HookRegistry;
|
|
55
|
+
private humanInTheLoop?: t.HumanInTheLoopConfig;
|
|
48
56
|
private toolOutputReferences?: t.ToolOutputReferencesConfig;
|
|
49
57
|
private indexTokenCountMap?: Record<string, number>;
|
|
50
58
|
calibrationRatio: number = 1;
|
|
@@ -53,6 +61,15 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
53
61
|
returnContent: boolean = false;
|
|
54
62
|
private skipCleanup: boolean = false;
|
|
55
63
|
private _streamResult: t.MessageContentComplex[] | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Captured interrupt payload typed as `unknown` because the SDK
|
|
66
|
+
* does not validate the runtime shape — custom graph nodes can
|
|
67
|
+
* raise interrupts with arbitrary payloads (not just the SDK's
|
|
68
|
+
* `HumanInterruptPayload` union). The public `getInterrupt<T>()`
|
|
69
|
+
* lets callers assert the type they expect.
|
|
70
|
+
*/
|
|
71
|
+
private _interrupt: t.RunInterruptResult<unknown> | undefined;
|
|
72
|
+
private _haltedReason: string | undefined;
|
|
56
73
|
|
|
57
74
|
private constructor(config: Partial<t.RunConfig>) {
|
|
58
75
|
const runId = config.runId ?? '';
|
|
@@ -79,6 +96,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
79
96
|
|
|
80
97
|
this.handlerRegistry = handlerRegistry;
|
|
81
98
|
this.hookRegistry = config.hooks;
|
|
99
|
+
this.humanInTheLoop = config.humanInTheLoop;
|
|
82
100
|
this.toolOutputReferences = config.toolOutputReferences;
|
|
83
101
|
|
|
84
102
|
if (!config.graphConfig) {
|
|
@@ -154,8 +172,11 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
154
172
|
calibrationRatio: this.calibrationRatio,
|
|
155
173
|
});
|
|
156
174
|
/** Propagate compile options from graph config */
|
|
157
|
-
standardGraph.compileOptions =
|
|
175
|
+
standardGraph.compileOptions = this.applyHITLCheckpointerFallback(
|
|
176
|
+
config.compileOptions
|
|
177
|
+
);
|
|
158
178
|
standardGraph.hookRegistry = this.hookRegistry;
|
|
179
|
+
standardGraph.humanInTheLoop = this.humanInTheLoop;
|
|
159
180
|
standardGraph.toolOutputReferences = this.toolOutputReferences;
|
|
160
181
|
this.Graph = standardGraph;
|
|
161
182
|
return standardGraph.createWorkflow();
|
|
@@ -175,16 +196,197 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
175
196
|
calibrationRatio: this.calibrationRatio,
|
|
176
197
|
});
|
|
177
198
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
199
|
+
multiAgentGraph.compileOptions =
|
|
200
|
+
this.applyHITLCheckpointerFallback(compileOptions);
|
|
181
201
|
|
|
182
202
|
multiAgentGraph.hookRegistry = this.hookRegistry;
|
|
203
|
+
multiAgentGraph.humanInTheLoop = this.humanInTheLoop;
|
|
183
204
|
multiAgentGraph.toolOutputReferences = this.toolOutputReferences;
|
|
184
205
|
this.Graph = multiAgentGraph;
|
|
185
206
|
return multiAgentGraph.createWorkflow();
|
|
186
207
|
}
|
|
187
208
|
|
|
209
|
+
/**
|
|
210
|
+
* When the host opted into HITL via `humanInTheLoop: { enabled: true }`
|
|
211
|
+
* and did not supply a checkpointer, install an in-memory `MemorySaver`
|
|
212
|
+
* so `interrupt()` can persist checkpoints and `Command({ resume })`
|
|
213
|
+
* can rebuild state. The fallback is intentionally process-local:
|
|
214
|
+
* production hosts that need durable resumption across processes /
|
|
215
|
+
* restarts must provide their own checkpointer (Redis, Postgres, etc.)
|
|
216
|
+
* on `compileOptions.checkpointer`.
|
|
217
|
+
*
|
|
218
|
+
* No-op when HITL is off (the default — omitted, or
|
|
219
|
+
* `{ enabled: false }`) or the host already supplied a checkpointer
|
|
220
|
+
* of their own. See `HumanInTheLoopConfig` JSDoc for the rationale
|
|
221
|
+
* behind the default-off stance.
|
|
222
|
+
*/
|
|
223
|
+
private applyHITLCheckpointerFallback(
|
|
224
|
+
compileOptions: t.CompileOptions | undefined
|
|
225
|
+
): t.CompileOptions | undefined {
|
|
226
|
+
if (this.humanInTheLoop?.enabled !== true) {
|
|
227
|
+
return compileOptions;
|
|
228
|
+
}
|
|
229
|
+
if (compileOptions?.checkpointer != null) {
|
|
230
|
+
return compileOptions;
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
...(compileOptions ?? {}),
|
|
234
|
+
checkpointer: new MemorySaver(),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Run RunStart + UserPromptSubmit hooks before the graph stream
|
|
240
|
+
* begins, accumulate any `additionalContext` strings into the input
|
|
241
|
+
* messages, and short-circuit when a hook signals the run should not
|
|
242
|
+
* proceed (deny / ask decision on the prompt, or `preventContinuation`
|
|
243
|
+
* on either hook).
|
|
244
|
+
*
|
|
245
|
+
* Returns `true` when the caller should bail with `undefined` (run
|
|
246
|
+
* was halted before any model call); returns `false` to proceed
|
|
247
|
+
* into the stream loop.
|
|
248
|
+
*
|
|
249
|
+
* ## Side effects
|
|
250
|
+
*
|
|
251
|
+
* On the success path:
|
|
252
|
+
* - Mutates `stateInputs.messages` in place to append a
|
|
253
|
+
* consolidated `HumanMessage` carrying any hook
|
|
254
|
+
* `additionalContext` strings. Safe because the host owns the
|
|
255
|
+
* array and `processStream` is the only consumer until LangGraph
|
|
256
|
+
* reads it.
|
|
257
|
+
*
|
|
258
|
+
* On the halt path (returning `true`):
|
|
259
|
+
* - Sets `this._haltedReason` so callers (and the eventual host)
|
|
260
|
+
* can distinguish a hook-driven halt from a natural completion.
|
|
261
|
+
* - Calls `registry.clearSession(this.id)` and
|
|
262
|
+
* `registry.clearHaltSignal(this.id)` because no resume is
|
|
263
|
+
* expected from a pre-stream halt — the run never entered the
|
|
264
|
+
* graph, so the session/halt state for this run would otherwise
|
|
265
|
+
* leak to the next `processStream` invocation on the same
|
|
266
|
+
* registry. Other concurrent runs on the same registry are
|
|
267
|
+
* untouched (halt signals are scoped per session id).
|
|
268
|
+
* - Sets `config.callbacks = undefined` to drop the callback
|
|
269
|
+
* references the caller built (langfuse handler, custom event
|
|
270
|
+
* handler, etc.) since they won't be exercised. Mirrors the
|
|
271
|
+
* equivalent cleanup the `processStream` `finally` block does
|
|
272
|
+
* on the natural-completion path.
|
|
273
|
+
*/
|
|
274
|
+
private async runPreStreamHooks(
|
|
275
|
+
stateInputs: t.IState,
|
|
276
|
+
threadId: string | undefined,
|
|
277
|
+
config: Partial<RunnableConfig>
|
|
278
|
+
): Promise<boolean> {
|
|
279
|
+
const registry = this.hookRegistry;
|
|
280
|
+
/**
|
|
281
|
+
* Defensive guard: `processStream` already validated `this.Graph`
|
|
282
|
+
* before calling this helper, but TypeScript can't propagate that
|
|
283
|
+
* narrowing across method boundaries. The check keeps the body
|
|
284
|
+
* free of `this.Graph!` non-null assertions.
|
|
285
|
+
*/
|
|
286
|
+
if (registry == null || this.Graph == null) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const preStreamContexts: string[] = [];
|
|
291
|
+
|
|
292
|
+
const runStartResult = await executeHooks({
|
|
293
|
+
registry,
|
|
294
|
+
input: {
|
|
295
|
+
hook_event_name: 'RunStart',
|
|
296
|
+
runId: this.id,
|
|
297
|
+
threadId,
|
|
298
|
+
agentId: this.Graph.defaultAgentId,
|
|
299
|
+
messages: stateInputs.messages,
|
|
300
|
+
},
|
|
301
|
+
sessionId: this.id,
|
|
302
|
+
});
|
|
303
|
+
for (const ctx of runStartResult.additionalContexts) {
|
|
304
|
+
preStreamContexts.push(ctx);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Honor `preventContinuation` from RunStart before the stream
|
|
308
|
+
* starts. Mid-flight halts (from tool/compact/subagent hooks)
|
|
309
|
+
* route through `HookRegistry.haltRun` and are polled by the
|
|
310
|
+
* stream loop in `processStream` — different mechanism, same
|
|
311
|
+
* intent.
|
|
312
|
+
*/
|
|
313
|
+
if (runStartResult.preventContinuation === true) {
|
|
314
|
+
this._haltedReason = runStartResult.stopReason ?? 'preventContinuation';
|
|
315
|
+
registry.clearSession(this.id);
|
|
316
|
+
registry.clearHaltSignal(this.id);
|
|
317
|
+
config.callbacks = undefined;
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const lastHuman = findLastMessageOfType(stateInputs.messages, 'human');
|
|
322
|
+
if (lastHuman != null) {
|
|
323
|
+
const promptResult = await executeHooks({
|
|
324
|
+
registry,
|
|
325
|
+
input: {
|
|
326
|
+
hook_event_name: 'UserPromptSubmit',
|
|
327
|
+
runId: this.id,
|
|
328
|
+
threadId,
|
|
329
|
+
agentId: this.Graph.defaultAgentId,
|
|
330
|
+
prompt: extractPromptText(lastHuman),
|
|
331
|
+
// attachments: not yet wired — Phase 2 will extract
|
|
332
|
+
// non-text content blocks (images, files) from messages
|
|
333
|
+
},
|
|
334
|
+
sessionId: this.id,
|
|
335
|
+
});
|
|
336
|
+
if (
|
|
337
|
+
promptResult.decision === 'deny' ||
|
|
338
|
+
promptResult.decision === 'ask' ||
|
|
339
|
+
promptResult.preventContinuation === true
|
|
340
|
+
) {
|
|
341
|
+
/**
|
|
342
|
+
* Always set `_haltedReason` so the host can call
|
|
343
|
+
* `getHaltReason()` and distinguish a hook-blocked prompt
|
|
344
|
+
* from a natural empty-output completion. Three signals can
|
|
345
|
+
* land here, each with its own canonical reason string when
|
|
346
|
+
* the hook didn't supply one.
|
|
347
|
+
*/
|
|
348
|
+
if (promptResult.preventContinuation === true) {
|
|
349
|
+
this._haltedReason = promptResult.stopReason ?? 'preventContinuation';
|
|
350
|
+
} else if (promptResult.decision === 'deny') {
|
|
351
|
+
this._haltedReason = promptResult.reason ?? 'prompt_denied';
|
|
352
|
+
} else {
|
|
353
|
+
this._haltedReason =
|
|
354
|
+
promptResult.reason ?? 'prompt_requires_approval';
|
|
355
|
+
}
|
|
356
|
+
registry.clearSession(this.id);
|
|
357
|
+
registry.clearHaltSignal(this.id);
|
|
358
|
+
config.callbacks = undefined;
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
for (const ctx of promptResult.additionalContexts) {
|
|
362
|
+
preStreamContexts.push(ctx);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (preStreamContexts.length > 0) {
|
|
367
|
+
/**
|
|
368
|
+
* Wraps the joined hook contexts as a `HumanMessage` even though
|
|
369
|
+
* the intent is system-level guidance. Using a `SystemMessage`
|
|
370
|
+
* mid-conversation is rejected by Anthropic and Google providers
|
|
371
|
+
* (system messages must be the leading entry), so the LangChain
|
|
372
|
+
* convention — also used by `ToolNode.convertInjectedMessages`
|
|
373
|
+
* — is `HumanMessage` carrying `additional_kwargs.role` as a
|
|
374
|
+
* marker for hosts inspecting state. The model still sees a
|
|
375
|
+
* user-role message; the `role: 'system'` field is metadata
|
|
376
|
+
* only. Hosts that want a true system message should compose
|
|
377
|
+
* it into the agent's `instructions` config instead.
|
|
378
|
+
*/
|
|
379
|
+
stateInputs.messages.push(
|
|
380
|
+
new HumanMessage({
|
|
381
|
+
content: preStreamContexts.join('\n\n'),
|
|
382
|
+
additional_kwargs: { role: 'system', source: 'hook' },
|
|
383
|
+
})
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
188
390
|
static async create<T extends t.BaseGraphState>(
|
|
189
391
|
config: t.RunConfig
|
|
190
392
|
): Promise<Run<T>> {
|
|
@@ -268,7 +470,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
268
470
|
}
|
|
269
471
|
|
|
270
472
|
async processStream(
|
|
271
|
-
inputs: t.IState,
|
|
473
|
+
inputs: t.IState | Command,
|
|
272
474
|
callerConfig: Partial<RunnableConfig> & {
|
|
273
475
|
version: 'v1' | 'v2';
|
|
274
476
|
run_id?: string;
|
|
@@ -286,6 +488,16 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
286
488
|
);
|
|
287
489
|
}
|
|
288
490
|
|
|
491
|
+
/**
|
|
492
|
+
* `Command` inputs (currently only `Command({ resume })`) are
|
|
493
|
+
* resume-mode invocations: LangGraph rebuilds graph state from the
|
|
494
|
+
* checkpointer, so we skip RunStart / UserPromptSubmit hooks (no
|
|
495
|
+
* new prompt to evaluate) and read run-state from the Graph wrapper
|
|
496
|
+
* instead of `inputs.messages`.
|
|
497
|
+
*/
|
|
498
|
+
const isResume = inputs instanceof Command;
|
|
499
|
+
const stateInputs = isResume ? undefined : (inputs as t.IState);
|
|
500
|
+
|
|
289
501
|
const config: Partial<RunnableConfig> & {
|
|
290
502
|
version: 'v1' | 'v2';
|
|
291
503
|
run_id?: string;
|
|
@@ -295,7 +507,23 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
295
507
|
configurable: { ...callerConfig.configurable },
|
|
296
508
|
};
|
|
297
509
|
|
|
298
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Skip `resetValues` on resume — we're continuing an in-flight
|
|
512
|
+
* run, not starting a fresh one. Resetting would wipe the
|
|
513
|
+
* sidecars (`toolCallStepIds`, `stepKeyIds`, accumulated
|
|
514
|
+
* `messages`, etc.) the resumed `ToolNode` needs to dispatch
|
|
515
|
+
* tool completions with the correct step ids and re-resolve
|
|
516
|
+
* `{{tool<i>turn<n>}}` references. Pairs with the
|
|
517
|
+
* `awaitingResume` gate on `clearHeavyState` in the `finally`
|
|
518
|
+
* block so the sidecars survive both ends of the interrupt
|
|
519
|
+
* boundary.
|
|
520
|
+
*/
|
|
521
|
+
if (!isResume) {
|
|
522
|
+
this.Graph.resetValues(streamOptions?.keepContent);
|
|
523
|
+
}
|
|
524
|
+
this._interrupt = undefined;
|
|
525
|
+
this._haltedReason = undefined;
|
|
526
|
+
this.hookRegistry?.clearHaltSignal(this.id);
|
|
299
527
|
|
|
300
528
|
/** Custom event callback to intercept and handle custom events */
|
|
301
529
|
const customEventCallback = this.createCustomEventCallback();
|
|
@@ -350,46 +578,24 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
350
578
|
|
|
351
579
|
const threadId = config.configurable.thread_id as string | undefined;
|
|
352
580
|
|
|
353
|
-
if (this.hookRegistry != null) {
|
|
354
|
-
await
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
messages: inputs.messages,
|
|
362
|
-
},
|
|
363
|
-
sessionId: this.id,
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
const lastHuman = findLastMessageOfType(inputs.messages, 'human');
|
|
367
|
-
if (lastHuman != null) {
|
|
368
|
-
const promptResult = await executeHooks({
|
|
369
|
-
registry: this.hookRegistry,
|
|
370
|
-
input: {
|
|
371
|
-
hook_event_name: 'UserPromptSubmit',
|
|
372
|
-
runId: this.id,
|
|
373
|
-
threadId,
|
|
374
|
-
agentId: this.Graph.defaultAgentId,
|
|
375
|
-
prompt: extractPromptText(lastHuman),
|
|
376
|
-
// attachments: not yet wired — Phase 2 will extract
|
|
377
|
-
// non-text content blocks (images, files) from messages
|
|
378
|
-
},
|
|
379
|
-
sessionId: this.id,
|
|
380
|
-
});
|
|
381
|
-
if (
|
|
382
|
-
promptResult.decision === 'deny' ||
|
|
383
|
-
promptResult.decision === 'ask'
|
|
384
|
-
) {
|
|
385
|
-
this.hookRegistry.clearSession(this.id);
|
|
386
|
-
config.callbacks = undefined;
|
|
387
|
-
return undefined;
|
|
388
|
-
}
|
|
581
|
+
if (this.hookRegistry != null && stateInputs != null) {
|
|
582
|
+
const shouldHalt = await this.runPreStreamHooks(
|
|
583
|
+
stateInputs,
|
|
584
|
+
threadId,
|
|
585
|
+
config
|
|
586
|
+
);
|
|
587
|
+
if (shouldHalt) {
|
|
588
|
+
return undefined;
|
|
389
589
|
}
|
|
390
590
|
}
|
|
391
591
|
|
|
392
|
-
|
|
592
|
+
/**
|
|
593
|
+
* `streamEvents` accepts both state inputs and `Command` (resume) at
|
|
594
|
+
* runtime, but our `CompiledStateWorkflow` type narrows the first
|
|
595
|
+
* arg to `BaseGraphState`. Cast on the call so the resume path
|
|
596
|
+
* type-checks without widening the wrapper for every caller.
|
|
597
|
+
*/
|
|
598
|
+
const stream = this.graphRunnable.streamEvents(inputs as t.IState, config, {
|
|
393
599
|
raiseError: true,
|
|
394
600
|
/**
|
|
395
601
|
* Prevent EventStreamCallbackHandler from processing custom events.
|
|
@@ -402,6 +608,17 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
402
608
|
ignoreCustomEvent: true,
|
|
403
609
|
});
|
|
404
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Tracks whether the stream loop threw. Used by the `finally`
|
|
613
|
+
* block to decide whether to honor the interrupt-preservation
|
|
614
|
+
* guard for session hooks: a captured `_interrupt` is only
|
|
615
|
+
* meaningful if the stream completed cleanly. If the loop errored
|
|
616
|
+
* after stashing an interrupt (e.g. a downstream handler throws
|
|
617
|
+
* after the interrupt event landed), the interrupt is stale —
|
|
618
|
+
* preserving session hooks would leak them into the next run.
|
|
619
|
+
*/
|
|
620
|
+
let streamThrew = false;
|
|
621
|
+
|
|
405
622
|
try {
|
|
406
623
|
for await (const event of stream) {
|
|
407
624
|
const { data, metadata, ...info } = event;
|
|
@@ -413,13 +630,81 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
413
630
|
continue;
|
|
414
631
|
}
|
|
415
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Detect interrupts surfaced by LangGraph as a synthetic
|
|
635
|
+
* `__interrupt__` field on the streamed chunk and stash the
|
|
636
|
+
* first one for the host to read via `run.getInterrupt()`
|
|
637
|
+
* once the stream drains. Captured as `unknown` because the
|
|
638
|
+
* SDK does not validate the runtime payload shape — the
|
|
639
|
+
* built-in ToolNode raises a `HumanInterruptPayload`
|
|
640
|
+
* (`tool_approval` / `ask_user_question`), but custom nodes
|
|
641
|
+
* can pass any payload to `interrupt()`. Callers narrow with
|
|
642
|
+
* the `isToolApprovalInterrupt` / `isAskUserQuestionInterrupt`
|
|
643
|
+
* guards or assert via `getInterrupt<T>()`.
|
|
644
|
+
*/
|
|
645
|
+
if (
|
|
646
|
+
this._interrupt == null &&
|
|
647
|
+
data.chunk != null &&
|
|
648
|
+
isInterrupted<unknown>(data.chunk)
|
|
649
|
+
) {
|
|
650
|
+
const interrupts = data.chunk[INTERRUPT];
|
|
651
|
+
if (interrupts.length > 0) {
|
|
652
|
+
const first = interrupts[0];
|
|
653
|
+
/**
|
|
654
|
+
* Capture the interrupt unconditionally — `interrupt(null)`
|
|
655
|
+
* and `interrupt(undefined)` are valid pauses (a custom
|
|
656
|
+
* node may want to pause without metadata) and the host
|
|
657
|
+
* still needs to know the run is awaiting resume. Gating
|
|
658
|
+
* on `payload != null` would silently downgrade a paused
|
|
659
|
+
* run to "completed" and let the `Stop` hook fire,
|
|
660
|
+
* breaking host resume handling.
|
|
661
|
+
*/
|
|
662
|
+
this._interrupt = {
|
|
663
|
+
interruptId: first.id ?? '',
|
|
664
|
+
threadId,
|
|
665
|
+
payload: first.value,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
416
670
|
const handler = this.handlerRegistry?.getHandler(eventName);
|
|
417
671
|
if (handler) {
|
|
418
672
|
await handler.handle(eventName, data, metadata, this.Graph);
|
|
419
673
|
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Mid-flight halt: any hook (PreToolUse, PostToolUse,
|
|
677
|
+
* PostToolBatch, SubagentStart/Stop, PreCompact, PostCompact)
|
|
678
|
+
* that returned `preventContinuation: true` raises a halt
|
|
679
|
+
* signal on the registry via `executeHooks`. We poll between
|
|
680
|
+
* stream events and break out as soon as one is set so the
|
|
681
|
+
* graph doesn't take another model turn after the halting
|
|
682
|
+
* operation completes.
|
|
683
|
+
*
|
|
684
|
+
* Limitation: the current step (in-flight model call, ongoing
|
|
685
|
+
* tool batch) is not aborted — only the next step is skipped.
|
|
686
|
+
* This matches Claude Code's `continue: false` semantic where
|
|
687
|
+
* the active operation finishes before halting takes effect.
|
|
688
|
+
*/
|
|
689
|
+
const haltSignal = this.hookRegistry?.getHaltSignal(this.id);
|
|
690
|
+
if (haltSignal != null) {
|
|
691
|
+
this._haltedReason = haltSignal.reason;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
420
694
|
}
|
|
421
695
|
|
|
422
|
-
|
|
696
|
+
/**
|
|
697
|
+
* Skip the Stop hook when the run paused on a HITL interrupt
|
|
698
|
+
* (still pending human input) or was halted by a hook (the host
|
|
699
|
+
* already chose to stop, so a Stop hook firing now would be
|
|
700
|
+
* misleading). The host fires Stop on the resumed-and-completed
|
|
701
|
+
* run instead.
|
|
702
|
+
*/
|
|
703
|
+
if (
|
|
704
|
+
this._interrupt == null &&
|
|
705
|
+
this._haltedReason == null &&
|
|
706
|
+
this.hookRegistry?.hasHookFor('Stop', this.id) === true
|
|
707
|
+
) {
|
|
423
708
|
await executeHooks({
|
|
424
709
|
registry: this.hookRegistry,
|
|
425
710
|
input: {
|
|
@@ -427,7 +712,8 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
427
712
|
runId: this.id,
|
|
428
713
|
threadId,
|
|
429
714
|
agentId: this.Graph.defaultAgentId,
|
|
430
|
-
messages:
|
|
715
|
+
messages:
|
|
716
|
+
this.Graph.getRunMessages() ?? stateInputs?.messages ?? [],
|
|
431
717
|
stopHookActive: false, // will be true when stop is triggered by a hook (Phase 2)
|
|
432
718
|
},
|
|
433
719
|
sessionId: this.id,
|
|
@@ -436,6 +722,7 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
436
722
|
});
|
|
437
723
|
}
|
|
438
724
|
} catch (err) {
|
|
725
|
+
streamThrew = true;
|
|
439
726
|
if (this.hookRegistry?.hasHookFor('StopFailure', this.id) === true) {
|
|
440
727
|
const runMessages = this.Graph.getRunMessages() ?? [];
|
|
441
728
|
await executeHooks({
|
|
@@ -455,7 +742,36 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
455
742
|
}
|
|
456
743
|
throw err;
|
|
457
744
|
} finally {
|
|
458
|
-
|
|
745
|
+
/**
|
|
746
|
+
* Preserve session-scoped hooks when the run paused on a HITL
|
|
747
|
+
* interrupt — the very next call will be `Run.resume()`, which
|
|
748
|
+
* needs the same policy hooks (e.g., the `PreToolUse` matcher
|
|
749
|
+
* that triggered the interrupt) to fire on the re-executed node
|
|
750
|
+
* and uphold the approval flow. Clearing here would leak the
|
|
751
|
+
* approval gate on resume. The session is cleared instead at
|
|
752
|
+
* natural completion, error (including errors that happen AFTER
|
|
753
|
+
* an interrupt was captured — those interrupts are stale), or
|
|
754
|
+
* hook-driven halt (including hooks that returned BOTH `ask`
|
|
755
|
+
* and `preventContinuation` — the halt wins, no resume is
|
|
756
|
+
* expected, sessions must drop). Every state where no resume
|
|
757
|
+
* is expected clears.
|
|
758
|
+
*/
|
|
759
|
+
if (
|
|
760
|
+
this._interrupt == null ||
|
|
761
|
+
this._haltedReason != null ||
|
|
762
|
+
streamThrew
|
|
763
|
+
) {
|
|
764
|
+
this.hookRegistry?.clearSession(this.id);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Drop any halt signal raised mid-stream for this run so a
|
|
768
|
+
* subsequent `processStream` / `resume` starts with clean state.
|
|
769
|
+
* The Run captured `_haltedReason` already; the registry entry
|
|
770
|
+
* for this `sessionId` would otherwise spuriously trip the next
|
|
771
|
+
* loop. Other concurrent runs sharing this registry are
|
|
772
|
+
* unaffected — their entries live under their own session ids.
|
|
773
|
+
*/
|
|
774
|
+
this.hookRegistry?.clearHaltSignal(this.id);
|
|
459
775
|
|
|
460
776
|
/**
|
|
461
777
|
* Break the reference chain that keeps heavy data alive via
|
|
@@ -494,7 +810,28 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
494
810
|
|
|
495
811
|
this.calibrationRatio = this.Graph.getCalibrationRatio();
|
|
496
812
|
|
|
497
|
-
|
|
813
|
+
/**
|
|
814
|
+
* Skip `clearHeavyState()` when the run paused on a clean HITL
|
|
815
|
+
* interrupt awaiting resume — `Run.resume()` re-enters the same
|
|
816
|
+
* `ToolNode` instance and needs the sidecars `clearHeavyState`
|
|
817
|
+
* would wipe (`toolCallStepIds` for completion-event step ids,
|
|
818
|
+
* the `_toolOutputRegistry` for `{{tool<i>turn<n>}}`
|
|
819
|
+
* substitutions, `sessions` for code-env continuity, plus the
|
|
820
|
+
* `hookRegistry` and `humanInTheLoop` config the interrupt
|
|
821
|
+
* branch itself relies on). Without preservation, the resumed
|
|
822
|
+
* tool completion would dispatch `ON_RUN_STEP_COMPLETED` with
|
|
823
|
+
* an empty step id and downstream stream consumers would drop
|
|
824
|
+
* the result.
|
|
825
|
+
*
|
|
826
|
+
* The natural-completion / error / hook-driven-halt paths still
|
|
827
|
+
* clean up — `_haltedReason != null` or `streamThrew` mean no
|
|
828
|
+
* resume is expected. Cross-process resume (host rebuilds the
|
|
829
|
+
* Run from scratch) is a separate concern; see
|
|
830
|
+
* `HumanInTheLoopConfig` JSDoc.
|
|
831
|
+
*/
|
|
832
|
+
const awaitingResume =
|
|
833
|
+
this._interrupt != null && this._haltedReason == null && !streamThrew;
|
|
834
|
+
if (!this.skipCleanup && !awaitingResume) {
|
|
498
835
|
this.Graph.clearHeavyState();
|
|
499
836
|
}
|
|
500
837
|
|
|
@@ -504,6 +841,78 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
504
841
|
return this._streamResult;
|
|
505
842
|
}
|
|
506
843
|
|
|
844
|
+
/**
|
|
845
|
+
* Returns the pending interrupt captured during the most recent
|
|
846
|
+
* `processStream` (or `resume`) invocation. `undefined` when the run
|
|
847
|
+
* either has not been streamed yet or completed without pausing.
|
|
848
|
+
*
|
|
849
|
+
* Hosts call this immediately after `processStream` returns to decide
|
|
850
|
+
* whether the run is awaiting human input. Persist the returned
|
|
851
|
+
* descriptor (alongside `thread_id` and the agent run config) so a
|
|
852
|
+
* later `resume(decisions)` can rebuild the run.
|
|
853
|
+
*
|
|
854
|
+
* The default `TPayload` is the SDK's `HumanInterruptPayload` union
|
|
855
|
+
* (`tool_approval` / `ask_user_question`), suitable for the common
|
|
856
|
+
* case where interrupts come from the built-in ToolNode or
|
|
857
|
+
* `askUserQuestion()` helper. Hosts that raise custom interrupts
|
|
858
|
+
* from custom graph nodes pass their own type — the SDK does not
|
|
859
|
+
* validate the runtime shape, it just transports whatever the
|
|
860
|
+
* `interrupt()` call carried. When in doubt, narrow with the
|
|
861
|
+
* `isToolApprovalInterrupt` / `isAskUserQuestionInterrupt` type
|
|
862
|
+
* guards (which accept `unknown`) before reading variant-specific
|
|
863
|
+
* fields.
|
|
864
|
+
*/
|
|
865
|
+
getInterrupt<TPayload = t.HumanInterruptPayload>():
|
|
866
|
+
| t.RunInterruptResult<TPayload>
|
|
867
|
+
| undefined {
|
|
868
|
+
return this._interrupt as t.RunInterruptResult<TPayload> | undefined;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Returns the reason a hook halted the run via
|
|
873
|
+
* `preventContinuation: true`, or `undefined` if no hook halted.
|
|
874
|
+
*
|
|
875
|
+
* Hosts inspect this after `processStream` returns to distinguish a
|
|
876
|
+
* natural completion (`undefined`) from a hook-driven halt (a
|
|
877
|
+
* truthy string). Independent from `getInterrupt()` — a halted run
|
|
878
|
+
* has no interrupt; an interrupted run has no halt reason.
|
|
879
|
+
*/
|
|
880
|
+
getHaltReason(): string | undefined {
|
|
881
|
+
return this._haltedReason;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Resume a paused HITL run with the value the user (or whatever
|
|
886
|
+
* decided the interrupt) supplied. The default `TResume` covers the
|
|
887
|
+
* `tool_approval` interrupt (the common case): an array of decisions
|
|
888
|
+
* in `action_requests` order, or a record keyed by `tool_call_id`.
|
|
889
|
+
*
|
|
890
|
+
* For other interrupt types (e.g., `ask_user_question` →
|
|
891
|
+
* `AskUserQuestionResolution`, or any custom interrupt a host raises
|
|
892
|
+
* from a custom node), pass the type parameter and the SDK forwards
|
|
893
|
+
* the value through unchanged. LangGraph delivers it as the return
|
|
894
|
+
* value of the original `interrupt()` call inside the paused node.
|
|
895
|
+
*
|
|
896
|
+
* The host MUST construct this Run with the same `thread_id` and the
|
|
897
|
+
* same checkpointer as the original paused run; LangGraph rebuilds
|
|
898
|
+
* graph state from the checkpoint and re-enters the interrupted node
|
|
899
|
+
* from the start.
|
|
900
|
+
*/
|
|
901
|
+
async resume<TResume = t.ToolApprovalDecision[] | t.ToolApprovalDecisionMap>(
|
|
902
|
+
resumeValue: TResume,
|
|
903
|
+
callerConfig: Partial<RunnableConfig> & {
|
|
904
|
+
version: 'v1' | 'v2';
|
|
905
|
+
run_id?: string;
|
|
906
|
+
},
|
|
907
|
+
streamOptions?: t.EventStreamOptions
|
|
908
|
+
): Promise<MessageContentComplex[] | undefined> {
|
|
909
|
+
return this.processStream(
|
|
910
|
+
new Command({ resume: resumeValue }),
|
|
911
|
+
callerConfig,
|
|
912
|
+
streamOptions
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
507
916
|
private createSystemCallback<K extends keyof t.ClientCallbacks>(
|
|
508
917
|
clientCallbacks: t.ClientCallbacks,
|
|
509
918
|
key: K
|
package/src/scripts/caching.ts
CHANGED
|
@@ -50,9 +50,8 @@ ${CACHED_TEXT}`;
|
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
const baseLlmConfig
|
|
54
|
-
|
|
55
|
-
);
|
|
53
|
+
const baseLlmConfig = getLLMConfig(Providers.ANTHROPIC) as t.LLMConfig &
|
|
54
|
+
t.AnthropicClientOptions;
|
|
56
55
|
|
|
57
56
|
if (baseLlmConfig.provider !== 'anthropic') {
|
|
58
57
|
console.error(
|