@librechat/agents 3.4.0 → 3.4.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 +111 -27
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +83 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +60 -14
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +2 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +3 -3
- package/dist/cjs/llm/invoke.cjs +2 -2
- package/dist/cjs/main.cjs +14 -13
- package/dist/cjs/messages/format.cjs +40 -23
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +82 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +37 -2
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/summarization/node.cjs +6 -3
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
- package/dist/cjs/tools/ToolNode.cjs +316 -94
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +630 -78
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +175 -0
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
- package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +2 -2
- package/dist/esm/graphs/Graph.mjs +111 -27
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +83 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +60 -14
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +2 -1
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +2 -2
- package/dist/esm/main.mjs +11 -11
- package/dist/esm/messages/format.mjs +40 -23
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +83 -16
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +37 -2
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/summarization/node.mjs +6 -3
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
- package/dist/esm/tools/ToolNode.mjs +317 -95
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +631 -79
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +168 -0
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
- package/dist/esm/tools/toolOutputReferences.mjs +20 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -2
- package/dist/types/graphs/Graph.d.ts +16 -1
- package/dist/types/hooks/HookRegistry.d.ts +10 -1
- package/dist/types/hooks/executeHooks.d.ts +5 -1
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/types.d.ts +12 -0
- package/dist/types/run.d.ts +4 -1
- package/dist/types/session/AgentSession.d.ts +1 -0
- package/dist/types/tools/ToolNode.d.ts +23 -15
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +46 -7
- package/dist/types/tools/subagent/SubagentReplay.d.ts +81 -0
- package/dist/types/tools/toolOutputReferences.d.ts +12 -0
- package/dist/types/types/graph.d.ts +5 -5
- package/dist/types/types/hitl.d.ts +15 -0
- package/package.json +6 -2
- package/src/graphs/Graph.ts +246 -52
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
- package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
- package/src/hooks/HookRegistry.ts +151 -1
- package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
- package/src/hooks/__tests__/executeHooks.test.ts +160 -0
- package/src/hooks/executeHooks.ts +133 -15
- package/src/hooks/index.ts +3 -1
- package/src/hooks/types.ts +16 -0
- package/src/messages/format.ts +54 -26
- package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
- package/src/messages/formatAgentMessages.steer.test.ts +16 -11
- package/src/messages/formatAgentMessages.test.ts +7 -2
- package/src/run.ts +164 -18
- package/src/session/AgentSession.ts +52 -2
- package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
- package/src/summarization/__tests__/node.test.ts +32 -14
- package/src/summarization/node.ts +15 -11
- package/src/tools/ToolNode.ts +631 -157
- package/src/tools/__tests__/SubagentExecutor.test.ts +542 -6
- package/src/tools/__tests__/SubagentReplay.test.ts +300 -0
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +486 -2
- package/src/tools/__tests__/hitl.test.ts +234 -5
- package/src/tools/__tests__/subagentHooks.test.ts +1040 -62
- package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
- package/src/tools/subagent/SubagentExecutor.ts +1369 -131
- package/src/tools/subagent/SubagentReplay.ts +575 -0
- package/src/tools/toolOutputReferences.ts +40 -1
- package/src/types/graph.ts +5 -5
- package/src/types/hitl.ts +16 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import type { ToolCall, ToolMessage } from '@langchain/core/messages/tool';
|
|
2
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
|
+
import type { ToolOutputReferenceState } from '@/tools/toolOutputReferences';
|
|
4
|
+
import type { ToolApprovalReplaySnapshot } from '@/hooks';
|
|
5
|
+
import type { ToolSessionContext } from '@/types';
|
|
6
|
+
|
|
7
|
+
export const SUBAGENT_RESUME_MANIFEST_CONFIG_KEY =
|
|
8
|
+
'__librechat_subagent_resume_manifest';
|
|
9
|
+
export const SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY =
|
|
10
|
+
'__librechat_subagent_resume_attempt';
|
|
11
|
+
export const SUBAGENT_PARENT_BATCH_CONFIG_KEY =
|
|
12
|
+
'__librechat_subagent_parent_batch';
|
|
13
|
+
|
|
14
|
+
const SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY =
|
|
15
|
+
'__librechat_subagent_resume_manifest';
|
|
16
|
+
const SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY =
|
|
17
|
+
'__librechat_subagent_resume_payload';
|
|
18
|
+
const SUBAGENT_RESUME_WRAPPER_VERSION_KEY =
|
|
19
|
+
'__librechat_subagent_resume_wrapper';
|
|
20
|
+
const SUBAGENT_RESUME_PRIVATE_PAYLOAD_KEYS = [
|
|
21
|
+
SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY,
|
|
22
|
+
SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY,
|
|
23
|
+
SUBAGENT_RESUME_WRAPPER_VERSION_KEY,
|
|
24
|
+
] as const;
|
|
25
|
+
const MAX_RESUME_MANIFEST_DEPTH = 32;
|
|
26
|
+
|
|
27
|
+
export interface SubagentCheckpointReference {
|
|
28
|
+
threadId: string;
|
|
29
|
+
checkpointId: string;
|
|
30
|
+
checkpointNs: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SubagentToolCallStepReference {
|
|
34
|
+
toolCallId: string;
|
|
35
|
+
stepId: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SubagentToolSessionReference {
|
|
39
|
+
toolName: string;
|
|
40
|
+
context: ToolSessionContext;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SubagentToolNodeResumeState {
|
|
44
|
+
stateKey: string;
|
|
45
|
+
toolUsageCounts: Array<{ toolName: string; count: number }>;
|
|
46
|
+
directPathTurns: Array<{ toolCallId: string; turn: number }>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SubagentEagerToolUsageState {
|
|
50
|
+
agentId: string;
|
|
51
|
+
toolUsageCounts: Array<{ toolName: string; count: number }>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SubagentGraphResumeState {
|
|
55
|
+
toolCallSteps: SubagentToolCallStepReference[];
|
|
56
|
+
toolSessions: SubagentToolSessionReference[];
|
|
57
|
+
toolNodes: SubagentToolNodeResumeState[];
|
|
58
|
+
eagerToolUsage: SubagentEagerToolUsageState[];
|
|
59
|
+
eagerToolSuppressions: string[];
|
|
60
|
+
toolOutputReferences?: ToolOutputReferenceState;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Private checkpoint payload linking a parent pause to an exact child state. */
|
|
64
|
+
export interface SubagentResumeExecution {
|
|
65
|
+
parentToolCallId: string;
|
|
66
|
+
childRunId: string;
|
|
67
|
+
approvalExecutionScope: string;
|
|
68
|
+
checkpoints: SubagentCheckpointReference[];
|
|
69
|
+
graphState: SubagentGraphResumeState;
|
|
70
|
+
approvalReplays: ToolApprovalReplaySnapshot[];
|
|
71
|
+
descendant?: SubagentResumeManifest;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Private checkpoint payload linking a parent pause to every child state. */
|
|
75
|
+
export interface SubagentResumeManifest {
|
|
76
|
+
version: 1;
|
|
77
|
+
executions: SubagentResumeExecution[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type PayloadWithSubagentResumeManifest = {
|
|
81
|
+
[SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY]?: unknown;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type WrappedSubagentResumePayload = PayloadWithSubagentResumeManifest & {
|
|
85
|
+
[SUBAGENT_RESUME_WRAPPER_VERSION_KEY]: 1;
|
|
86
|
+
[SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY]: unknown;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function isString(value: unknown): value is string {
|
|
90
|
+
return typeof value === 'string' && value.length > 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isNonnegativeInteger(value: unknown): value is number {
|
|
94
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isApprovalReplaySnapshot(
|
|
98
|
+
value: unknown
|
|
99
|
+
): value is ToolApprovalReplaySnapshot {
|
|
100
|
+
if (value == null || typeof value !== 'object') {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const snapshot = value as Partial<ToolApprovalReplaySnapshot>;
|
|
104
|
+
const key = snapshot.key;
|
|
105
|
+
const result = snapshot.result;
|
|
106
|
+
return (
|
|
107
|
+
key != null &&
|
|
108
|
+
isString(key.executionScope) &&
|
|
109
|
+
typeof key.agentId === 'string' &&
|
|
110
|
+
isString(key.toolUseId) &&
|
|
111
|
+
result != null &&
|
|
112
|
+
result.decision === 'ask' &&
|
|
113
|
+
Array.isArray(result.additionalContexts) &&
|
|
114
|
+
Array.isArray(result.injectedMessages) &&
|
|
115
|
+
Array.isArray(result.errors)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isCheckpointReference(
|
|
120
|
+
value: unknown
|
|
121
|
+
): value is SubagentCheckpointReference {
|
|
122
|
+
if (value == null || typeof value !== 'object') {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const checkpoint = value as Partial<SubagentCheckpointReference>;
|
|
126
|
+
return (
|
|
127
|
+
isString(checkpoint.threadId) &&
|
|
128
|
+
isString(checkpoint.checkpointId) &&
|
|
129
|
+
typeof checkpoint.checkpointNs === 'string'
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isToolCallStepReference(
|
|
134
|
+
value: unknown
|
|
135
|
+
): value is SubagentToolCallStepReference {
|
|
136
|
+
if (value == null || typeof value !== 'object') {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const reference = value as Partial<SubagentToolCallStepReference>;
|
|
140
|
+
return isString(reference.toolCallId) && isString(reference.stepId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isToolSessionReference(
|
|
144
|
+
value: unknown
|
|
145
|
+
): value is SubagentToolSessionReference {
|
|
146
|
+
if (value == null || typeof value !== 'object') {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const reference = value as Partial<SubagentToolSessionReference>;
|
|
150
|
+
const context = reference.context;
|
|
151
|
+
return (
|
|
152
|
+
isString(reference.toolName) &&
|
|
153
|
+
context != null &&
|
|
154
|
+
typeof context === 'object' &&
|
|
155
|
+
isString(context.session_id) &&
|
|
156
|
+
Number.isFinite(context.lastUpdated) &&
|
|
157
|
+
(context.files == null || Array.isArray(context.files))
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isToolUsageCountReference(
|
|
162
|
+
value: unknown
|
|
163
|
+
): value is SubagentToolNodeResumeState['toolUsageCounts'][number] {
|
|
164
|
+
if (value == null || typeof value !== 'object') {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
const entry = value as Partial<
|
|
168
|
+
SubagentToolNodeResumeState['toolUsageCounts'][number]
|
|
169
|
+
>;
|
|
170
|
+
return isString(entry.toolName) && isNonnegativeInteger(entry.count);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isDirectPathTurnReference(
|
|
174
|
+
value: unknown
|
|
175
|
+
): value is SubagentToolNodeResumeState['directPathTurns'][number] {
|
|
176
|
+
if (value == null || typeof value !== 'object') {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const entry = value as Partial<
|
|
180
|
+
SubagentToolNodeResumeState['directPathTurns'][number]
|
|
181
|
+
>;
|
|
182
|
+
return isString(entry.toolCallId) && isNonnegativeInteger(entry.turn);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isToolNodeResumeState(
|
|
186
|
+
value: unknown
|
|
187
|
+
): value is SubagentToolNodeResumeState {
|
|
188
|
+
if (value == null || typeof value !== 'object') {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const state = value as Partial<SubagentToolNodeResumeState>;
|
|
192
|
+
if (
|
|
193
|
+
!isString(state.stateKey) ||
|
|
194
|
+
!Array.isArray(state.toolUsageCounts) ||
|
|
195
|
+
!Array.isArray(state.directPathTurns)
|
|
196
|
+
) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const validUsageCounts = state.toolUsageCounts.every(
|
|
200
|
+
isToolUsageCountReference
|
|
201
|
+
);
|
|
202
|
+
const validDirectTurns = state.directPathTurns.every(
|
|
203
|
+
isDirectPathTurnReference
|
|
204
|
+
);
|
|
205
|
+
if (!validUsageCounts || !validDirectTurns) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
const usageToolNames = new Set(
|
|
209
|
+
state.toolUsageCounts.map((entry) => entry.toolName)
|
|
210
|
+
);
|
|
211
|
+
const directToolCallIds = new Set(
|
|
212
|
+
state.directPathTurns.map((entry) => entry.toolCallId)
|
|
213
|
+
);
|
|
214
|
+
return (
|
|
215
|
+
usageToolNames.size === state.toolUsageCounts.length &&
|
|
216
|
+
directToolCallIds.size === state.directPathTurns.length
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isEagerToolUsageState(
|
|
221
|
+
value: unknown
|
|
222
|
+
): value is SubagentEagerToolUsageState {
|
|
223
|
+
if (value == null || typeof value !== 'object') {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const state = value as Partial<SubagentEagerToolUsageState>;
|
|
227
|
+
if (
|
|
228
|
+
typeof state.agentId !== 'string' ||
|
|
229
|
+
!Array.isArray(state.toolUsageCounts) ||
|
|
230
|
+
!state.toolUsageCounts.every(isToolUsageCountReference)
|
|
231
|
+
) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const toolNames = new Set(
|
|
235
|
+
state.toolUsageCounts.map((entry) => entry.toolName)
|
|
236
|
+
);
|
|
237
|
+
return toolNames.size === state.toolUsageCounts.length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isToolOutputReferenceEntry(
|
|
241
|
+
value: unknown
|
|
242
|
+
): value is ToolOutputReferenceState['entries'][number] {
|
|
243
|
+
if (value == null || typeof value !== 'object') {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const entry = value as Partial<ToolOutputReferenceState['entries'][number]>;
|
|
247
|
+
return isString(entry.key) && typeof entry.value === 'string';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isToolOutputReferenceState(
|
|
251
|
+
value: unknown
|
|
252
|
+
): value is ToolOutputReferenceState {
|
|
253
|
+
if (value == null || typeof value !== 'object') {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const state = value as Partial<ToolOutputReferenceState>;
|
|
257
|
+
if (
|
|
258
|
+
!Array.isArray(state.entries) ||
|
|
259
|
+
!state.entries.every(isToolOutputReferenceEntry) ||
|
|
260
|
+
!isNonnegativeInteger(state.turnCounter) ||
|
|
261
|
+
!Array.isArray(state.warnedNonStringTools) ||
|
|
262
|
+
!state.warnedNonStringTools.every(isString)
|
|
263
|
+
) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const entryKeys = new Set(state.entries.map((entry) => entry.key));
|
|
267
|
+
const warnedTools = new Set(state.warnedNonStringTools);
|
|
268
|
+
return (
|
|
269
|
+
entryKeys.size === state.entries.length &&
|
|
270
|
+
warnedTools.size === state.warnedNonStringTools.length
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isGraphResumeState(value: unknown): value is SubagentGraphResumeState {
|
|
275
|
+
if (value == null || typeof value !== 'object') {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
const state = value as Partial<SubagentGraphResumeState>;
|
|
279
|
+
if (
|
|
280
|
+
!Array.isArray(state.toolCallSteps) ||
|
|
281
|
+
!state.toolCallSteps.every(isToolCallStepReference) ||
|
|
282
|
+
!Array.isArray(state.toolSessions) ||
|
|
283
|
+
!state.toolSessions.every(isToolSessionReference) ||
|
|
284
|
+
!Array.isArray(state.toolNodes) ||
|
|
285
|
+
!state.toolNodes.every(isToolNodeResumeState) ||
|
|
286
|
+
!Array.isArray(state.eagerToolUsage) ||
|
|
287
|
+
!state.eagerToolUsage.every(isEagerToolUsageState) ||
|
|
288
|
+
!Array.isArray(state.eagerToolSuppressions) ||
|
|
289
|
+
!state.eagerToolSuppressions.every(isString) ||
|
|
290
|
+
(state.toolOutputReferences != null &&
|
|
291
|
+
!isToolOutputReferenceState(state.toolOutputReferences))
|
|
292
|
+
) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
const toolCallIds = new Set(
|
|
296
|
+
state.toolCallSteps.map((reference) => reference.toolCallId)
|
|
297
|
+
);
|
|
298
|
+
const toolNames = new Set(
|
|
299
|
+
state.toolSessions.map((reference) => reference.toolName)
|
|
300
|
+
);
|
|
301
|
+
const toolNodeKeys = new Set(state.toolNodes.map((node) => node.stateKey));
|
|
302
|
+
const eagerAgentIds = new Set(
|
|
303
|
+
state.eagerToolUsage.map((usage) => usage.agentId)
|
|
304
|
+
);
|
|
305
|
+
const eagerSuppressions = new Set(state.eagerToolSuppressions);
|
|
306
|
+
return (
|
|
307
|
+
toolCallIds.size === state.toolCallSteps.length &&
|
|
308
|
+
toolNames.size === state.toolSessions.length &&
|
|
309
|
+
toolNodeKeys.size === state.toolNodes.length &&
|
|
310
|
+
eagerAgentIds.size === state.eagerToolUsage.length &&
|
|
311
|
+
eagerSuppressions.size === state.eagerToolSuppressions.length
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function isSubagentResumeExecution(
|
|
316
|
+
value: unknown,
|
|
317
|
+
seen: Set<object>,
|
|
318
|
+
depth: number
|
|
319
|
+
): value is SubagentResumeExecution {
|
|
320
|
+
if (value == null || typeof value !== 'object') {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
const execution = value as Partial<SubagentResumeExecution>;
|
|
324
|
+
const childRunId = execution.childRunId;
|
|
325
|
+
const approvalExecutionScope = execution.approvalExecutionScope;
|
|
326
|
+
const checkpoints = execution.checkpoints;
|
|
327
|
+
const graphState = execution.graphState;
|
|
328
|
+
const approvalReplays = execution.approvalReplays;
|
|
329
|
+
if (
|
|
330
|
+
!isString(execution.parentToolCallId) ||
|
|
331
|
+
!isString(childRunId) ||
|
|
332
|
+
!isString(approvalExecutionScope) ||
|
|
333
|
+
!Array.isArray(checkpoints) ||
|
|
334
|
+
checkpoints.length === 0 ||
|
|
335
|
+
!checkpoints.every(isCheckpointReference) ||
|
|
336
|
+
!isGraphResumeState(graphState) ||
|
|
337
|
+
!Array.isArray(approvalReplays)
|
|
338
|
+
) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
const checkpointThreadId = checkpoints[0].threadId;
|
|
342
|
+
const checkpointNamespaces = new Set(
|
|
343
|
+
checkpoints.map((checkpoint) => checkpoint.checkpointNs)
|
|
344
|
+
);
|
|
345
|
+
const validApprovalReplays = approvalReplays.every(
|
|
346
|
+
(snapshot) =>
|
|
347
|
+
isApprovalReplaySnapshot(snapshot) &&
|
|
348
|
+
snapshot.key.executionScope === approvalExecutionScope
|
|
349
|
+
);
|
|
350
|
+
if (!validApprovalReplays) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const approvalKeys = new Set(
|
|
354
|
+
approvalReplays.map((snapshot) =>
|
|
355
|
+
JSON.stringify([
|
|
356
|
+
snapshot.key.executionScope,
|
|
357
|
+
snapshot.key.agentId,
|
|
358
|
+
snapshot.key.toolUseId,
|
|
359
|
+
])
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
return (
|
|
363
|
+
checkpoints.every(
|
|
364
|
+
(checkpoint) => checkpoint.threadId === checkpointThreadId
|
|
365
|
+
) &&
|
|
366
|
+
checkpointNamespaces.size === checkpoints.length &&
|
|
367
|
+
approvalKeys.size === approvalReplays.length &&
|
|
368
|
+
(execution.descendant == null ||
|
|
369
|
+
isSubagentResumeManifest(execution.descendant, seen, depth + 1))
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function isSubagentResumeManifest(
|
|
374
|
+
value: unknown,
|
|
375
|
+
seen: Set<object> = new Set(),
|
|
376
|
+
depth = 0
|
|
377
|
+
): value is SubagentResumeManifest {
|
|
378
|
+
if (
|
|
379
|
+
value == null ||
|
|
380
|
+
typeof value !== 'object' ||
|
|
381
|
+
depth > MAX_RESUME_MANIFEST_DEPTH ||
|
|
382
|
+
seen.has(value)
|
|
383
|
+
) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
seen.add(value);
|
|
387
|
+
const manifest = value as Partial<SubagentResumeManifest>;
|
|
388
|
+
if (
|
|
389
|
+
manifest.version !== 1 ||
|
|
390
|
+
!Array.isArray(manifest.executions) ||
|
|
391
|
+
manifest.executions.length === 0 ||
|
|
392
|
+
!manifest.executions.every((execution) =>
|
|
393
|
+
isSubagentResumeExecution(execution, seen, depth)
|
|
394
|
+
)
|
|
395
|
+
) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
const parentToolCallIds = new Set(
|
|
399
|
+
manifest.executions.map((execution) => execution.parentToolCallId)
|
|
400
|
+
);
|
|
401
|
+
return parentToolCallIds.size === manifest.executions.length;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function getSubagentResumeManifest(
|
|
405
|
+
payload: unknown
|
|
406
|
+
): SubagentResumeManifest | undefined {
|
|
407
|
+
if (payload == null || typeof payload !== 'object') {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
if (
|
|
411
|
+
!Object.prototype.hasOwnProperty.call(
|
|
412
|
+
payload,
|
|
413
|
+
SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY
|
|
414
|
+
)
|
|
415
|
+
) {
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
const manifest = (payload as PayloadWithSubagentResumeManifest)[
|
|
419
|
+
SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY
|
|
420
|
+
];
|
|
421
|
+
return isSubagentResumeManifest(manifest) ? manifest : undefined;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function requireValidSubagentResumeManifest(
|
|
425
|
+
payload: unknown
|
|
426
|
+
): SubagentResumeManifest | undefined {
|
|
427
|
+
if (
|
|
428
|
+
payload == null ||
|
|
429
|
+
typeof payload !== 'object' ||
|
|
430
|
+
!Object.prototype.hasOwnProperty.call(
|
|
431
|
+
payload,
|
|
432
|
+
SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY
|
|
433
|
+
)
|
|
434
|
+
) {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
const manifest = getSubagentResumeManifest(payload);
|
|
438
|
+
if (manifest == null) {
|
|
439
|
+
throw new Error('Invalid subagent resume manifest.');
|
|
440
|
+
}
|
|
441
|
+
return manifest;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function canAttachSubagentResumeManifestInline(
|
|
445
|
+
payload: unknown
|
|
446
|
+
): payload is Record<string, unknown> {
|
|
447
|
+
if (
|
|
448
|
+
payload == null ||
|
|
449
|
+
typeof payload !== 'object' ||
|
|
450
|
+
Array.isArray(payload) ||
|
|
451
|
+
Object.getPrototypeOf(payload) !== Object.prototype
|
|
452
|
+
) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
return SUBAGENT_RESUME_PRIVATE_PAYLOAD_KEYS.every(
|
|
456
|
+
(key) => !Object.prototype.hasOwnProperty.call(payload, key)
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function isInlineSubagentResumePayload(
|
|
461
|
+
payload: unknown
|
|
462
|
+
): payload is PayloadWithSubagentResumeManifest & Record<string, unknown> {
|
|
463
|
+
return (
|
|
464
|
+
payload != null &&
|
|
465
|
+
typeof payload === 'object' &&
|
|
466
|
+
!Array.isArray(payload) &&
|
|
467
|
+
Object.getPrototypeOf(payload) === Object.prototype &&
|
|
468
|
+
!Object.prototype.hasOwnProperty.call(
|
|
469
|
+
payload,
|
|
470
|
+
SUBAGENT_RESUME_WRAPPER_VERSION_KEY
|
|
471
|
+
) &&
|
|
472
|
+
!Object.prototype.hasOwnProperty.call(
|
|
473
|
+
payload,
|
|
474
|
+
SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY
|
|
475
|
+
) &&
|
|
476
|
+
getSubagentResumeManifest(payload) != null
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function attachSubagentResumeManifest(
|
|
481
|
+
payload: unknown,
|
|
482
|
+
manifest: SubagentResumeManifest
|
|
483
|
+
): object {
|
|
484
|
+
if (
|
|
485
|
+
isWrappedSubagentResumePayload(payload) ||
|
|
486
|
+
isInlineSubagentResumePayload(payload)
|
|
487
|
+
) {
|
|
488
|
+
return {
|
|
489
|
+
...payload,
|
|
490
|
+
[SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY]: manifest,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (!canAttachSubagentResumeManifestInline(payload)) {
|
|
494
|
+
return {
|
|
495
|
+
[SUBAGENT_RESUME_WRAPPER_VERSION_KEY]: 1,
|
|
496
|
+
[SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY]: payload,
|
|
497
|
+
[SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY]: manifest,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
...payload,
|
|
502
|
+
[SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY]: manifest,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function stripSubagentResumeManifest(payload: unknown): unknown {
|
|
507
|
+
if (isWrappedSubagentResumePayload(payload)) {
|
|
508
|
+
return payload[SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY];
|
|
509
|
+
}
|
|
510
|
+
if (
|
|
511
|
+
payload == null ||
|
|
512
|
+
typeof payload !== 'object' ||
|
|
513
|
+
getSubagentResumeManifest(payload) == null
|
|
514
|
+
) {
|
|
515
|
+
return payload;
|
|
516
|
+
}
|
|
517
|
+
return Object.fromEntries(
|
|
518
|
+
Object.entries(payload).filter(
|
|
519
|
+
([key]) => key !== SUBAGENT_RESUME_MANIFEST_PAYLOAD_KEY
|
|
520
|
+
)
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function isWrappedSubagentResumePayload(
|
|
525
|
+
payload: unknown
|
|
526
|
+
): payload is WrappedSubagentResumePayload {
|
|
527
|
+
if (
|
|
528
|
+
payload == null ||
|
|
529
|
+
typeof payload !== 'object' ||
|
|
530
|
+
Array.isArray(payload) ||
|
|
531
|
+
Object.getPrototypeOf(payload) !== Object.prototype ||
|
|
532
|
+
Object.keys(payload).length !== SUBAGENT_RESUME_PRIVATE_PAYLOAD_KEYS.length
|
|
533
|
+
) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
const wrapper = payload as Partial<WrappedSubagentResumePayload>;
|
|
537
|
+
return (
|
|
538
|
+
wrapper[SUBAGENT_RESUME_WRAPPER_VERSION_KEY] === 1 &&
|
|
539
|
+
Object.prototype.hasOwnProperty.call(
|
|
540
|
+
wrapper,
|
|
541
|
+
SUBAGENT_RESUME_WRAPPED_PAYLOAD_KEY
|
|
542
|
+
) &&
|
|
543
|
+
getSubagentResumeManifest(wrapper) != null
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export const SUBAGENT_REPLAY_CONTROLLER = Symbol.for(
|
|
548
|
+
'@librechat/agents/subagent-replay-controller'
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
export type SettledSubagentToolOutput = {
|
|
552
|
+
output: ToolMessage;
|
|
553
|
+
additionalContexts: string[];
|
|
554
|
+
resolvedArgs?: Record<string, unknown>;
|
|
555
|
+
referenceContent?: string;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
export interface SubagentReplayController {
|
|
559
|
+
getResumeManifest?(
|
|
560
|
+
parentToolCallIds?: ReadonlySet<string>
|
|
561
|
+
): Promise<SubagentResumeManifest | undefined>;
|
|
562
|
+
getSettledOutput(
|
|
563
|
+
call: ToolCall,
|
|
564
|
+
config: RunnableConfig
|
|
565
|
+
): Promise<SettledSubagentToolOutput | undefined>;
|
|
566
|
+
persistSettledOutput(
|
|
567
|
+
call: ToolCall,
|
|
568
|
+
config: RunnableConfig,
|
|
569
|
+
settled: SettledSubagentToolOutput
|
|
570
|
+
): Promise<void>;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export type ReplayableSubagentTool = {
|
|
574
|
+
[SUBAGENT_REPLAY_CONTROLLER]?: SubagentReplayController;
|
|
575
|
+
};
|
|
@@ -41,7 +41,11 @@ function parseStringifiedArgsObject(
|
|
|
41
41
|
}
|
|
42
42
|
try {
|
|
43
43
|
const parsed = JSON.parse(value) as unknown;
|
|
44
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
parsed != null &&
|
|
46
|
+
typeof parsed === 'object' &&
|
|
47
|
+
!Array.isArray(parsed)
|
|
48
|
+
) {
|
|
45
49
|
return parsed as Record<string, unknown>;
|
|
46
50
|
}
|
|
47
51
|
} catch {
|
|
@@ -91,6 +95,12 @@ export type ToolOutputReferenceRegistryOptions = {
|
|
|
91
95
|
maxActiveRuns?: number;
|
|
92
96
|
};
|
|
93
97
|
|
|
98
|
+
export interface ToolOutputReferenceState {
|
|
99
|
+
entries: Array<{ key: string; value: string }>;
|
|
100
|
+
turnCounter: number;
|
|
101
|
+
warnedNonStringTools: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
94
104
|
/**
|
|
95
105
|
* Result of resolving placeholders in tool args.
|
|
96
106
|
*/
|
|
@@ -397,6 +407,35 @@ export class ToolOutputReferenceRegistry {
|
|
|
397
407
|
};
|
|
398
408
|
}
|
|
399
409
|
|
|
410
|
+
/** Captures the checkpoint-safe state needed to resume one run bucket. */
|
|
411
|
+
snapshotState(runId: string | undefined): ToolOutputReferenceState {
|
|
412
|
+
const bucket = this.runStates.get(this.keyFor(runId));
|
|
413
|
+
return {
|
|
414
|
+
entries: [...(bucket?.entries ?? EMPTY_ENTRIES)].map(([key, value]) => ({
|
|
415
|
+
key,
|
|
416
|
+
value,
|
|
417
|
+
})),
|
|
418
|
+
turnCounter: bucket?.turnCounter ?? 0,
|
|
419
|
+
warnedNonStringTools: [...(bucket?.warnedNonStringTools ?? [])],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Restores a checkpointed run bucket under the current resume scope. */
|
|
424
|
+
restoreState(
|
|
425
|
+
runId: string | undefined,
|
|
426
|
+
state: ToolOutputReferenceState
|
|
427
|
+
): void {
|
|
428
|
+
this.releaseRun(runId);
|
|
429
|
+
const bucket = this.getOrCreate(runId);
|
|
430
|
+
for (const { key, value } of state.entries) {
|
|
431
|
+
this.set(runId, key, value);
|
|
432
|
+
}
|
|
433
|
+
bucket.turnCounter = state.turnCounter;
|
|
434
|
+
for (const toolName of state.warnedNonStringTools) {
|
|
435
|
+
bucket.warnedNonStringTools.add(toolName);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
400
439
|
private resolveAgainst<T>(
|
|
401
440
|
entries: ReadonlyMap<string, string>,
|
|
402
441
|
args: T,
|
package/src/types/graph.ts
CHANGED
|
@@ -659,11 +659,11 @@ export interface AgentInputs {
|
|
|
659
659
|
* raise a LangGraph `interrupt()` (e.g. a tool built on `askUserQuestion()`) —
|
|
660
660
|
* the host-side event handler runs outside the graph task, where `interrupt()`
|
|
661
661
|
* throws. Do NOT also list these tools in `toolDefinitions` (they would be bound
|
|
662
|
-
* twice). NOT inherited by SELF-SPAWNED subagent children
|
|
663
|
-
* shallow
|
|
664
|
-
*
|
|
665
|
-
* `
|
|
666
|
-
*
|
|
662
|
+
* twice). NOT inherited by SELF-SPAWNED subagent children: `buildChildInputs`
|
|
663
|
+
* scrubs the shallow-spread parent copy so parent-scoped direct tools are not
|
|
664
|
+
* exposed to a child implicitly. An EXPLICIT child config that lists its own
|
|
665
|
+
* `graphTools` keeps them; with HITL enabled, those tools share the parent's
|
|
666
|
+
* checkpointer and may pause and resume inside the child graph.
|
|
667
667
|
*
|
|
668
668
|
* Deliberately `GenericTool[]`, not `GraphTools`: the wider union admits
|
|
669
669
|
* schema-only shapes (OpenAI `BindToolsInput`, Google tool objects) that
|
package/src/types/hitl.ts
CHANGED
|
@@ -101,6 +101,18 @@ export type ToolApprovalDecisionMap = Record<string, ToolApprovalDecision>;
|
|
|
101
101
|
*/
|
|
102
102
|
export type HumanInterruptType = 'tool_approval' | 'ask_user_question';
|
|
103
103
|
|
|
104
|
+
/** Identifies an interrupt that originated inside a checkpointed subagent. */
|
|
105
|
+
export interface SubagentInterruptScope {
|
|
106
|
+
/** Child execution run id used by subagent update and usage events. */
|
|
107
|
+
run_id: string;
|
|
108
|
+
/** Child agent id that owns the interrupted tool call. */
|
|
109
|
+
agent_id: string;
|
|
110
|
+
/** Configured subagent type selected by the parent tool call. */
|
|
111
|
+
subagent_type: string;
|
|
112
|
+
/** Parent `subagent` tool call that launched this child. */
|
|
113
|
+
parent_tool_call_id?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
/**
|
|
105
117
|
* Structured payload the SDK passes to `interrupt()` when one or more
|
|
106
118
|
* pending tool calls require host approval. All `ask`-decision tool calls
|
|
@@ -114,6 +126,10 @@ export interface ToolApprovalInterruptPayload {
|
|
|
114
126
|
type: 'tool_approval';
|
|
115
127
|
action_requests: ToolApprovalRequest[];
|
|
116
128
|
review_configs: ToolApprovalReviewConfig[];
|
|
129
|
+
/** Hook-registry session whose policy raised this interrupt. */
|
|
130
|
+
hook_session_id?: string;
|
|
131
|
+
/** Present when the approval request was bridged from a child graph. */
|
|
132
|
+
subagent?: SubagentInterruptScope;
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
/**
|