@librechat/agents 3.8.3 → 3.8.5
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/tools/ToolNode.cjs +7 -6
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolBatchReplay.cjs +64 -12
- package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +8 -7
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolBatchReplay.mjs +63 -12
- package/dist/esm/tools/toolBatchReplay.mjs.map +1 -1
- package/dist/types/tools/toolBatchReplay.d.ts +6 -3
- package/package.json +1 -1
- package/src/tools/ToolNode.ts +18 -8
- package/src/tools/subagent/SubagentExecutor.ts +12 -3
- package/src/tools/toolBatchReplay.ts +121 -18
|
@@ -46,6 +46,8 @@ export interface ToolBatchReplayRecord {
|
|
|
46
46
|
referenceState?: ToolOutputReferenceState;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export type ToolReplayResumeStatus = 'active' | 'stale' | 'unverifiable';
|
|
50
|
+
|
|
49
51
|
interface ToolBatchReplayState {
|
|
50
52
|
version: 1;
|
|
51
53
|
approvalOwner?: string;
|
|
@@ -62,6 +64,44 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
62
64
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
function bindLegacyReplayOwner(
|
|
68
|
+
owner: string,
|
|
69
|
+
configurable: Record<string, unknown>
|
|
70
|
+
): string {
|
|
71
|
+
let identity: unknown;
|
|
72
|
+
try {
|
|
73
|
+
identity = JSON.parse(owner);
|
|
74
|
+
} catch {
|
|
75
|
+
return owner;
|
|
76
|
+
}
|
|
77
|
+
if (!Array.isArray(identity) || identity.length !== 3) {
|
|
78
|
+
return owner;
|
|
79
|
+
}
|
|
80
|
+
return JSON.stringify([
|
|
81
|
+
...identity,
|
|
82
|
+
typeof configurable.user_id === 'string' ? configurable.user_id : '',
|
|
83
|
+
typeof configurable.thread_id === 'string' ? configurable.thread_id : '',
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function bindLegacyReplayState(
|
|
88
|
+
state: ToolBatchReplayState | undefined,
|
|
89
|
+
configurable: Record<string, unknown>
|
|
90
|
+
): ToolBatchReplayState | undefined {
|
|
91
|
+
if (state == null) return undefined;
|
|
92
|
+
return {
|
|
93
|
+
...state,
|
|
94
|
+
approvalOwner:
|
|
95
|
+
state.approvalOwner == null
|
|
96
|
+
? undefined
|
|
97
|
+
: bindLegacyReplayOwner(state.approvalOwner, configurable),
|
|
98
|
+
records: state.records.map((record) => ({
|
|
99
|
+
...record,
|
|
100
|
+
owner: bindLegacyReplayOwner(record.owner, configurable),
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
65
105
|
/** The only host-to-runtime entry point for checkpoint-owned tool replay state. */
|
|
66
106
|
export function restoreToolReplayConfig(
|
|
67
107
|
configurable: Record<string, unknown>,
|
|
@@ -70,7 +110,10 @@ export function restoreToolReplayConfig(
|
|
|
70
110
|
): void {
|
|
71
111
|
delete configurable[TOOL_BATCH_REPLAY_KEY];
|
|
72
112
|
delete configurable[TOOL_APPROVAL_REVIEW_CONFIG_KEY];
|
|
73
|
-
const state =
|
|
113
|
+
const state = bindLegacyReplayState(
|
|
114
|
+
getToolBatchReplayState(payload),
|
|
115
|
+
configurable
|
|
116
|
+
);
|
|
74
117
|
const publicPayload = getPublicToolInterruptPayload(payload);
|
|
75
118
|
const review = createToolApprovalReviewEvidence(
|
|
76
119
|
interruptId,
|
|
@@ -95,23 +138,40 @@ export function restoreToolReplayConfig(
|
|
|
95
138
|
* steps, so the presence of replay evidence alone does not imply a resume.
|
|
96
139
|
* A parent replaying a checkpointed child has no local resume value.
|
|
97
140
|
*/
|
|
98
|
-
export function
|
|
141
|
+
export function getToolReplayResumeStatus(
|
|
99
142
|
config: RunnableConfig,
|
|
100
143
|
interruptId: string | undefined,
|
|
101
144
|
isChildReplay: boolean
|
|
102
|
-
):
|
|
103
|
-
const resumeMap:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return
|
|
145
|
+
): ToolReplayResumeStatus {
|
|
146
|
+
const resumeMap: unknown = config.configurable?.__pregel_resume_map;
|
|
147
|
+
const scratchpad: unknown = config.configurable?.__pregel_scratchpad;
|
|
148
|
+
if (!isRecord(scratchpad) || !Array.isArray(scratchpad.resume)) {
|
|
149
|
+
return 'unverifiable';
|
|
107
150
|
}
|
|
108
|
-
|
|
109
|
-
|
|
151
|
+
/**
|
|
152
|
+
* LangGraph supports both an interrupt-id resume map and a task-local
|
|
153
|
+
* `nullResume` value. The latter is already scoped to the checkpoint task,
|
|
154
|
+
* so it remains a supported, verifiable resume without a map entry.
|
|
155
|
+
*/
|
|
156
|
+
if (scratchpad.nullResume !== undefined) {
|
|
157
|
+
return 'active';
|
|
110
158
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
159
|
+
const hasResume = scratchpad.resume.length > 0;
|
|
160
|
+
if (!isRecord(resumeMap)) {
|
|
161
|
+
return hasResume ? 'unverifiable' : 'stale';
|
|
162
|
+
}
|
|
163
|
+
if (interruptId == null) {
|
|
164
|
+
/** Legacy direct graph callers do not restore the ID into replay state. */
|
|
165
|
+
if (!hasResume) return 'stale';
|
|
166
|
+
return Object.keys(resumeMap).length === 1 ? 'active' : 'unverifiable';
|
|
167
|
+
}
|
|
168
|
+
if (!Object.prototype.hasOwnProperty.call(resumeMap, interruptId)) {
|
|
169
|
+
return hasResume ? 'unverifiable' : 'stale';
|
|
170
|
+
}
|
|
171
|
+
if (isChildReplay) {
|
|
172
|
+
return 'active';
|
|
173
|
+
}
|
|
174
|
+
return hasResume ? 'active' : 'stale';
|
|
115
175
|
}
|
|
116
176
|
|
|
117
177
|
export function getToolBatchReplayOwner(
|
|
@@ -124,9 +184,39 @@ export function getToolBatchReplayOwner(
|
|
|
124
184
|
? (config.configurable?.checkpoint_ns ?? '')
|
|
125
185
|
: '',
|
|
126
186
|
agentId,
|
|
187
|
+
typeof config.configurable?.user_id === 'string'
|
|
188
|
+
? config.configurable.user_id
|
|
189
|
+
: '',
|
|
190
|
+
typeof config.configurable?.thread_id === 'string'
|
|
191
|
+
? config.configurable.thread_id
|
|
192
|
+
: '',
|
|
127
193
|
]);
|
|
128
194
|
}
|
|
129
195
|
|
|
196
|
+
/** Remove stale authorization while retaining only settled results for this batch. */
|
|
197
|
+
export function clearStaleToolApprovalConfig(
|
|
198
|
+
configurable: Record<string, unknown>,
|
|
199
|
+
owner: string,
|
|
200
|
+
batch: string | undefined
|
|
201
|
+
): void {
|
|
202
|
+
delete configurable[TOOL_APPROVAL_REVIEW_CONFIG_KEY];
|
|
203
|
+
const state = getToolBatchReplayState(configurable);
|
|
204
|
+
const records = batch == null
|
|
205
|
+
? []
|
|
206
|
+
: (state?.records.filter(
|
|
207
|
+
(record) => record.owner === owner && record.batch === batch
|
|
208
|
+
) ?? []);
|
|
209
|
+
if (state == null || records.length === 0) {
|
|
210
|
+
delete configurable[TOOL_BATCH_REPLAY_KEY];
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
configurable[TOOL_BATCH_REPLAY_KEY] = {
|
|
214
|
+
version: 1,
|
|
215
|
+
records,
|
|
216
|
+
wrappedPayload: state.wrappedPayload,
|
|
217
|
+
} satisfies ToolBatchReplayState;
|
|
218
|
+
}
|
|
219
|
+
|
|
130
220
|
export function getToolBatchReplayScope(
|
|
131
221
|
config: RunnableConfig
|
|
132
222
|
): string | undefined {
|
|
@@ -145,7 +235,9 @@ export function isChildToolReplayOwner(
|
|
|
145
235
|
const manifest = requireValidSubagentResumeManifest(config.configurable);
|
|
146
236
|
if (manifest == null || trustedParentCallIds.size === 0) return false;
|
|
147
237
|
const identity: unknown = JSON.parse(owner);
|
|
148
|
-
if (!Array.isArray(identity) ||
|
|
238
|
+
if (!Array.isArray(identity) ||
|
|
239
|
+
(identity.length !== 3 && identity.length !== 5) ||
|
|
240
|
+
identity[1] !== '') return false;
|
|
149
241
|
const pending = manifest.executions.filter((execution) => trustedParentCallIds.has(execution.parentToolCallId));
|
|
150
242
|
while (pending.length > 0) {
|
|
151
243
|
const execution = pending.pop()!;
|
|
@@ -159,14 +251,19 @@ export function isChildToolReplayOwner(
|
|
|
159
251
|
export function rebindToolBatchReplayScope(
|
|
160
252
|
configurable: Record<string, unknown>,
|
|
161
253
|
sourceScope: string,
|
|
162
|
-
destinationScope: string
|
|
254
|
+
destinationScope: string,
|
|
255
|
+
destinationThreadId?: string
|
|
163
256
|
): void {
|
|
164
257
|
const rebind = (key: string): string => {
|
|
165
258
|
const parts: unknown = JSON.parse(key);
|
|
166
259
|
if (!Array.isArray(parts) || parts[0] !== sourceScope) {
|
|
167
260
|
return key;
|
|
168
261
|
}
|
|
169
|
-
|
|
262
|
+
const rebound = [destinationScope, ...parts.slice(1)];
|
|
263
|
+
if (rebound.length === 5 && destinationThreadId != null) {
|
|
264
|
+
rebound[4] = destinationThreadId;
|
|
265
|
+
}
|
|
266
|
+
return JSON.stringify(rebound);
|
|
170
267
|
};
|
|
171
268
|
const config = { configurable };
|
|
172
269
|
const review = getToolApprovalReviewEvidence(config);
|
|
@@ -195,7 +292,8 @@ export function rebindToolBatchReplayScope(
|
|
|
195
292
|
export function rebindToolBatchReplayPayload(
|
|
196
293
|
payload: unknown,
|
|
197
294
|
sourceScope: string,
|
|
198
|
-
destinationScope: string
|
|
295
|
+
destinationScope: string,
|
|
296
|
+
destinationThreadId?: string
|
|
199
297
|
): unknown {
|
|
200
298
|
const state = getToolBatchReplayState(payload);
|
|
201
299
|
const value = stripRunStepResumeState(payload);
|
|
@@ -203,7 +301,12 @@ export function rebindToolBatchReplayPayload(
|
|
|
203
301
|
return payload;
|
|
204
302
|
}
|
|
205
303
|
const configurable = { [TOOL_BATCH_REPLAY_KEY]: state };
|
|
206
|
-
rebindToolBatchReplayScope(
|
|
304
|
+
rebindToolBatchReplayScope(
|
|
305
|
+
configurable,
|
|
306
|
+
sourceScope,
|
|
307
|
+
destinationScope,
|
|
308
|
+
destinationThreadId
|
|
309
|
+
);
|
|
207
310
|
const rebound = {
|
|
208
311
|
...value,
|
|
209
312
|
[TOOL_BATCH_REPLAY_KEY]: configurable[TOOL_BATCH_REPLAY_KEY],
|