@librechat/agents 3.2.61 → 3.2.63
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/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +6 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +32 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +6 -0
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +12 -0
- package/dist/cjs/hooks/index.cjs.map +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +62 -1
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +266 -43
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +274 -7
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/streamMetadata.cjs +69 -0
- package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -0
- package/dist/cjs/llm/openrouter/index.cjs +5 -6
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +6 -1
- package/dist/cjs/messages/format.cjs +61 -0
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +31 -19
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/stream.cjs +13 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +76 -11
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +2 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +111 -0
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +6 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +32 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +6 -0
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +12 -1
- package/dist/esm/hooks/index.mjs.map +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +62 -1
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +266 -43
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +274 -7
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/streamMetadata.mjs +69 -0
- package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -0
- package/dist/esm/llm/openrouter/index.mjs +5 -6
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/messages/format.mjs +61 -0
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +31 -19
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/stream.mjs +13 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +76 -11
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +2 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +108 -1
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +3 -1
- package/dist/types/graphs/Graph.d.ts +3 -1
- package/dist/types/hooks/HookRegistry.d.ts +10 -0
- package/dist/types/hooks/index.d.ts +7 -0
- package/dist/types/hooks/types.d.ts +18 -1
- package/dist/types/llm/bedrock/index.d.ts +5 -0
- package/dist/types/llm/openai/index.d.ts +19 -0
- package/dist/types/llm/openai/streamMetadata.d.ts +16 -0
- package/dist/types/messages/prune.d.ts +4 -3
- package/dist/types/tools/ToolNode.d.ts +1 -0
- package/dist/types/types/graph.d.ts +10 -0
- package/dist/types/types/llm.d.ts +12 -2
- package/dist/types/types/tools.d.ts +1 -1
- package/dist/types/utils/tokens.d.ts +30 -0
- package/package.json +6 -6
- package/src/__tests__/stream.eagerEventExecution.test.ts +100 -2
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +12 -4
- package/src/hooks/HookRegistry.ts +45 -0
- package/src/hooks/__tests__/HookRegistry.test.ts +48 -0
- package/src/hooks/__tests__/executeHooks.test.ts +85 -2
- package/src/hooks/executeHooks.ts +15 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/types.ts +18 -1
- package/src/langfuse.ts +134 -1
- package/src/llm/bedrock/index.ts +434 -83
- package/src/llm/bedrock/streamSealDispatch.test.ts +97 -0
- package/src/llm/custom-chat-models.smoke.test.ts +7 -0
- package/src/llm/openai/index.ts +604 -6
- package/src/llm/openai/managedRequests.test.ts +182 -0
- package/src/llm/openai/streamMetadata.spec.ts +86 -0
- package/src/llm/openai/streamMetadata.ts +95 -0
- package/src/llm/openai/streamMetadataDedup.spec.ts +166 -0
- package/src/llm/openrouter/index.ts +9 -5
- package/src/messages/format.ts +96 -3
- package/src/messages/formatAgentMessages.steer.test.ts +326 -0
- package/src/messages/labelContentByAgent.test.ts +75 -0
- package/src/messages/prune.ts +56 -30
- package/src/specs/anthropic.simple.test.ts +4 -2
- package/src/specs/cache.simple.test.ts +17 -4
- package/src/specs/langfuse-callbacks.test.ts +61 -0
- package/src/specs/openai.simple.test.ts +4 -2
- package/src/specs/spec.utils.ts +12 -0
- package/src/specs/summarization.test.ts +9 -13
- package/src/specs/token-accounting-pipeline.test.ts +130 -4
- package/src/specs/tokens.test.ts +214 -0
- package/src/stream.ts +22 -3
- package/src/tools/ToolNode.ts +112 -13
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +554 -0
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +49 -2
- package/src/tools/__tests__/hitl.test.ts +112 -0
- package/src/tools/__tests__/subagentHooks.test.ts +124 -0
- package/src/tools/subagent/SubagentExecutor.ts +3 -0
- package/src/types/graph.ts +10 -0
- package/src/types/llm.ts +12 -2
- package/src/types/tools.ts +1 -1
- package/src/utils/tokens.ts +181 -3
package/src/tools/ToolNode.ts
CHANGED
|
@@ -822,10 +822,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
822
822
|
private recordEventToolPlanningTurn(
|
|
823
823
|
toolName: string,
|
|
824
824
|
turn: number,
|
|
825
|
-
callId?: string
|
|
825
|
+
callId?: string,
|
|
826
|
+
runId?: string
|
|
826
827
|
): void {
|
|
827
828
|
this.recordToolUsageTurn(toolName, turn, callId);
|
|
828
|
-
if (this.canConsumeEagerEventExecution()) {
|
|
829
|
+
if (this.canConsumeEagerEventExecution(runId)) {
|
|
829
830
|
this.eagerEventToolUsageCount?.set(
|
|
830
831
|
toolName,
|
|
831
832
|
Math.max(this.eagerEventToolUsageCount.get(toolName) ?? 0, turn + 1)
|
|
@@ -2091,6 +2092,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2091
2092
|
const postToolBatchEntryByCallId = new Map<string, PostToolBatchEntry>();
|
|
2092
2093
|
const HOOK_FALLBACK: AggregatedHookResult = Object.freeze({
|
|
2093
2094
|
additionalContexts: [] as string[],
|
|
2095
|
+
injectedMessages: [] as t.InjectedMessage[],
|
|
2094
2096
|
errors: [] as string[],
|
|
2095
2097
|
});
|
|
2096
2098
|
|
|
@@ -2101,6 +2103,32 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2101
2103
|
* to defensively `?.` it across every reference inside.
|
|
2102
2104
|
*/
|
|
2103
2105
|
const hookRegistry = this.hookRegistry;
|
|
2106
|
+
/**
|
|
2107
|
+
* Pull each call's prestarted eager record BEFORE awaiting the hooks:
|
|
2108
|
+
* an async deny must never race the eager host promise into emitting a
|
|
2109
|
+
* successful completion (`dispatchEagerToolCompletions`' map-identity
|
|
2110
|
+
* check skips deleted records). Allowed entries get their record
|
|
2111
|
+
* restored in the decision loop below so consumption still works;
|
|
2112
|
+
* denied entries stay deleted. Ask/interrupt configs never have eager
|
|
2113
|
+
* records (HITL disables the reservation gate).
|
|
2114
|
+
*/
|
|
2115
|
+
const preemptedEagerRecords = new Map<
|
|
2116
|
+
string,
|
|
2117
|
+
t.EagerEventToolExecution
|
|
2118
|
+
>();
|
|
2119
|
+
if (this.eagerEventToolExecutions != null) {
|
|
2120
|
+
for (const entry of preToolCalls) {
|
|
2121
|
+
const callId = entry.call.id;
|
|
2122
|
+
if (callId == null) {
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
const record = this.eagerEventToolExecutions.get(callId);
|
|
2126
|
+
if (record != null) {
|
|
2127
|
+
preemptedEagerRecords.set(callId, record);
|
|
2128
|
+
this.eagerEventToolExecutions.delete(callId);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2104
2132
|
const preResults = await Promise.all(
|
|
2105
2133
|
preToolCalls.map((entry) =>
|
|
2106
2134
|
executeHooks({
|
|
@@ -2183,6 +2211,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2183
2211
|
contentString,
|
|
2184
2212
|
reason,
|
|
2185
2213
|
});
|
|
2214
|
+
/**
|
|
2215
|
+
* A prestarted eager execution for a now-blocked call must neither be
|
|
2216
|
+
* consumed nor emit its completion — the run reports this call as
|
|
2217
|
+
* blocked. Deleting the record makes `dispatchEagerToolCompletions`'
|
|
2218
|
+
* map-identity check skip the pending emission (same pattern as the
|
|
2219
|
+
* rejected-results cleanup below).
|
|
2220
|
+
*/
|
|
2221
|
+
this.eagerEventToolExecutions?.delete(entry.call.id!);
|
|
2186
2222
|
};
|
|
2187
2223
|
|
|
2188
2224
|
const flushDeferredBlockedSideEffects = async (): Promise<void> => {
|
|
@@ -2312,6 +2348,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2312
2348
|
if (hookResult.updatedInput != null) {
|
|
2313
2349
|
applyInputOverride(entry, hookResult.updatedInput);
|
|
2314
2350
|
}
|
|
2351
|
+
if (entry.call.id != null) {
|
|
2352
|
+
const preempted = preemptedEagerRecords.get(entry.call.id);
|
|
2353
|
+
if (preempted != null) {
|
|
2354
|
+
this.eagerEventToolExecutions?.set(entry.call.id, preempted);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2315
2357
|
approvedEntries.push(entry);
|
|
2316
2358
|
}
|
|
2317
2359
|
|
|
@@ -2592,7 +2634,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2592
2634
|
usageCount: this.toolUsageCount,
|
|
2593
2635
|
invalidArgsBehavior: 'error-result',
|
|
2594
2636
|
recordTurn: (toolName, reservedTurn, callId) => {
|
|
2595
|
-
this.recordEventToolPlanningTurn(
|
|
2637
|
+
this.recordEventToolPlanningTurn(
|
|
2638
|
+
toolName,
|
|
2639
|
+
reservedTurn,
|
|
2640
|
+
callId,
|
|
2641
|
+
runId
|
|
2642
|
+
);
|
|
2596
2643
|
},
|
|
2597
2644
|
});
|
|
2598
2645
|
if (plan == null) {
|
|
@@ -2637,7 +2684,20 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2637
2684
|
* released if the dispatch fails, letting the batch path re-emit.
|
|
2638
2685
|
*/
|
|
2639
2686
|
const canEmitEarlyCompletions =
|
|
2640
|
-
this.hookRegistry
|
|
2687
|
+
this.hookRegistry?.hasResultAlteringHooks(runId) !== true &&
|
|
2688
|
+
this.humanInTheLoop?.enabled !== true;
|
|
2689
|
+
/**
|
|
2690
|
+
* Snapshot the post-hook gates at the same instant as the early-emission
|
|
2691
|
+
* gate: a result-altering hook registered while this batch is in flight
|
|
2692
|
+
* applies from the NEXT batch. Evaluating these after results settle
|
|
2693
|
+
* would let a late hook rewrite the ToolMessage AFTER an early
|
|
2694
|
+
* completion already emitted the pre-hook output — two views of the
|
|
2695
|
+
* same call.
|
|
2696
|
+
*/
|
|
2697
|
+
const hasPostHook =
|
|
2698
|
+
this.hookRegistry?.hasHookFor('PostToolUse', runId) === true;
|
|
2699
|
+
const hasFailureHook =
|
|
2700
|
+
this.hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
|
|
2641
2701
|
const earlyCompletionDispatchedIds = new Set<string>();
|
|
2642
2702
|
const earlyCompletionDispatches: Array<Promise<void>> = [];
|
|
2643
2703
|
const dispatchRequestById = new Map(
|
|
@@ -2686,7 +2746,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2686
2746
|
const batchRequest: t.ToolExecuteBatchRequest = {
|
|
2687
2747
|
toolCalls: dispatchRequests,
|
|
2688
2748
|
userId: config.configurable?.user_id as string | undefined,
|
|
2689
|
-
|
|
2749
|
+
// Dispatch attribution, NOT the hook subagent-scope marker:
|
|
2750
|
+
// hosts key tool/credential lookup on the owning agent, and
|
|
2751
|
+
// the eager path sends `agentContext.agentId` — this must
|
|
2752
|
+
// match it at the top level too.
|
|
2753
|
+
agentId: this.executingAgentId,
|
|
2690
2754
|
configurable: config.configurable as
|
|
2691
2755
|
| Record<string, unknown>
|
|
2692
2756
|
| undefined,
|
|
@@ -2753,11 +2817,6 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2753
2817
|
|
|
2754
2818
|
this.storeCodeSessionFromResults(results, requestMap);
|
|
2755
2819
|
|
|
2756
|
-
const hasPostHook =
|
|
2757
|
-
this.hookRegistry?.hasHookFor('PostToolUse', runId) === true;
|
|
2758
|
-
const hasFailureHook =
|
|
2759
|
-
this.hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
|
|
2760
|
-
|
|
2761
2820
|
for (const result of results) {
|
|
2762
2821
|
if (result.injectedMessages && result.injectedMessages.length > 0) {
|
|
2763
2822
|
try {
|
|
@@ -2895,6 +2954,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2895
2954
|
this.maxToolResultChars
|
|
2896
2955
|
);
|
|
2897
2956
|
finalToolOutput = hookResult.updatedOutput;
|
|
2957
|
+
/**
|
|
2958
|
+
* The hook ACTUALLY rewrote this output: any completion the
|
|
2959
|
+
* stream already emitted for the consumed eager result showed
|
|
2960
|
+
* the raw content, so un-mark it and let the dispatch below
|
|
2961
|
+
* re-emit the corrected version. Hooks that merely observe or
|
|
2962
|
+
* add context leave the original emission final — no
|
|
2963
|
+
* duplicates.
|
|
2964
|
+
*/
|
|
2965
|
+
eagerCompletionDispatchedIds.delete(result.toolCallId);
|
|
2898
2966
|
}
|
|
2899
2967
|
}
|
|
2900
2968
|
|
|
@@ -2971,11 +3039,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2971
3039
|
return { toolMessages, injected };
|
|
2972
3040
|
}
|
|
2973
3041
|
|
|
2974
|
-
|
|
3042
|
+
/** Run-scoped so another run's session hooks can't flip this run's gate. */
|
|
3043
|
+
private canConsumeEagerEventExecution(runId?: string): boolean {
|
|
2975
3044
|
return (
|
|
2976
3045
|
this.eventDrivenMode &&
|
|
2977
3046
|
this.eagerEventToolExecution?.enabled === true &&
|
|
2978
|
-
this.hookRegistry
|
|
3047
|
+
this.hookRegistry?.hasResultAlteringHooks(runId) !== true &&
|
|
2979
3048
|
this.humanInTheLoop?.enabled !== true
|
|
2980
3049
|
);
|
|
2981
3050
|
}
|
|
@@ -2983,7 +3052,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2983
3052
|
private takeMatchingEagerEventExecution(
|
|
2984
3053
|
request: t.ToolCallRequest
|
|
2985
3054
|
): t.EagerEventToolExecution | undefined {
|
|
2986
|
-
|
|
3055
|
+
// Static enablement only: a stored record means the reservation-time
|
|
3056
|
+
// gates passed and the host already dispatched the execution. Re-checking
|
|
3057
|
+
// the dynamic hook gate here could decline consumption after a mid-run
|
|
3058
|
+
// registration and send the same call through normal dispatch — executing
|
|
3059
|
+
// the tool twice. PostToolUse hooks still process consumed results below.
|
|
3060
|
+
if (
|
|
3061
|
+
!this.eventDrivenMode ||
|
|
3062
|
+
this.eagerEventToolExecution?.enabled !== true
|
|
3063
|
+
) {
|
|
2987
3064
|
return undefined;
|
|
2988
3065
|
}
|
|
2989
3066
|
|
|
@@ -3098,6 +3175,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3098
3175
|
orderedBatchEntries.push(entry);
|
|
3099
3176
|
}
|
|
3100
3177
|
}
|
|
3178
|
+
const hookInjectedMessages: t.InjectedMessage[] = [];
|
|
3101
3179
|
if (
|
|
3102
3180
|
this.hookRegistry?.hasHookFor('PostToolBatch', runId) === true &&
|
|
3103
3181
|
orderedBatchEntries.length > 0
|
|
@@ -3118,6 +3196,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3118
3196
|
for (const ctx of batchHookResult.additionalContexts) {
|
|
3119
3197
|
batchAdditionalContexts.push(ctx);
|
|
3120
3198
|
}
|
|
3199
|
+
for (const msg of batchHookResult.injectedMessages) {
|
|
3200
|
+
hookInjectedMessages.push(msg);
|
|
3201
|
+
}
|
|
3121
3202
|
}
|
|
3122
3203
|
}
|
|
3123
3204
|
|
|
@@ -3137,6 +3218,24 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3137
3218
|
})
|
|
3138
3219
|
);
|
|
3139
3220
|
}
|
|
3221
|
+
|
|
3222
|
+
/**
|
|
3223
|
+
* Hook-returned `injectedMessages` land AFTER the consolidated context
|
|
3224
|
+
* message: one converted `HumanMessage` per entry (role/source kept in
|
|
3225
|
+
* `additional_kwargs`), preserving per-message identity so verbatim
|
|
3226
|
+
* user speech (e.g. steering) sits closest to the next model call.
|
|
3227
|
+
*/
|
|
3228
|
+
if (hookInjectedMessages.length > 0) {
|
|
3229
|
+
try {
|
|
3230
|
+
injected.push(...this.convertInjectedMessages(hookInjectedMessages));
|
|
3231
|
+
} catch (e) {
|
|
3232
|
+
// eslint-disable-next-line no-console
|
|
3233
|
+
console.warn(
|
|
3234
|
+
'[ToolNode] Failed to convert PostToolBatch injectedMessages:',
|
|
3235
|
+
e instanceof Error ? e.message : e
|
|
3236
|
+
);
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3140
3239
|
}
|
|
3141
3240
|
|
|
3142
3241
|
private async dispatchStepCompleted(
|