@librechat/agents 3.2.58 → 3.2.60
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 +31 -7
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +7 -0
- package/dist/cjs/run.cjs +4 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +2 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +58 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +4 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +57 -7
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +9 -3
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +114 -11
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +18 -1
- package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +31 -7
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/run.mjs +4 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +2 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +56 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +4 -2
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +54 -8
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +9 -3
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +115 -12
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +18 -2
- package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +21 -3
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/BashExecutor.d.ts +13 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -0
- package/dist/types/tools/ToolNode.d.ts +60 -1
- package/dist/types/tools/eagerEventExecution.d.ts +8 -0
- package/dist/types/types/hitl.d.ts +49 -3
- package/dist/types/types/run.d.ts +21 -0
- package/dist/types/types/tools.d.ts +95 -1
- package/package.json +1 -1
- package/src/graphs/Graph.ts +74 -28
- package/src/run.ts +4 -0
- package/src/specs/ask-user-question-batch.test.ts +289 -0
- package/src/specs/tool-error-resume.test.ts +194 -0
- package/src/stream.ts +17 -1
- package/src/tools/BashExecutor.ts +107 -14
- package/src/tools/BashProgrammaticToolCalling.ts +20 -1
- package/src/tools/CodeExecutor.ts +113 -9
- package/src/tools/ProgrammaticToolCalling.ts +27 -1
- package/src/tools/ToolNode.ts +208 -31
- package/src/tools/__tests__/BashExecutor.test.ts +39 -0
- package/src/tools/__tests__/CodeExecutor.stateful.test.ts +113 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +86 -0
- package/src/tools/__tests__/eagerEventExecution.session.test.ts +92 -0
- package/src/tools/__tests__/hitl.test.ts +48 -0
- package/src/tools/eagerEventExecution.ts +32 -5
- package/src/types/hitl.ts +49 -3
- package/src/types/run.ts +21 -0
- package/src/types/tools.ts +102 -1
|
@@ -4,7 +4,7 @@ import "../common/index.mjs";
|
|
|
4
4
|
import { toLangChainContent } from "../messages/langchain.mjs";
|
|
5
5
|
import { calculateMaxToolResultChars, truncateToolResultContent } from "../utils/truncation.mjs";
|
|
6
6
|
import { safeDispatchCustomEvent } from "../utils/events.mjs";
|
|
7
|
-
import { buildToolExecutionRequestPlan, recordArgsEqual } from "./eagerEventExecution.mjs";
|
|
7
|
+
import { buildToolExecutionRequestPlan, recordArgsEqual, resolveRuntimeSessionHint } from "./eagerEventExecution.mjs";
|
|
8
8
|
import { ToolOutputReferenceRegistry, buildReferenceKey } from "./toolOutputReferences.mjs";
|
|
9
9
|
import { RunnableCallable } from "../utils/run.mjs";
|
|
10
10
|
import "../utils/index.mjs";
|
|
@@ -207,6 +207,13 @@ var ToolNode = class extends RunnableCallable {
|
|
|
207
207
|
agentLangfuse;
|
|
208
208
|
toolCallStepIds;
|
|
209
209
|
errorHandler;
|
|
210
|
+
/**
|
|
211
|
+
* Tool call ids whose `errorHandler` did NOT dispatch the error completion
|
|
212
|
+
* event (it returned `false` or threw). The output loop must dispatch the
|
|
213
|
+
* completion for these itself — skipping them there would strand the
|
|
214
|
+
* client's tool-call part without a terminal event.
|
|
215
|
+
*/
|
|
216
|
+
undispatchedToolErrors = /* @__PURE__ */ new Set();
|
|
210
217
|
toolUsageCount;
|
|
211
218
|
/** Maps toolCallId → turn captured in runTool, used by handleRunToolCompletions */
|
|
212
219
|
toolCallTurns = /* @__PURE__ */ new Map();
|
|
@@ -249,6 +256,21 @@ var ToolNode = class extends RunnableCallable {
|
|
|
249
256
|
/** Tool names that bypass event dispatch and execute directly (e.g., graph-managed handoff tools) */
|
|
250
257
|
directToolNames;
|
|
251
258
|
/**
|
|
259
|
+
* Tool names whose in-process body may raise a LangGraph `interrupt()`
|
|
260
|
+
* mid-execution (e.g. `ask_user_question`). Used only to REORDER within
|
|
261
|
+
* the direct group: a tool named here that is *already* direct (a real
|
|
262
|
+
* in-process graphTool — the only kind whose body can reach
|
|
263
|
+
* `interrupt()`) is scheduled ahead of its non-interrupting direct
|
|
264
|
+
* siblings, so a mid-body interrupt unwinds the ToolNode before a
|
|
265
|
+
* non-idempotent sibling executes and LangGraph's resume-time batch
|
|
266
|
+
* re-execution can't double it. This set is deliberately NOT folded
|
|
267
|
+
* into direct classification: a name that resolves to a schema-only
|
|
268
|
+
* event stub (an inherited `toolDefinition` with no executable
|
|
269
|
+
* instance) stays event-dispatched — forcing it direct would invoke
|
|
270
|
+
* the stub, which throws. See {@link t.ToolNodeOptions.interruptingToolNames}.
|
|
271
|
+
*/
|
|
272
|
+
interruptingToolNames;
|
|
273
|
+
/**
|
|
252
274
|
* File checkpointer extracted from the local coding tool bundle when
|
|
253
275
|
* `toolExecution.local.fileCheckpointing === true`. Exposed via
|
|
254
276
|
* {@link getFileCheckpointer}. Undefined when checkpointing is off
|
|
@@ -287,7 +309,7 @@ var ToolNode = class extends RunnableCallable {
|
|
|
287
309
|
* other's in-flight state.
|
|
288
310
|
*/
|
|
289
311
|
anonBatchCounter = 0;
|
|
290
|
-
constructor({ tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, agentId, executingAgentId, directToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer }) {
|
|
312
|
+
constructor({ tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, agentId, executingAgentId, directToolNames, interruptingToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer }) {
|
|
291
313
|
super({
|
|
292
314
|
name: name ?? TOOL_NODE_RUN_NAME,
|
|
293
315
|
tags,
|
|
@@ -314,6 +336,7 @@ var ToolNode = class extends RunnableCallable {
|
|
|
314
336
|
this.agentId = agentId;
|
|
315
337
|
this.executingAgentId = executingAgentId ?? agentId;
|
|
316
338
|
this.directToolNames = directToolNames;
|
|
339
|
+
this.interruptingToolNames = interruptingToolNames != null && interruptingToolNames.size > 0 ? interruptingToolNames : void 0;
|
|
317
340
|
this.codeSessionToolNames = codeSessionToolNames != null && codeSessionToolNames.length > 0 ? new Set(codeSessionToolNames) : void 0;
|
|
318
341
|
this.maxToolResultChars = maxToolResultChars ?? calculateMaxToolResultChars(maxContextTokens);
|
|
319
342
|
this.hookRegistry = hookRegistry;
|
|
@@ -580,6 +603,17 @@ var ToolNode = class extends RunnableCallable {
|
|
|
580
603
|
};
|
|
581
604
|
if (codeSession?.files != null && codeSession.files.length > 0) invokeParams._injected_files = codeSession.files.map((file) => toInjectedFileRef(file, execSessionId));
|
|
582
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Stateful runtime session hint — orthogonal to the transient
|
|
608
|
+
* exec-session above, and injected independently (a first call has a
|
|
609
|
+
* hint but no exec session yet). Explicit host hint wins; otherwise
|
|
610
|
+
* fall back to the conversation's thread_id.
|
|
611
|
+
*/
|
|
612
|
+
const runtimeSessionHint = this.resolveRuntimeSessionHint(config);
|
|
613
|
+
if (runtimeSessionHint != null) invokeParams = {
|
|
614
|
+
...invokeParams,
|
|
615
|
+
_runtime_session_hint: runtimeSessionHint
|
|
616
|
+
};
|
|
583
617
|
}
|
|
584
618
|
/**
|
|
585
619
|
* Forward the graph state as langgraph 1.4's `runtime.state` so
|
|
@@ -659,12 +693,20 @@ var ToolNode = class extends RunnableCallable {
|
|
|
659
693
|
if (!this.handleToolErrors) throw e;
|
|
660
694
|
if (isGraphInterrupt(e)) throw e;
|
|
661
695
|
if (this.errorHandler) try {
|
|
662
|
-
await this.errorHandler({
|
|
696
|
+
if (await this.errorHandler({
|
|
663
697
|
error: e,
|
|
664
698
|
id: call.id,
|
|
665
699
|
name: call.name,
|
|
666
700
|
input: call.args
|
|
667
|
-
}, config.metadata)
|
|
701
|
+
}, config.metadata) === false && call.id != null && call.id !== "")
|
|
702
|
+
/**
|
|
703
|
+
* The handler could not dispatch the error completion (typically
|
|
704
|
+
* a resume pass, where a fast-failing tool errors before the
|
|
705
|
+
* step replay registers its run step). Remember the call so the
|
|
706
|
+
* output loop dispatches the completion itself instead of
|
|
707
|
+
* assuming the handler covered it.
|
|
708
|
+
*/
|
|
709
|
+
this.undispatchedToolErrors.add(call.id);
|
|
668
710
|
} catch (handlerError) {
|
|
669
711
|
console.error("Error in errorHandler:", {
|
|
670
712
|
toolName: call.name,
|
|
@@ -1060,6 +1102,11 @@ var ToolNode = class extends RunnableCallable {
|
|
|
1060
1102
|
if (name === "") return false;
|
|
1061
1103
|
return CODE_EXECUTION_TOOLS.has(name) || this.codeSessionToolNames?.has(name) === true;
|
|
1062
1104
|
}
|
|
1105
|
+
/** Delegates to the shared resolver so the direct and event-driven planning
|
|
1106
|
+
* paths derive the runtime session hint identically. */
|
|
1107
|
+
resolveRuntimeSessionHint(config) {
|
|
1108
|
+
return resolveRuntimeSessionHint(this.toolExecution, config.configurable?.thread_id);
|
|
1109
|
+
}
|
|
1063
1110
|
storeCodeSessionFromResults(results, requestMap) {
|
|
1064
1111
|
if (!this.sessions) return;
|
|
1065
1112
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -1094,7 +1141,8 @@ var ToolNode = class extends RunnableCallable {
|
|
|
1094
1141
|
if (isCommand(output)) continue;
|
|
1095
1142
|
const toolMessage = output;
|
|
1096
1143
|
const toolCallId = call.id ?? "";
|
|
1097
|
-
if (toolMessage.status === "error" && this.errorHandler != null)
|
|
1144
|
+
if (toolMessage.status === "error" && this.errorHandler != null) if (this.undispatchedToolErrors.has(toolCallId)) this.undispatchedToolErrors.delete(toolCallId);
|
|
1145
|
+
else continue;
|
|
1098
1146
|
if (this.sessions && this.participatesInCodeSession(call.name)) {
|
|
1099
1147
|
const artifact = toolMessage.artifact;
|
|
1100
1148
|
const execSessionId = artifact?.session_id;
|
|
@@ -1546,12 +1594,14 @@ var ToolNode = class extends RunnableCallable {
|
|
|
1546
1594
|
const plan = buildToolExecutionRequestPlan({
|
|
1547
1595
|
toolCalls: approvedEntries.map((entry) => {
|
|
1548
1596
|
const codeSessionContext = this.participatesInCodeSession(entry.call.name) || entry.call.name === "skill" || entry.call.name === "read_file" ? this.getCodeSessionContext() : void 0;
|
|
1597
|
+
const runtimeSessionHint = this.participatesInCodeSession(entry.call.name) ? this.resolveRuntimeSessionHint(config) : void 0;
|
|
1549
1598
|
return {
|
|
1550
1599
|
id: entry.call.id,
|
|
1551
1600
|
name: entry.call.name,
|
|
1552
1601
|
args: entry.args,
|
|
1553
1602
|
stepId: entry.stepId,
|
|
1554
|
-
codeSessionContext
|
|
1603
|
+
codeSessionContext,
|
|
1604
|
+
runtimeSessionHint
|
|
1555
1605
|
};
|
|
1556
1606
|
}),
|
|
1557
1607
|
usageCount: this.toolUsageCount,
|
|
@@ -1921,6 +1971,61 @@ var ToolNode = class extends RunnableCallable {
|
|
|
1921
1971
|
return converted;
|
|
1922
1972
|
}
|
|
1923
1973
|
/**
|
|
1974
|
+
* Execute a group of direct (in-process) tool calls with interrupt-safe
|
|
1975
|
+
* ordering, returning outputs aligned 1:1 with `directCalls`.
|
|
1976
|
+
*
|
|
1977
|
+
* Fast path (the common case): when no call in the group is in
|
|
1978
|
+
* `interruptingToolNames`, this is a single `Promise.all` — byte-for-byte
|
|
1979
|
+
* the prior behavior, so ordinary batches are unaffected.
|
|
1980
|
+
*
|
|
1981
|
+
* Interrupt-safe path: when the group contains an interrupting tool (e.g.
|
|
1982
|
+
* `ask_user_question`, whose body raises a LangGraph `interrupt()` to
|
|
1983
|
+
* collect a human answer), the interrupting calls run as their own awaited
|
|
1984
|
+
* group **first**; only after they all settle without interrupting do the
|
|
1985
|
+
* remaining (potentially non-idempotent) siblings run. If an interrupting
|
|
1986
|
+
* call throws a `GraphInterrupt`, the `await` below rejects and unwinds the
|
|
1987
|
+
* whole ToolNode *before* any non-interrupting sibling has started — so a
|
|
1988
|
+
* sibling with real side effects (send_email, billing) never executes on
|
|
1989
|
+
* the first pass. On the resume pass LangGraph re-runs the batch from the
|
|
1990
|
+
* top; the interrupting tool resolves with the host's answer instead of
|
|
1991
|
+
* throwing, and the siblings execute for the FIRST time, exactly once.
|
|
1992
|
+
*
|
|
1993
|
+
* Without this ordering, a flat `Promise.all` starts every sibling
|
|
1994
|
+
* concurrently, so a non-idempotent sibling can complete its side effect
|
|
1995
|
+
* before the interrupt unwinds and then run a SECOND time on resume — the
|
|
1996
|
+
* duplicate side effect this method exists to prevent. Interrupting tools
|
|
1997
|
+
* are expected to be side-effect-free (they only suspend), so running them
|
|
1998
|
+
* as a group and re-running them on resume is harmless.
|
|
1999
|
+
*
|
|
2000
|
+
* `batchIndices[i]` is `directCalls[i]`'s position within the parent
|
|
2001
|
+
* ToolNode batch (used for `{{tool<i>turn<n>}}` registration); it is
|
|
2002
|
+
* preserved regardless of execution order. `baseContext` carries the
|
|
2003
|
+
* batch-scoped fields every call shares; `batchIndex` is filled in
|
|
2004
|
+
* per-call here.
|
|
2005
|
+
*/
|
|
2006
|
+
async runDirectBatchInterruptSafe(directCalls, batchIndices, config, baseContext) {
|
|
2007
|
+
const runOne = (call, position) => this.runDirectToolWithLifecycleHooks(call, config, {
|
|
2008
|
+
...baseContext,
|
|
2009
|
+
batchIndex: batchIndices[position]
|
|
2010
|
+
});
|
|
2011
|
+
const interrupting = this.interruptingToolNames;
|
|
2012
|
+
if (!(interrupting != null && directCalls.some((call) => interrupting.has(call.name)))) return Promise.all(directCalls.map((call, i) => runOne(call, i)));
|
|
2013
|
+
const outputs = new Array(directCalls.length);
|
|
2014
|
+
const interruptingPositions = [];
|
|
2015
|
+
const regularPositions = [];
|
|
2016
|
+
for (let i = 0; i < directCalls.length; i++) if (interrupting.has(directCalls[i].name)) interruptingPositions.push(i);
|
|
2017
|
+
else regularPositions.push(i);
|
|
2018
|
+
const interruptingOutputs = await Promise.all(interruptingPositions.map((i) => runOne(directCalls[i], i)));
|
|
2019
|
+
interruptingPositions.forEach((i, k) => {
|
|
2020
|
+
outputs[i] = interruptingOutputs[k];
|
|
2021
|
+
});
|
|
2022
|
+
const regularOutputs = await Promise.all(regularPositions.map((i) => runOne(directCalls[i], i)));
|
|
2023
|
+
regularPositions.forEach((i, k) => {
|
|
2024
|
+
outputs[i] = regularOutputs[k];
|
|
2025
|
+
});
|
|
2026
|
+
return outputs;
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
1924
2029
|
* Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
|
|
1925
2030
|
* Injected messages are placed AFTER ToolMessages to respect provider
|
|
1926
2031
|
* message ordering (AIMessage tool_calls must be immediately followed
|
|
@@ -2057,15 +2162,14 @@ var ToolNode = class extends RunnableCallable {
|
|
|
2057
2162
|
}
|
|
2058
2163
|
}
|
|
2059
2164
|
const directAdditionalContexts = [];
|
|
2060
|
-
const directOutputs = directCalls.length > 0 ? await
|
|
2061
|
-
batchIndex: directIndices[i],
|
|
2165
|
+
const directOutputs = directCalls.length > 0 ? await this.runDirectBatchInterruptSafe(directCalls, directIndices, config, {
|
|
2062
2166
|
turn,
|
|
2063
2167
|
batchScopeId,
|
|
2064
2168
|
resolvedArgsByCallId,
|
|
2065
2169
|
preBatchSnapshot,
|
|
2066
2170
|
additionalContextsSink: directAdditionalContexts,
|
|
2067
2171
|
runInput: input
|
|
2068
|
-
})
|
|
2172
|
+
}) : [];
|
|
2069
2173
|
if (directCalls.length > 0 && directOutputs.length > 0) await this.handleRunToolCompletions(directCalls, directOutputs, config, resolvedArgsByCallId);
|
|
2070
2174
|
const eventResult = eventCalls.length > 0 ? await this.dispatchToolEvents(eventCalls, config, {
|
|
2071
2175
|
batchIndices: eventIndices,
|
|
@@ -2093,15 +2197,14 @@ var ToolNode = class extends RunnableCallable {
|
|
|
2093
2197
|
} else {
|
|
2094
2198
|
const preBatchSnapshot = this.toolOutputRegistry?.snapshot(batchScopeId);
|
|
2095
2199
|
const directAdditionalContexts = [];
|
|
2096
|
-
const toolOutputs = await
|
|
2097
|
-
batchIndex: i,
|
|
2200
|
+
const toolOutputs = await this.runDirectBatchInterruptSafe(filteredCalls, filteredCalls.map((_call, i) => i), config, {
|
|
2098
2201
|
turn,
|
|
2099
2202
|
batchScopeId,
|
|
2100
2203
|
resolvedArgsByCallId,
|
|
2101
2204
|
preBatchSnapshot,
|
|
2102
2205
|
additionalContextsSink: directAdditionalContexts,
|
|
2103
2206
|
runInput: input
|
|
2104
|
-
})
|
|
2207
|
+
});
|
|
2105
2208
|
await this.handleRunToolCompletions(filteredCalls, toolOutputs, config, resolvedArgsByCallId);
|
|
2106
2209
|
outputs = directAdditionalContexts.length > 0 ? [...toolOutputs, new HumanMessage({
|
|
2107
2210
|
content: directAdditionalContexts.join("\n\n"),
|