@librechat/agents 3.5.1 → 3.6.0
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/agents/AgentContext.cjs +5 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +148 -22
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +2 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/run.cjs +29 -11
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +8 -6
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +8 -7
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +5 -4
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +26 -7
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +7 -6
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +33 -10
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +1 -1
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/runStepResume.cjs +51 -0
- package/dist/cjs/tools/runStepResume.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +15 -43
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +6 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/utils/toolSessions.cjs +63 -0
- package/dist/cjs/utils/toolSessions.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +5 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +148 -22
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +2 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/run.mjs +29 -11
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +8 -6
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +9 -8
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +6 -5
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +23 -8
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +8 -7
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +33 -10
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +2 -2
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/runStepResume.mjs +48 -0
- package/dist/esm/tools/runStepResume.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +15 -43
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +6 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/utils/toolSessions.mjs +62 -0
- package/dist/esm/utils/toolSessions.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +4 -1
- package/dist/types/graphs/Graph.d.ts +11 -3
- package/dist/types/tools/CodeExecutor.d.ts +4 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +1 -1
- package/dist/types/tools/ToolNode.d.ts +3 -1
- package/dist/types/tools/runStepResume.d.ts +5 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +25 -0
- package/dist/types/tools/subagent/SubagentReplay.d.ts +2 -1
- package/dist/types/types/graph.d.ts +10 -1
- package/dist/types/types/stream.d.ts +18 -0
- package/dist/types/types/tools.d.ts +36 -16
- package/dist/types/utils/toolSessions.d.ts +18 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +7 -0
- package/src/graphs/Graph.ts +192 -13
- package/src/graphs/MultiAgentGraph.ts +1 -0
- package/src/run.ts +62 -11
- package/src/stream.ts +28 -9
- package/src/tools/BashExecutor.ts +31 -13
- package/src/tools/BashProgrammaticToolCalling.ts +19 -9
- package/src/tools/CodeExecutor.ts +57 -16
- package/src/tools/ProgrammaticToolCalling.ts +26 -12
- package/src/tools/ToolNode.ts +58 -8
- package/src/tools/ToolSearch.ts +2 -2
- package/src/tools/runStepResume.ts +121 -0
- package/src/tools/subagent/SubagentExecutor.ts +67 -65
- package/src/tools/subagent/SubagentReplay.ts +22 -1
- package/src/types/graph.ts +10 -0
- package/src/types/stream.ts +17 -0
- package/src/types/tools.ts +50 -21
- package/src/utils/toolSessions.ts +113 -0
package/src/tools/ToolNode.ts
CHANGED
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
SUBAGENT_PARENT_BATCH_CONFIG_KEY,
|
|
63
63
|
SUBAGENT_REPLAY_CONTROLLER,
|
|
64
64
|
} from '@/tools/subagent/SubagentReplay';
|
|
65
|
+
import { attachRunStepResumeState } from '@/tools/runStepResume';
|
|
65
66
|
import {
|
|
66
67
|
INTENT_ARG,
|
|
67
68
|
readOutcomeFields,
|
|
@@ -595,17 +596,18 @@ function fileIdentityKey(file: {
|
|
|
595
596
|
|
|
596
597
|
function updateCodeSession(
|
|
597
598
|
sessions: t.ToolSessionMap,
|
|
599
|
+
sessionKey: string,
|
|
598
600
|
execSessionId: string,
|
|
599
601
|
files: t.FileRefs | undefined
|
|
600
602
|
): void {
|
|
601
603
|
const newFiles = files ?? [];
|
|
602
|
-
const existingSession = sessions.get(
|
|
604
|
+
const existingSession = sessions.get(sessionKey) as
|
|
603
605
|
| t.CodeSessionContext
|
|
604
606
|
| undefined;
|
|
605
607
|
const existingFiles = existingSession?.files ?? [];
|
|
606
608
|
|
|
607
609
|
if (newFiles.length === 0) {
|
|
608
|
-
sessions.set(
|
|
610
|
+
sessions.set(sessionKey, {
|
|
609
611
|
session_id: execSessionId,
|
|
610
612
|
files: existingFiles,
|
|
611
613
|
lastUpdated: Date.now(),
|
|
@@ -643,7 +645,7 @@ function updateCodeSession(
|
|
|
643
645
|
}
|
|
644
646
|
}
|
|
645
647
|
|
|
646
|
-
sessions.set(
|
|
648
|
+
sessions.set(sessionKey, {
|
|
647
649
|
session_id: execSessionId,
|
|
648
650
|
files: [...filteredExisting, ...filesWithSession],
|
|
649
651
|
lastUpdated: Date.now(),
|
|
@@ -694,6 +696,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
694
696
|
private programmaticCache?: t.ProgrammaticCache;
|
|
695
697
|
/** Reference to Graph's sessions map for automatic session injection */
|
|
696
698
|
private sessions?: t.ToolSessionMap;
|
|
699
|
+
/** Partition within `sessions` owned by this agent's execution profile. */
|
|
700
|
+
private codeSessionKey: string;
|
|
697
701
|
/** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
|
|
698
702
|
private eventDrivenMode: boolean = false;
|
|
699
703
|
/** Opt-in stream-layer prestart config for event-driven tools. */
|
|
@@ -794,6 +798,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
794
798
|
loadRuntimeTools,
|
|
795
799
|
toolRegistry,
|
|
796
800
|
sessions,
|
|
801
|
+
codeSessionKey,
|
|
797
802
|
eventDrivenMode,
|
|
798
803
|
eagerEventToolExecution,
|
|
799
804
|
eagerEventToolExecutions,
|
|
@@ -814,11 +819,45 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
814
819
|
fileCheckpointer,
|
|
815
820
|
getBreakerSignal,
|
|
816
821
|
getRunScope,
|
|
822
|
+
restoreRunStepResumeState,
|
|
823
|
+
createRunStepResumeState,
|
|
817
824
|
}: t.ToolNodeConstructorParams) {
|
|
818
825
|
super({
|
|
819
826
|
name: name ?? TOOL_NODE_RUN_NAME,
|
|
820
827
|
tags,
|
|
821
|
-
func: (input, config) =>
|
|
828
|
+
func: async (input, config) => {
|
|
829
|
+
const state = input as T & Pick<t.BaseGraphState, 'runStepState'>;
|
|
830
|
+
restoreRunStepResumeState?.(state.runStepState, config);
|
|
831
|
+
let result: T;
|
|
832
|
+
try {
|
|
833
|
+
result = await this.run(input, config);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (!isGraphInterrupt(error) || createRunStepResumeState == null) {
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
838
|
+
const resumeState = createRunStepResumeState();
|
|
839
|
+
throw new GraphInterrupt(
|
|
840
|
+
error.interrupts.map((pendingInterrupt) => ({
|
|
841
|
+
...pendingInterrupt,
|
|
842
|
+
value: attachRunStepResumeState(
|
|
843
|
+
pendingInterrupt.value,
|
|
844
|
+
resumeState
|
|
845
|
+
),
|
|
846
|
+
}))
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (createRunStepResumeState == null) {
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
const runStepState = createRunStepResumeState();
|
|
853
|
+
if (Array.isArray(result)) {
|
|
854
|
+
return [...result, { runStepState }] as T;
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
...result,
|
|
858
|
+
runStepState,
|
|
859
|
+
};
|
|
860
|
+
},
|
|
822
861
|
});
|
|
823
862
|
this.trace = trace ?? this.trace;
|
|
824
863
|
this.runLangfuse = runLangfuse;
|
|
@@ -834,6 +873,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
834
873
|
toolExecution,
|
|
835
874
|
});
|
|
836
875
|
this.sessions = sessions;
|
|
876
|
+
this.codeSessionKey = codeSessionKey ?? Constants.EXECUTE_CODE;
|
|
837
877
|
this.eventDrivenMode = eventDrivenMode ?? false;
|
|
838
878
|
this.eagerEventToolExecution = eagerEventToolExecution;
|
|
839
879
|
this.eagerEventToolExecutions = eagerEventToolExecutions;
|
|
@@ -1328,7 +1368,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1328
1368
|
* `/files/<session_id>` fallback was removed from the executors.
|
|
1329
1369
|
*/
|
|
1330
1370
|
if (this.participatesInCodeSession(call.name)) {
|
|
1331
|
-
const codeSession = this.sessions?.get(
|
|
1371
|
+
const codeSession = this.sessions?.get(this.codeSessionKey) as
|
|
1332
1372
|
| t.CodeSessionContext
|
|
1333
1373
|
| undefined;
|
|
1334
1374
|
const execSessionId = codeSession?.session_id;
|
|
@@ -2356,7 +2396,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2356
2396
|
return undefined;
|
|
2357
2397
|
}
|
|
2358
2398
|
|
|
2359
|
-
const codeSession = this.sessions.get(
|
|
2399
|
+
const codeSession = this.sessions.get(this.codeSessionKey) as
|
|
2360
2400
|
| t.CodeSessionContext
|
|
2361
2401
|
| undefined;
|
|
2362
2402
|
if (!codeSession) {
|
|
@@ -2439,7 +2479,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2439
2479
|
continue;
|
|
2440
2480
|
}
|
|
2441
2481
|
|
|
2442
|
-
updateCodeSession(
|
|
2482
|
+
updateCodeSession(
|
|
2483
|
+
this.sessions,
|
|
2484
|
+
this.codeSessionKey,
|
|
2485
|
+
execSessionId,
|
|
2486
|
+
artifact?.files
|
|
2487
|
+
);
|
|
2443
2488
|
}
|
|
2444
2489
|
}
|
|
2445
2490
|
|
|
@@ -2502,7 +2547,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2502
2547
|
| undefined;
|
|
2503
2548
|
const execSessionId = artifact?.session_id;
|
|
2504
2549
|
if (execSessionId != null && execSessionId !== '') {
|
|
2505
|
-
updateCodeSession(
|
|
2550
|
+
updateCodeSession(
|
|
2551
|
+
this.sessions,
|
|
2552
|
+
this.codeSessionKey,
|
|
2553
|
+
execSessionId,
|
|
2554
|
+
artifact?.files
|
|
2555
|
+
);
|
|
2506
2556
|
}
|
|
2507
2557
|
}
|
|
2508
2558
|
|
package/src/tools/ToolSearch.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
|
25
25
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
26
26
|
import type * as t from '@/types';
|
|
27
27
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
28
|
-
import { getCodeBaseURL } from './CodeExecutor';
|
|
28
|
+
import { buildCodeApiEndpoint, getCodeBaseURL } from './CodeExecutor';
|
|
29
29
|
import { Constants } from '@/common';
|
|
30
30
|
|
|
31
31
|
config();
|
|
@@ -914,7 +914,7 @@ function createToolSearch(
|
|
|
914
914
|
const schema = createToolSearchSchema(mode);
|
|
915
915
|
|
|
916
916
|
const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();
|
|
917
|
-
const EXEC_ENDPOINT =
|
|
917
|
+
const EXEC_ENDPOINT = buildCodeApiEndpoint(baseEndpoint, 'exec');
|
|
918
918
|
|
|
919
919
|
const deferredToolsListing = getDeferredToolsListing(
|
|
920
920
|
initParams.toolRegistry,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { RunStepResumeEntry, RunStepResumeState } from '@/types';
|
|
2
|
+
|
|
3
|
+
const RUN_STEP_RESUME_STATE_KEY = '__librechat_run_step_resume_state';
|
|
4
|
+
const RUN_STEP_RESUME_PAYLOAD_KEY = '__librechat_run_step_resume_payload';
|
|
5
|
+
const RUN_STEP_RESUME_WRAPPER_KEY = '__librechat_run_step_resume_wrapper';
|
|
6
|
+
|
|
7
|
+
type RunStepResumePayload = {
|
|
8
|
+
[RUN_STEP_RESUME_STATE_KEY]: RunStepResumeState;
|
|
9
|
+
[RUN_STEP_RESUME_PAYLOAD_KEY]: unknown;
|
|
10
|
+
[RUN_STEP_RESUME_WRAPPER_KEY]: 1;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function isRunStepResumeState(
|
|
14
|
+
value: unknown
|
|
15
|
+
): value is RunStepResumeState {
|
|
16
|
+
if (value == null || typeof value !== 'object') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const state = value as Partial<RunStepResumeState>;
|
|
20
|
+
if (
|
|
21
|
+
state.version !== 1 ||
|
|
22
|
+
!Number.isSafeInteger(state.revision) ||
|
|
23
|
+
(state.revision ?? -1) < 0 ||
|
|
24
|
+
!Number.isSafeInteger(state.nextIndex) ||
|
|
25
|
+
(state.nextIndex ?? -1) < 0 ||
|
|
26
|
+
!Array.isArray(state.toolCallSteps) ||
|
|
27
|
+
!Array.isArray(state.steps)
|
|
28
|
+
) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const toolCallSteps = new Map<string, string>();
|
|
32
|
+
for (const reference of state.toolCallSteps as unknown[]) {
|
|
33
|
+
if (reference == null || typeof reference !== 'object') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const { toolCallId, stepId } = reference as {
|
|
37
|
+
toolCallId?: unknown;
|
|
38
|
+
stepId?: unknown;
|
|
39
|
+
};
|
|
40
|
+
if (
|
|
41
|
+
typeof toolCallId !== 'string' ||
|
|
42
|
+
toolCallId === '' ||
|
|
43
|
+
typeof stepId !== 'string' ||
|
|
44
|
+
stepId === '' ||
|
|
45
|
+
toolCallSteps.has(toolCallId)
|
|
46
|
+
) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
toolCallSteps.set(toolCallId, stepId);
|
|
50
|
+
}
|
|
51
|
+
const stepIds = new Set<string>();
|
|
52
|
+
for (const value of state.steps as unknown[]) {
|
|
53
|
+
if (value == null || typeof value !== 'object') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const entry = value as Partial<RunStepResumeEntry>;
|
|
57
|
+
const step = entry.step;
|
|
58
|
+
if (
|
|
59
|
+
step == null ||
|
|
60
|
+
typeof step.id !== 'string' ||
|
|
61
|
+
step.id === '' ||
|
|
62
|
+
!Number.isSafeInteger(step.index) ||
|
|
63
|
+
step.index < 0 ||
|
|
64
|
+
!Array.isArray(entry.pendingToolCallIds) ||
|
|
65
|
+
entry.pendingToolCallIds.some(
|
|
66
|
+
(toolCallId) => typeof toolCallId !== 'string' || toolCallId === ''
|
|
67
|
+
) ||
|
|
68
|
+
(entry.latestCompletionAt != null &&
|
|
69
|
+
!Number.isFinite(entry.latestCompletionAt)) ||
|
|
70
|
+
typeof entry.openMessageStep !== 'boolean' ||
|
|
71
|
+
entry.pendingToolCallIds.some(
|
|
72
|
+
(toolCallId) => toolCallSteps.get(toolCallId) !== step.id
|
|
73
|
+
) ||
|
|
74
|
+
stepIds.has(step.id)
|
|
75
|
+
) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
stepIds.add(step.id);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isRunStepResumePayload(value: unknown): value is RunStepResumePayload {
|
|
84
|
+
if (value == null || typeof value !== 'object') {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const payload = value as Partial<RunStepResumePayload>;
|
|
88
|
+
return (
|
|
89
|
+
payload[RUN_STEP_RESUME_WRAPPER_KEY] === 1 &&
|
|
90
|
+
Object.prototype.hasOwnProperty.call(value, RUN_STEP_RESUME_PAYLOAD_KEY) &&
|
|
91
|
+
isRunStepResumeState(payload[RUN_STEP_RESUME_STATE_KEY])
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function attachRunStepResumeState(
|
|
96
|
+
payload: unknown,
|
|
97
|
+
state: RunStepResumeState
|
|
98
|
+
): object {
|
|
99
|
+
const publicPayload = isRunStepResumePayload(payload)
|
|
100
|
+
? payload[RUN_STEP_RESUME_PAYLOAD_KEY]
|
|
101
|
+
: payload;
|
|
102
|
+
return {
|
|
103
|
+
[RUN_STEP_RESUME_WRAPPER_KEY]: 1,
|
|
104
|
+
[RUN_STEP_RESUME_PAYLOAD_KEY]: publicPayload,
|
|
105
|
+
[RUN_STEP_RESUME_STATE_KEY]: state,
|
|
106
|
+
} satisfies RunStepResumePayload;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getRunStepResumeState(
|
|
110
|
+
payload: unknown
|
|
111
|
+
): RunStepResumeState | undefined {
|
|
112
|
+
return isRunStepResumePayload(payload)
|
|
113
|
+
? payload[RUN_STEP_RESUME_STATE_KEY]
|
|
114
|
+
: undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function stripRunStepResumeState(payload: unknown): unknown {
|
|
118
|
+
return isRunStepResumePayload(payload)
|
|
119
|
+
? payload[RUN_STEP_RESUME_PAYLOAD_KEY]
|
|
120
|
+
: payload;
|
|
121
|
+
}
|
|
@@ -46,6 +46,7 @@ import type {
|
|
|
46
46
|
RunStep,
|
|
47
47
|
RunStepDeltaEvent,
|
|
48
48
|
RunStepClosedEvent,
|
|
49
|
+
RunStepStatus,
|
|
49
50
|
StandardGraphInput,
|
|
50
51
|
ExecutableSubagentConfigEntry,
|
|
51
52
|
ResolvedSubagentConfig,
|
|
@@ -61,7 +62,6 @@ import type {
|
|
|
61
62
|
ToolApprovalInterruptPayload,
|
|
62
63
|
ToolExecuteBatchRequest,
|
|
63
64
|
ToolCallDelta,
|
|
64
|
-
ToolSessionContext,
|
|
65
65
|
TokenCounter,
|
|
66
66
|
ToolApprovalDecision,
|
|
67
67
|
ToolApprovalDecisionMap,
|
|
@@ -87,6 +87,7 @@ import type {
|
|
|
87
87
|
import type { GraphFactory } from '@/graphs/graphFactory';
|
|
88
88
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
89
89
|
import type { HandlerRegistry } from '@/events';
|
|
90
|
+
import { stripRunStepResumeState } from '@/tools/runStepResume';
|
|
90
91
|
import {
|
|
91
92
|
getSubagentApprovalExecutionScope,
|
|
92
93
|
SubagentDefinitionBindingError,
|
|
@@ -124,6 +125,7 @@ import {
|
|
|
124
125
|
createChildGraphPlan,
|
|
125
126
|
isGraphSubagentConfig,
|
|
126
127
|
} from './childGraphConfig';
|
|
128
|
+
import { seedAgentInitialSessions } from '@/utils/toolSessions';
|
|
127
129
|
import { stableStringify } from '@/tools/eagerEventExecution';
|
|
128
130
|
import { composeAbortSignals } from '@/utils/misc';
|
|
129
131
|
|
|
@@ -147,71 +149,11 @@ const SUBAGENT_CONFIG_CHANGED_MESSAGE =
|
|
|
147
149
|
const SUBAGENT_INVOCATION_CHANGED_MESSAGE =
|
|
148
150
|
'Subagent error: Subagent invocation changed for this execution.';
|
|
149
151
|
|
|
150
|
-
function cloneToolSessionContext(
|
|
151
|
-
context: ToolSessionContext
|
|
152
|
-
): ToolSessionContext {
|
|
153
|
-
return {
|
|
154
|
-
...context,
|
|
155
|
-
...(context.files == null
|
|
156
|
-
? {}
|
|
157
|
-
: {
|
|
158
|
-
files: context.files.map((file) => ({
|
|
159
|
-
...file,
|
|
160
|
-
storage_session_id: file.storage_session_id ?? context.session_id,
|
|
161
|
-
})),
|
|
162
|
-
}),
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
152
|
function seedChildGraphSessions(
|
|
167
153
|
childGraph: StandardGraph,
|
|
168
154
|
agents: AgentInputs[]
|
|
169
155
|
): void {
|
|
170
|
-
|
|
171
|
-
for (const agent of agents) {
|
|
172
|
-
if (agent.initialSessions == null) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
for (const [toolName, context] of agent.initialSessions) {
|
|
176
|
-
const existing = childGraph.sessions.get(toolName);
|
|
177
|
-
if (existing == null) {
|
|
178
|
-
const cloned = cloneToolSessionContext(context);
|
|
179
|
-
childGraph.sessions.set(toolName, cloned);
|
|
180
|
-
seenFilesByTool.set(
|
|
181
|
-
toolName,
|
|
182
|
-
new Set(
|
|
183
|
-
cloned.files?.map(
|
|
184
|
-
(file) => `${file.storage_session_id ?? ''}\0${file.id}`
|
|
185
|
-
) ?? []
|
|
186
|
-
)
|
|
187
|
-
);
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
if (context.files == null || context.files.length === 0) {
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
const seenFiles =
|
|
194
|
-
seenFilesByTool.get(toolName) ??
|
|
195
|
-
new Set(
|
|
196
|
-
existing.files?.map(
|
|
197
|
-
(file) =>
|
|
198
|
-
`${file.storage_session_id ?? existing.session_id}\0${file.id}`
|
|
199
|
-
) ?? []
|
|
200
|
-
);
|
|
201
|
-
const files = existing.files == null ? [] : [...existing.files];
|
|
202
|
-
for (const file of context.files) {
|
|
203
|
-
const storageSessionId = file.storage_session_id ?? context.session_id;
|
|
204
|
-
const key = `${storageSessionId}\0${file.id}`;
|
|
205
|
-
if (seenFiles.has(key)) {
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
seenFiles.add(key);
|
|
209
|
-
files.push({ ...file, storage_session_id: storageSessionId });
|
|
210
|
-
}
|
|
211
|
-
seenFilesByTool.set(toolName, seenFiles);
|
|
212
|
-
childGraph.sessions.set(toolName, { ...existing, files });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
156
|
+
seedAgentInitialSessions(childGraph.sessions, agents);
|
|
215
157
|
}
|
|
216
158
|
|
|
217
159
|
async function dispatchObservationalSubagentUpdate(
|
|
@@ -713,11 +655,11 @@ function addSubagentScope(
|
|
|
713
655
|
resumeManifest?: SubagentResumeManifest
|
|
714
656
|
): Interrupt[] {
|
|
715
657
|
return interrupts.map((childInterrupt) => {
|
|
716
|
-
let payload = childInterrupt.value;
|
|
658
|
+
let payload = stripRunStepResumeState(childInterrupt.value);
|
|
717
659
|
if (isToolApprovalPayload(payload)) {
|
|
718
660
|
payload = {
|
|
719
|
-
...
|
|
720
|
-
subagent:
|
|
661
|
+
...payload,
|
|
662
|
+
subagent: payload.subagent ?? scope,
|
|
721
663
|
};
|
|
722
664
|
}
|
|
723
665
|
return {
|
|
@@ -1610,6 +1552,42 @@ export class SubagentExecutor {
|
|
|
1610
1552
|
graph.clearHeavyState();
|
|
1611
1553
|
}
|
|
1612
1554
|
|
|
1555
|
+
/**
|
|
1556
|
+
* Terminal sweep for a child graph's run steps. `Run.processStream` sweeps
|
|
1557
|
+
* only the graph it owns, and a child executes through `workflow.invoke()`
|
|
1558
|
+
* outside that loop — so without this, a child's last message step (no
|
|
1559
|
+
* successor step ever opens to close it) and every step of an aborted child
|
|
1560
|
+
* stay `in_progress` forever, leaving hosts with unmatched starts.
|
|
1561
|
+
*
|
|
1562
|
+
* Must run BEFORE `forwarding.drain()`: closures reach the parent only as
|
|
1563
|
+
* child custom events through the forwarder, which stops relaying once
|
|
1564
|
+
* drained.
|
|
1565
|
+
*
|
|
1566
|
+
* Deliberately not called on the interrupt path, mirroring the parent's
|
|
1567
|
+
* `isAwaitingResume` guard: an in-process resume continues into these same
|
|
1568
|
+
* steps, so closing them would strand the resumed run. A resume that
|
|
1569
|
+
* crosses a process boundary is a known gap either way — the child rebuilds
|
|
1570
|
+
* from `SubagentGraphResumeState`, which carries tool-call identity but not
|
|
1571
|
+
* `contentData`, so pre-interrupt steps are unreachable from the new graph
|
|
1572
|
+
* and stay open. That is the child-level instance of the same cross-process
|
|
1573
|
+
* orphan the parent graph has, and it needs persisted lifecycle state to
|
|
1574
|
+
* fix rather than a sweep here.
|
|
1575
|
+
*
|
|
1576
|
+
* `at` is the moment execution actually ended, threaded through so
|
|
1577
|
+
* observational work between then and the sweep cannot inflate the stamps.
|
|
1578
|
+
*/
|
|
1579
|
+
private async closeChildRunSteps(
|
|
1580
|
+
graph: StandardGraph,
|
|
1581
|
+
status: Exclude<RunStepStatus, 'in_progress'>,
|
|
1582
|
+
at?: number
|
|
1583
|
+
): Promise<void> {
|
|
1584
|
+
try {
|
|
1585
|
+
await graph.closeUnfinishedRunSteps(status, at);
|
|
1586
|
+
} catch (_e) {
|
|
1587
|
+
/** A failed sweep must never mask the subagent's own outcome */
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1613
1591
|
clearHeavyState(): void {
|
|
1614
1592
|
this.executions.clear((record) => {
|
|
1615
1593
|
if (record.activeRun != null) {
|
|
@@ -2470,6 +2448,8 @@ export class SubagentExecutor {
|
|
|
2470
2448
|
result = childResult;
|
|
2471
2449
|
}
|
|
2472
2450
|
} catch (error) {
|
|
2451
|
+
/** Stamped at failure, not after the error-envelope work below. */
|
|
2452
|
+
const childTerminalAt = Date.now();
|
|
2473
2453
|
if (isGraphInterrupt(error)) {
|
|
2474
2454
|
const activeChildRun = execution.activeRun;
|
|
2475
2455
|
if (activeChildRun != null) {
|
|
@@ -2505,6 +2485,16 @@ export class SubagentExecutor {
|
|
|
2505
2485
|
childBreaker.abort(error);
|
|
2506
2486
|
}
|
|
2507
2487
|
const errorMessage = truncateErrorMessage(error);
|
|
2488
|
+
/**
|
|
2489
|
+
* `cancelled` vs `failed` mirrors `Run.resolveSweepStatus`: an aborted
|
|
2490
|
+
* child was stopped on purpose (caller abort, or a breaker trip from a
|
|
2491
|
+
* parallel sibling), anything else died of an unexpected error.
|
|
2492
|
+
*/
|
|
2493
|
+
await this.closeChildRunSteps(
|
|
2494
|
+
childGraph,
|
|
2495
|
+
childSignal.aborted ? 'cancelled' : 'failed',
|
|
2496
|
+
childTerminalAt
|
|
2497
|
+
);
|
|
2508
2498
|
if (forwarding) {
|
|
2509
2499
|
await forwarding.drain();
|
|
2510
2500
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
@@ -2537,6 +2527,14 @@ export class SubagentExecutor {
|
|
|
2537
2527
|
};
|
|
2538
2528
|
}
|
|
2539
2529
|
|
|
2530
|
+
/**
|
|
2531
|
+
* Child execution ended here. Captured before the observational work
|
|
2532
|
+
* below — an awaited `SubagentStop` hook, then the forwarder drain — so a
|
|
2533
|
+
* slow hook cannot inflate the closure stamps with its own latency.
|
|
2534
|
+
* Mirrors the parent's `terminalAt` capture in `Run.processStream`.
|
|
2535
|
+
*/
|
|
2536
|
+
const childTerminalAt = Date.now();
|
|
2537
|
+
|
|
2540
2538
|
if (result == null) {
|
|
2541
2539
|
throw new Error('Subagent completed without producing graph state.');
|
|
2542
2540
|
}
|
|
@@ -2573,6 +2571,10 @@ export class SubagentExecutor {
|
|
|
2573
2571
|
});
|
|
2574
2572
|
}
|
|
2575
2573
|
|
|
2574
|
+
if (!childAlreadyCompleted) {
|
|
2575
|
+
await this.closeChildRunSteps(childGraph, 'completed', childTerminalAt);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2576
2578
|
if (forwarding && !childAlreadyCompleted) {
|
|
2577
2579
|
await forwarding.drain();
|
|
2578
2580
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
@@ -2,7 +2,13 @@ import type { ToolCall, ToolMessage } from '@langchain/core/messages/tool';
|
|
|
2
2
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
3
|
import type { ToolOutputReferenceState } from '@/tools/toolOutputReferences';
|
|
4
4
|
import type { ToolApprovalReplaySnapshot } from '@/hooks';
|
|
5
|
-
import type { ToolSessionContext } from '@/types';
|
|
5
|
+
import type { RunStepResumeState, ToolSessionContext } from '@/types';
|
|
6
|
+
import {
|
|
7
|
+
attachRunStepResumeState,
|
|
8
|
+
getRunStepResumeState,
|
|
9
|
+
isRunStepResumeState,
|
|
10
|
+
stripRunStepResumeState,
|
|
11
|
+
} from '@/tools/runStepResume';
|
|
6
12
|
|
|
7
13
|
export const SUBAGENT_RESUME_MANIFEST_CONFIG_KEY =
|
|
8
14
|
'__librechat_subagent_resume_manifest';
|
|
@@ -57,6 +63,7 @@ export interface SubagentGraphResumeState {
|
|
|
57
63
|
toolNodes: SubagentToolNodeResumeState[];
|
|
58
64
|
eagerToolUsage: SubagentEagerToolUsageState[];
|
|
59
65
|
eagerToolSuppressions: string[];
|
|
66
|
+
runStepState?: RunStepResumeState;
|
|
60
67
|
toolOutputReferences?: ToolOutputReferenceState;
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -291,6 +298,8 @@ function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
|
|
|
291
298
|
!state.eagerToolUsage.every(isEagerToolUsageState) ||
|
|
292
299
|
!Array.isArray(state.eagerToolSuppressions) ||
|
|
293
300
|
!state.eagerToolSuppressions.every(isString) ||
|
|
301
|
+
(state.runStepState != null &&
|
|
302
|
+
!isRunStepResumeState(state.runStepState)) ||
|
|
294
303
|
(state.toolOutputReferences != null &&
|
|
295
304
|
!isToolOutputReferenceState(state.toolOutputReferences))
|
|
296
305
|
) {
|
|
@@ -412,6 +421,7 @@ function isSubagentResumeManifest(
|
|
|
412
421
|
export function getSubagentResumeManifest(
|
|
413
422
|
payload: unknown
|
|
414
423
|
): SubagentResumeManifest | undefined {
|
|
424
|
+
payload = stripRunStepResumeState(payload);
|
|
415
425
|
if (payload == null || typeof payload !== 'object') {
|
|
416
426
|
return undefined;
|
|
417
427
|
}
|
|
@@ -489,6 +499,16 @@ export function attachSubagentResumeManifest(
|
|
|
489
499
|
payload: unknown,
|
|
490
500
|
manifest: SubagentResumeManifest
|
|
491
501
|
): object {
|
|
502
|
+
const runStepState = getRunStepResumeState(payload);
|
|
503
|
+
if (runStepState != null) {
|
|
504
|
+
return attachRunStepResumeState(
|
|
505
|
+
attachSubagentResumeManifest(
|
|
506
|
+
stripRunStepResumeState(payload),
|
|
507
|
+
manifest
|
|
508
|
+
),
|
|
509
|
+
runStepState
|
|
510
|
+
);
|
|
511
|
+
}
|
|
492
512
|
if (
|
|
493
513
|
isWrappedSubagentResumePayload(payload) ||
|
|
494
514
|
isInlineSubagentResumePayload(payload)
|
|
@@ -512,6 +532,7 @@ export function attachSubagentResumeManifest(
|
|
|
512
532
|
}
|
|
513
533
|
|
|
514
534
|
export function stripSubagentResumeManifest(payload: unknown): unknown {
|
|
535
|
+
payload = stripRunStepResumeState(payload);
|
|
515
536
|
if (isWrappedSubagentResumePayload(payload)) {
|
|
516
537
|
return payload[SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY];
|
|
517
538
|
}
|
package/src/types/graph.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
import type {
|
|
21
21
|
RunStep,
|
|
22
22
|
RunStepDeltaEvent,
|
|
23
|
+
RunStepResumeState,
|
|
23
24
|
RunStepClosedEvent,
|
|
24
25
|
MessageDeltaEvent,
|
|
25
26
|
ReasoningDeltaEvent,
|
|
@@ -76,6 +77,7 @@ export type SystemCallbacks = {
|
|
|
76
77
|
|
|
77
78
|
export type BaseGraphState = {
|
|
78
79
|
messages: BaseMessage[];
|
|
80
|
+
runStepState?: RunStepResumeState;
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
export type AgentSubgraphState = BaseGraphState & {
|
|
@@ -803,6 +805,14 @@ export interface LangfuseConfig {
|
|
|
803
805
|
|
|
804
806
|
export interface AgentInputs {
|
|
805
807
|
agentId: string;
|
|
808
|
+
/**
|
|
809
|
+
* Partition key for transient code-session ids and file refs. Agents with
|
|
810
|
+
* the same key share those refs; different execution profiles/scopes must
|
|
811
|
+
* use different keys. Defaults to the legacy shared `execute_code` slot.
|
|
812
|
+
* Non-default partitions also disable speculative eager execution of code
|
|
813
|
+
* tools because their factory may target a durable backend.
|
|
814
|
+
*/
|
|
815
|
+
codeSessionKey?: string;
|
|
806
816
|
/** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
|
|
807
817
|
name?: string;
|
|
808
818
|
toolEnd?: boolean;
|
package/src/types/stream.ts
CHANGED
|
@@ -103,6 +103,23 @@ export type RunStep = {
|
|
|
103
103
|
// };
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
/** Minimal durable lifecycle state needed to continue open run steps. */
|
|
107
|
+
export interface RunStepResumeEntry {
|
|
108
|
+
step: RunStep;
|
|
109
|
+
pendingToolCallIds: string[];
|
|
110
|
+
latestCompletionAt?: number;
|
|
111
|
+
openMessageStep: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** SDK-private state persisted in LangGraph checkpoints for process-safe resume. */
|
|
115
|
+
export interface RunStepResumeState {
|
|
116
|
+
version: 1;
|
|
117
|
+
revision: number;
|
|
118
|
+
nextIndex: number;
|
|
119
|
+
toolCallSteps: Array<{ toolCallId: string; stepId: string }>;
|
|
120
|
+
steps: RunStepResumeEntry[];
|
|
121
|
+
}
|
|
122
|
+
|
|
106
123
|
/**
|
|
107
124
|
* Represents a run step delta i.e. any changed fields on a run step during
|
|
108
125
|
* streaming.
|