@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.
Files changed (125) hide show
  1. package/dist/cjs/common/enum.cjs +2 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +6 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/HookRegistry.cjs +32 -0
  6. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  7. package/dist/cjs/hooks/executeHooks.cjs +6 -0
  8. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  9. package/dist/cjs/hooks/index.cjs +12 -0
  10. package/dist/cjs/hooks/index.cjs.map +1 -0
  11. package/dist/cjs/hooks/types.cjs.map +1 -1
  12. package/dist/cjs/langfuse.cjs +62 -1
  13. package/dist/cjs/langfuse.cjs.map +1 -1
  14. package/dist/cjs/llm/bedrock/index.cjs +266 -43
  15. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  16. package/dist/cjs/llm/openai/index.cjs +274 -7
  17. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  18. package/dist/cjs/llm/openai/streamMetadata.cjs +69 -0
  19. package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -0
  20. package/dist/cjs/llm/openrouter/index.cjs +5 -6
  21. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  22. package/dist/cjs/main.cjs +6 -1
  23. package/dist/cjs/messages/format.cjs +61 -0
  24. package/dist/cjs/messages/format.cjs.map +1 -1
  25. package/dist/cjs/messages/prune.cjs +31 -19
  26. package/dist/cjs/messages/prune.cjs.map +1 -1
  27. package/dist/cjs/stream.cjs +13 -3
  28. package/dist/cjs/stream.cjs.map +1 -1
  29. package/dist/cjs/tools/ToolNode.cjs +76 -11
  30. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +2 -0
  32. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  33. package/dist/cjs/utils/tokens.cjs +111 -0
  34. package/dist/cjs/utils/tokens.cjs.map +1 -1
  35. package/dist/esm/common/enum.mjs +2 -0
  36. package/dist/esm/common/enum.mjs.map +1 -1
  37. package/dist/esm/graphs/Graph.mjs +6 -2
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/hooks/HookRegistry.mjs +32 -0
  40. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  41. package/dist/esm/hooks/executeHooks.mjs +6 -0
  42. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  43. package/dist/esm/hooks/index.mjs +12 -1
  44. package/dist/esm/hooks/index.mjs.map +1 -0
  45. package/dist/esm/hooks/types.mjs.map +1 -1
  46. package/dist/esm/langfuse.mjs +62 -1
  47. package/dist/esm/langfuse.mjs.map +1 -1
  48. package/dist/esm/llm/bedrock/index.mjs +266 -43
  49. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  50. package/dist/esm/llm/openai/index.mjs +274 -7
  51. package/dist/esm/llm/openai/index.mjs.map +1 -1
  52. package/dist/esm/llm/openai/streamMetadata.mjs +69 -0
  53. package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -0
  54. package/dist/esm/llm/openrouter/index.mjs +5 -6
  55. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  56. package/dist/esm/main.mjs +3 -3
  57. package/dist/esm/messages/format.mjs +61 -0
  58. package/dist/esm/messages/format.mjs.map +1 -1
  59. package/dist/esm/messages/prune.mjs +31 -19
  60. package/dist/esm/messages/prune.mjs.map +1 -1
  61. package/dist/esm/stream.mjs +13 -3
  62. package/dist/esm/stream.mjs.map +1 -1
  63. package/dist/esm/tools/ToolNode.mjs +76 -11
  64. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  65. package/dist/esm/tools/subagent/SubagentExecutor.mjs +2 -0
  66. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  67. package/dist/esm/utils/tokens.mjs +108 -1
  68. package/dist/esm/utils/tokens.mjs.map +1 -1
  69. package/dist/types/common/enum.d.ts +3 -1
  70. package/dist/types/graphs/Graph.d.ts +3 -1
  71. package/dist/types/hooks/HookRegistry.d.ts +10 -0
  72. package/dist/types/hooks/index.d.ts +7 -0
  73. package/dist/types/hooks/types.d.ts +18 -1
  74. package/dist/types/llm/bedrock/index.d.ts +5 -0
  75. package/dist/types/llm/openai/index.d.ts +19 -0
  76. package/dist/types/llm/openai/streamMetadata.d.ts +16 -0
  77. package/dist/types/messages/prune.d.ts +4 -3
  78. package/dist/types/tools/ToolNode.d.ts +1 -0
  79. package/dist/types/types/graph.d.ts +10 -0
  80. package/dist/types/types/llm.d.ts +12 -2
  81. package/dist/types/types/tools.d.ts +1 -1
  82. package/dist/types/utils/tokens.d.ts +30 -0
  83. package/package.json +6 -6
  84. package/src/__tests__/stream.eagerEventExecution.test.ts +100 -2
  85. package/src/common/enum.ts +2 -0
  86. package/src/graphs/Graph.ts +12 -4
  87. package/src/hooks/HookRegistry.ts +45 -0
  88. package/src/hooks/__tests__/HookRegistry.test.ts +48 -0
  89. package/src/hooks/__tests__/executeHooks.test.ts +85 -2
  90. package/src/hooks/executeHooks.ts +15 -0
  91. package/src/hooks/index.ts +7 -0
  92. package/src/hooks/types.ts +18 -1
  93. package/src/langfuse.ts +134 -1
  94. package/src/llm/bedrock/index.ts +434 -83
  95. package/src/llm/bedrock/streamSealDispatch.test.ts +97 -0
  96. package/src/llm/custom-chat-models.smoke.test.ts +7 -0
  97. package/src/llm/openai/index.ts +604 -6
  98. package/src/llm/openai/managedRequests.test.ts +182 -0
  99. package/src/llm/openai/streamMetadata.spec.ts +86 -0
  100. package/src/llm/openai/streamMetadata.ts +95 -0
  101. package/src/llm/openai/streamMetadataDedup.spec.ts +166 -0
  102. package/src/llm/openrouter/index.ts +9 -5
  103. package/src/messages/format.ts +96 -3
  104. package/src/messages/formatAgentMessages.steer.test.ts +326 -0
  105. package/src/messages/labelContentByAgent.test.ts +75 -0
  106. package/src/messages/prune.ts +56 -30
  107. package/src/specs/anthropic.simple.test.ts +4 -2
  108. package/src/specs/cache.simple.test.ts +17 -4
  109. package/src/specs/langfuse-callbacks.test.ts +61 -0
  110. package/src/specs/openai.simple.test.ts +4 -2
  111. package/src/specs/spec.utils.ts +12 -0
  112. package/src/specs/summarization.test.ts +9 -13
  113. package/src/specs/token-accounting-pipeline.test.ts +130 -4
  114. package/src/specs/tokens.test.ts +214 -0
  115. package/src/stream.ts +22 -3
  116. package/src/tools/ToolNode.ts +112 -13
  117. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +554 -0
  118. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +49 -2
  119. package/src/tools/__tests__/hitl.test.ts +112 -0
  120. package/src/tools/__tests__/subagentHooks.test.ts +124 -0
  121. package/src/tools/subagent/SubagentExecutor.ts +3 -0
  122. package/src/types/graph.ts +10 -0
  123. package/src/types/llm.ts +12 -2
  124. package/src/types/tools.ts +1 -1
  125. package/src/utils/tokens.ts +181 -3
@@ -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(toolName, reservedTurn, callId);
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 == null && this.humanInTheLoop?.enabled !== true;
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
- agentId: this.agentId,
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
- private canConsumeEagerEventExecution(): boolean {
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 == null &&
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
- if (!this.canConsumeEagerEventExecution()) {
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(