@librechat/agents 3.3.13 → 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.
Files changed (192) hide show
  1. package/dist/cjs/graphs/Graph.cjs +121 -27
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/HookRegistry.cjs +83 -0
  4. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  5. package/dist/cjs/hooks/executeHooks.cjs +60 -14
  6. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  7. package/dist/cjs/hooks/index.cjs.map +1 -1
  8. package/dist/cjs/hooks/types.cjs +2 -0
  9. package/dist/cjs/hooks/types.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/index.cjs +35 -206
  11. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  12. package/dist/cjs/llm/bedrock/index.cjs +121 -241
  13. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  14. package/dist/cjs/llm/google/index.cjs +19 -12
  15. package/dist/cjs/llm/google/index.cjs.map +1 -1
  16. package/dist/cjs/llm/init.cjs +3 -3
  17. package/dist/cjs/llm/invoke.cjs +2 -2
  18. package/dist/cjs/llm/mistral/index.cjs +26 -0
  19. package/dist/cjs/llm/mistral/index.cjs.map +1 -0
  20. package/dist/cjs/llm/openai/index.cjs +82 -80
  21. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  23. package/dist/cjs/llm/providers.cjs +19 -19
  24. package/dist/cjs/llm/providers.cjs.map +1 -1
  25. package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
  26. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
  27. package/dist/cjs/llm/stream/smoother.cjs +369 -0
  28. package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
  29. package/dist/cjs/llm/vertexai/index.cjs +13 -1
  30. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  31. package/dist/cjs/main.cjs +25 -16
  32. package/dist/cjs/messages/format.cjs +40 -23
  33. package/dist/cjs/messages/format.cjs.map +1 -1
  34. package/dist/cjs/run.cjs +82 -15
  35. package/dist/cjs/run.cjs.map +1 -1
  36. package/dist/cjs/session/AgentSession.cjs +37 -2
  37. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  38. package/dist/cjs/stream.cjs +1 -1
  39. package/dist/cjs/summarization/node.cjs +6 -3
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  42. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  43. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  44. package/dist/cjs/tools/ToolNode.cjs +316 -94
  45. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  46. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +664 -80
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  49. package/dist/cjs/tools/subagent/SubagentReplay.cjs +175 -0
  50. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
  51. package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
  52. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  53. package/dist/cjs/utils/index.cjs +2 -2
  54. package/dist/cjs/utils/tokens.cjs +76 -29
  55. package/dist/cjs/utils/tokens.cjs.map +1 -1
  56. package/dist/esm/graphs/Graph.mjs +121 -27
  57. package/dist/esm/graphs/Graph.mjs.map +1 -1
  58. package/dist/esm/hooks/HookRegistry.mjs +83 -0
  59. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  60. package/dist/esm/hooks/executeHooks.mjs +60 -14
  61. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  62. package/dist/esm/hooks/index.mjs.map +1 -1
  63. package/dist/esm/hooks/types.mjs +2 -1
  64. package/dist/esm/hooks/types.mjs.map +1 -1
  65. package/dist/esm/llm/anthropic/index.mjs +34 -205
  66. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  67. package/dist/esm/llm/bedrock/index.mjs +120 -240
  68. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  69. package/dist/esm/llm/google/index.mjs +19 -12
  70. package/dist/esm/llm/google/index.mjs.map +1 -1
  71. package/dist/esm/llm/init.mjs +1 -1
  72. package/dist/esm/llm/invoke.mjs +2 -2
  73. package/dist/esm/llm/mistral/index.mjs +26 -0
  74. package/dist/esm/llm/mistral/index.mjs.map +1 -0
  75. package/dist/esm/llm/openai/index.mjs +82 -80
  76. package/dist/esm/llm/openai/index.mjs.map +1 -1
  77. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  78. package/dist/esm/llm/providers.mjs +3 -3
  79. package/dist/esm/llm/providers.mjs.map +1 -1
  80. package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
  81. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
  82. package/dist/esm/llm/stream/smoother.mjs +365 -0
  83. package/dist/esm/llm/stream/smoother.mjs.map +1 -0
  84. package/dist/esm/llm/vertexai/index.mjs +13 -1
  85. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  86. package/dist/esm/main.mjs +13 -11
  87. package/dist/esm/messages/format.mjs +40 -23
  88. package/dist/esm/messages/format.mjs.map +1 -1
  89. package/dist/esm/run.mjs +83 -16
  90. package/dist/esm/run.mjs.map +1 -1
  91. package/dist/esm/session/AgentSession.mjs +37 -2
  92. package/dist/esm/session/AgentSession.mjs.map +1 -1
  93. package/dist/esm/stream.mjs +1 -1
  94. package/dist/esm/summarization/node.mjs +6 -3
  95. package/dist/esm/summarization/node.mjs.map +1 -1
  96. package/dist/esm/tools/BashExecutor.mjs +1 -1
  97. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  98. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  99. package/dist/esm/tools/ToolNode.mjs +317 -95
  100. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  101. package/dist/esm/tools/ToolSearch.mjs +1 -1
  102. package/dist/esm/tools/subagent/SubagentExecutor.mjs +665 -81
  103. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  104. package/dist/esm/tools/subagent/SubagentReplay.mjs +168 -0
  105. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
  106. package/dist/esm/tools/toolOutputReferences.mjs +20 -0
  107. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  108. package/dist/esm/utils/index.mjs +2 -2
  109. package/dist/esm/utils/tokens.mjs +76 -30
  110. package/dist/esm/utils/tokens.mjs.map +1 -1
  111. package/dist/types/graphs/Graph.d.ts +19 -1
  112. package/dist/types/hooks/HookRegistry.d.ts +10 -1
  113. package/dist/types/hooks/executeHooks.d.ts +5 -1
  114. package/dist/types/hooks/index.d.ts +2 -2
  115. package/dist/types/hooks/types.d.ts +12 -0
  116. package/dist/types/index.d.ts +3 -0
  117. package/dist/types/llm/google/index.d.ts +2 -0
  118. package/dist/types/llm/mistral/index.d.ts +11 -0
  119. package/dist/types/llm/openai/index.d.ts +20 -4
  120. package/dist/types/llm/openrouter/index.d.ts +4 -1
  121. package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
  122. package/dist/types/llm/stream/smoother.d.ts +95 -0
  123. package/dist/types/llm/vertexai/index.d.ts +2 -0
  124. package/dist/types/run.d.ts +4 -1
  125. package/dist/types/session/AgentSession.d.ts +1 -0
  126. package/dist/types/tools/ToolNode.d.ts +23 -15
  127. package/dist/types/tools/subagent/SubagentExecutor.d.ts +49 -7
  128. package/dist/types/tools/subagent/SubagentReplay.d.ts +81 -0
  129. package/dist/types/tools/toolOutputReferences.d.ts +12 -0
  130. package/dist/types/types/graph.d.ts +5 -5
  131. package/dist/types/types/hitl.d.ts +15 -0
  132. package/dist/types/types/llm.d.ts +21 -14
  133. package/dist/types/utils/tokens.d.ts +10 -0
  134. package/package.json +6 -2
  135. package/src/graphs/Graph.ts +257 -52
  136. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
  137. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
  138. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
  139. package/src/hooks/HookRegistry.ts +151 -1
  140. package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
  141. package/src/hooks/__tests__/executeHooks.test.ts +160 -0
  142. package/src/hooks/executeHooks.ts +133 -15
  143. package/src/hooks/index.ts +3 -1
  144. package/src/hooks/types.ts +16 -0
  145. package/src/index.ts +8 -0
  146. package/src/llm/anthropic/index.ts +85 -354
  147. package/src/llm/bedrock/index.ts +174 -390
  148. package/src/llm/bedrock/llm.spec.ts +2 -0
  149. package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
  150. package/src/llm/custom-chat-models.smoke.test.ts +16 -1
  151. package/src/llm/google/index.ts +17 -18
  152. package/src/llm/google/streamSmoothing.test.ts +121 -0
  153. package/src/llm/mistral/index.ts +33 -0
  154. package/src/llm/mistral/streamSmoothing.test.ts +97 -0
  155. package/src/llm/openai/deepseek.test.ts +56 -0
  156. package/src/llm/openai/index.ts +119 -126
  157. package/src/llm/openrouter/index.ts +4 -1
  158. package/src/llm/providers.ts +3 -3
  159. package/src/llm/stream/chunkAdapters.test.ts +202 -0
  160. package/src/llm/stream/chunkAdapters.ts +317 -0
  161. package/src/llm/stream/reassembly.test.ts +241 -0
  162. package/src/llm/stream/smoother.bench.test.ts +155 -0
  163. package/src/llm/stream/smoother.test.ts +519 -0
  164. package/src/llm/stream/smoother.ts +574 -0
  165. package/src/llm/vertexai/index.ts +17 -1
  166. package/src/llm/vertexai/streamSmoothing.test.ts +109 -0
  167. package/src/messages/format.ts +54 -26
  168. package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
  169. package/src/messages/formatAgentMessages.steer.test.ts +16 -11
  170. package/src/messages/formatAgentMessages.test.ts +7 -2
  171. package/src/run.ts +164 -18
  172. package/src/session/AgentSession.ts +52 -2
  173. package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
  174. package/src/specs/subagent.test.ts +44 -0
  175. package/src/specs/tokens.test.ts +159 -14
  176. package/src/summarization/__tests__/node.test.ts +32 -14
  177. package/src/summarization/node.ts +15 -11
  178. package/src/tools/ToolNode.ts +631 -157
  179. package/src/tools/__tests__/SubagentExecutor.test.ts +590 -7
  180. package/src/tools/__tests__/SubagentReplay.test.ts +300 -0
  181. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
  182. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +486 -2
  183. package/src/tools/__tests__/hitl.test.ts +234 -5
  184. package/src/tools/__tests__/subagentHooks.test.ts +1040 -62
  185. package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
  186. package/src/tools/subagent/SubagentExecutor.ts +1438 -136
  187. package/src/tools/subagent/SubagentReplay.ts +575 -0
  188. package/src/tools/toolOutputReferences.ts +40 -1
  189. package/src/types/graph.ts +5 -5
  190. package/src/types/hitl.ts +16 -0
  191. package/src/types/llm.ts +53 -36
  192. package/src/utils/tokens.ts +115 -30
@@ -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 (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) {
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,
@@ -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 (their config is a
663
- * shallow spread of the parent's inputs, and child graphs compile without a
664
- * checkpointer, so an interrupt-capable tool could never pause there)
665
- * `buildChildInputs` scrubs the inherited copy; an EXPLICIT child config that
666
- * lists its own `graphTools` keeps them.
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
  /**