@librechat/agents 3.1.88 → 3.1.89

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.
@@ -1062,13 +1062,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1062
1062
  handlerError:
1063
1063
  handlerError instanceof Error
1064
1064
  ? {
1065
- message: handlerError.message,
1066
- stack: handlerError.stack ?? undefined,
1067
- }
1065
+ message: handlerError.message,
1066
+ stack: handlerError.stack ?? undefined,
1067
+ }
1068
1068
  : {
1069
- message: String(handlerError),
1070
- stack: undefined,
1071
- },
1069
+ message: String(handlerError),
1070
+ stack: undefined,
1071
+ },
1072
1072
  });
1073
1073
  }
1074
1074
  }
@@ -1076,11 +1076,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1076
1076
  const refMeta =
1077
1077
  unresolvedRefs.length > 0
1078
1078
  ? this.recordOutputReference(
1079
- runId,
1080
- errorContent,
1081
- undefined,
1082
- unresolvedRefs
1083
- )
1079
+ runId,
1080
+ errorContent,
1081
+ undefined,
1082
+ unresolvedRefs
1083
+ )
1084
1084
  : undefined;
1085
1085
  return new ToolMessage({
1086
1086
  status: 'error',
@@ -2432,59 +2432,77 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2432
2432
  dispatchRequests.length === 0
2433
2433
  ? Promise.resolve([] as t.ToolExecuteResult[])
2434
2434
  : new Promise<t.ToolExecuteResult[]>((resolve, reject) => {
2435
- let dispatchSettled = false;
2436
- let resultSettled = false;
2437
- let settledResults: t.ToolExecuteResult[] | undefined;
2435
+ let dispatchSettled = false;
2436
+ let resultSettled = false;
2437
+ let settledResults: t.ToolExecuteResult[] | undefined;
2438
2438
 
2439
- const maybeResolve = (): void => {
2440
- if (dispatchSettled && resultSettled) {
2441
- resolve(settledResults ?? []);
2442
- }
2443
- };
2439
+ const maybeResolve = (): void => {
2440
+ if (dispatchSettled && resultSettled) {
2441
+ resolve(settledResults ?? []);
2442
+ }
2443
+ };
2444
2444
 
2445
- const batchRequest: t.ToolExecuteBatchRequest = {
2446
- toolCalls: dispatchRequests,
2447
- userId: config.configurable?.user_id as string | undefined,
2448
- agentId: this.agentId,
2449
- configurable: config.configurable as
2445
+ const batchRequest: t.ToolExecuteBatchRequest = {
2446
+ toolCalls: dispatchRequests,
2447
+ userId: config.configurable?.user_id as string | undefined,
2448
+ agentId: this.agentId,
2449
+ configurable: config.configurable as
2450
2450
  | Record<string, unknown>
2451
2451
  | undefined,
2452
- metadata: config.metadata as
2452
+ metadata: config.metadata as
2453
2453
  | Record<string, unknown>
2454
2454
  | undefined,
2455
- resolve: (results): void => {
2456
- resultSettled = true;
2457
- settledResults = results;
2458
- maybeResolve();
2459
- },
2460
- reject,
2461
- };
2455
+ resolve: (results): void => {
2456
+ resultSettled = true;
2457
+ settledResults = results;
2458
+ maybeResolve();
2459
+ },
2460
+ reject,
2461
+ };
2462
2462
 
2463
- void safeDispatchCustomEvent(
2464
- GraphEvents.ON_TOOL_EXECUTE,
2465
- batchRequest,
2466
- config
2467
- )
2468
- .then(() => {
2469
- dispatchSettled = true;
2470
- maybeResolve();
2471
- })
2472
- .catch(reject);
2473
- });
2463
+ void safeDispatchCustomEvent(
2464
+ GraphEvents.ON_TOOL_EXECUTE,
2465
+ batchRequest,
2466
+ config
2467
+ )
2468
+ .then(() => {
2469
+ dispatchSettled = true;
2470
+ maybeResolve();
2471
+ })
2472
+ .catch(reject);
2473
+ });
2474
2474
 
2475
2475
  const eagerResultsPromise = Promise.all(
2476
- eagerExecutions.map(({ request, execution }) =>
2477
- this.resolveEagerEventExecution(request, execution)
2478
- )
2479
- ).then((results) => results.flat());
2476
+ eagerExecutions.map(async ({ request, execution }) => {
2477
+ const results = await this.resolveEagerEventExecution(
2478
+ request,
2479
+ execution
2480
+ );
2481
+ return {
2482
+ results,
2483
+ completionDispatched:
2484
+ execution.completionDispatched === true &&
2485
+ execution.request.turn === request.turn,
2486
+ toolCallId: request.id,
2487
+ };
2488
+ })
2489
+ );
2480
2490
 
2481
2491
  const [eagerResults, dispatchedResults] = await Promise.all([
2482
2492
  eagerResultsPromise,
2483
2493
  dispatchPromise,
2484
2494
  ]);
2495
+ const eagerCompletionDispatchedIds = new Set(
2496
+ eagerResults
2497
+ .filter((result) => result.completionDispatched)
2498
+ .map((result) => result.toolCallId)
2499
+ );
2500
+ const flattenedEagerResults = eagerResults.flatMap(
2501
+ (result) => result.results
2502
+ );
2485
2503
  const results = [
2486
2504
  ...plan.rejectedResults,
2487
- ...eagerResults,
2505
+ ...flattenedEagerResults,
2488
2506
  ...dispatchedResults,
2489
2507
  ];
2490
2508
 
@@ -2537,11 +2555,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2537
2555
  const errorRefMeta =
2538
2556
  unresolved.length > 0
2539
2557
  ? this.recordOutputReference(
2540
- registryRunId,
2541
- contentString,
2542
- undefined,
2543
- unresolved
2544
- )
2558
+ registryRunId,
2559
+ contentString,
2560
+ undefined,
2561
+ unresolved
2562
+ )
2545
2563
  : undefined;
2546
2564
  toolMessage = new ToolMessage({
2547
2565
  status: 'error',
@@ -2660,14 +2678,16 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2660
2678
  });
2661
2679
  }
2662
2680
 
2663
- await this.dispatchStepCompleted(
2664
- result.toolCallId,
2665
- toolName,
2666
- request?.args ?? {},
2667
- contentString,
2668
- config,
2669
- request?.turn
2670
- );
2681
+ if (!eagerCompletionDispatchedIds.has(result.toolCallId)) {
2682
+ await this.dispatchStepCompleted(
2683
+ result.toolCallId,
2684
+ toolName,
2685
+ request?.args ?? {},
2686
+ contentString,
2687
+ config,
2688
+ request?.turn
2689
+ );
2690
+ }
2671
2691
 
2672
2692
  postToolBatchEntryByCallId.set(result.toolCallId, {
2673
2693
  toolName,
@@ -2706,8 +2726,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2706
2726
  this.eventDrivenMode &&
2707
2727
  this.eagerEventToolExecution?.enabled === true &&
2708
2728
  this.hookRegistry == null &&
2709
- this.humanInTheLoop?.enabled !== true &&
2710
- this.toolOutputRegistry == null
2729
+ this.humanInTheLoop?.enabled !== true
2711
2730
  );
2712
2731
  }
2713
2732
 
@@ -2725,10 +2744,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2725
2744
 
2726
2745
  this.eagerEventToolExecutions?.delete(request.id);
2727
2746
 
2747
+ // Only tool identity + canonical args define side-effect identity here.
2748
+ // `request.turn` is final-planning metadata; if it drifts between the
2749
+ // streamed eager reservation and model-end materialization, consume the
2750
+ // same-name/same-args eager result and let the final request drive refs,
2751
+ // completion metadata, and PostToolBatch state.
2728
2752
  if (
2729
2753
  execution.toolName !== request.name ||
2730
- !recordArgsEqual(execution.args, request.args) ||
2731
- execution.request.turn !== request.turn
2754
+ !recordArgsEqual(execution.args, request.args)
2732
2755
  ) {
2733
2756
  return {
2734
2757
  toolCallId: request.id,
@@ -3015,15 +3038,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3015
3038
  outputs =
3016
3039
  directAdditionalContexts.length > 0
3017
3040
  ? [
3018
- sendOutput,
3019
- new HumanMessage({
3020
- content: directAdditionalContexts.join('\n\n'),
3021
- // Match the event-driven path's marker so hosts /
3022
- // model-side annotators treat this as system intent
3023
- // rather than ordinary user text. Codex P2 [46].
3024
- additional_kwargs: { role: 'system', source: 'hook' },
3025
- }),
3026
- ]
3041
+ sendOutput,
3042
+ new HumanMessage({
3043
+ content: directAdditionalContexts.join('\n\n'),
3044
+ // Match the event-driven path's marker so hosts /
3045
+ // model-side annotators treat this as system intent
3046
+ // rather than ordinary user text. Codex P2 [46].
3047
+ additional_kwargs: { role: 'system', source: 'hook' },
3048
+ }),
3049
+ ]
3027
3050
  : [sendOutput];
3028
3051
  await this.handleRunToolCompletions(
3029
3052
  [input.lg_tool_call],
@@ -3174,17 +3197,17 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3174
3197
  const directOutputs: (BaseMessage | Command)[] =
3175
3198
  directCalls.length > 0
3176
3199
  ? await Promise.all(
3177
- directCalls.map((call, i) =>
3178
- this.runDirectToolWithLifecycleHooks(call, config, {
3179
- batchIndex: directIndices[i],
3180
- turn,
3181
- batchScopeId,
3182
- resolvedArgsByCallId,
3183
- preBatchSnapshot,
3184
- additionalContextsSink: directAdditionalContexts,
3185
- })
3186
- )
3200
+ directCalls.map((call, i) =>
3201
+ this.runDirectToolWithLifecycleHooks(call, config, {
3202
+ batchIndex: directIndices[i],
3203
+ turn,
3204
+ batchScopeId,
3205
+ resolvedArgsByCallId,
3206
+ preBatchSnapshot,
3207
+ additionalContextsSink: directAdditionalContexts,
3208
+ })
3187
3209
  )
3210
+ )
3188
3211
  : [];
3189
3212
 
3190
3213
  if (directCalls.length > 0 && directOutputs.length > 0) {
@@ -3199,29 +3222,29 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3199
3222
  const eventResult =
3200
3223
  eventCalls.length > 0
3201
3224
  ? await this.dispatchToolEvents(eventCalls, config, {
3202
- batchIndices: eventIndices,
3203
- turn,
3204
- batchScopeId,
3205
- preResolvedArgs: preResolvedEventArgs,
3206
- preBatchSnapshot,
3207
- })
3225
+ batchIndices: eventIndices,
3226
+ turn,
3227
+ batchScopeId,
3228
+ preResolvedArgs: preResolvedEventArgs,
3229
+ preBatchSnapshot,
3230
+ })
3208
3231
  : {
3209
- toolMessages: [] as ToolMessage[],
3210
- injected: [] as BaseMessage[],
3211
- };
3232
+ toolMessages: [] as ToolMessage[],
3233
+ injected: [] as BaseMessage[],
3234
+ };
3212
3235
 
3213
3236
  const directInjected: BaseMessage[] =
3214
3237
  directAdditionalContexts.length > 0
3215
3238
  ? [
3216
- new HumanMessage({
3217
- content: directAdditionalContexts.join('\n\n'),
3218
- // System-role metadata to match the event-driven
3219
- // path so policy/recovery guidance is treated
3220
- // consistently regardless of whether the tool ran
3221
- // direct or dispatched. Codex P2 [46].
3222
- additional_kwargs: { role: 'system', source: 'hook' },
3223
- }),
3224
- ]
3239
+ new HumanMessage({
3240
+ content: directAdditionalContexts.join('\n\n'),
3241
+ // System-role metadata to match the event-driven
3242
+ // path so policy/recovery guidance is treated
3243
+ // consistently regardless of whether the tool ran
3244
+ // direct or dispatched. Codex P2 [46].
3245
+ additional_kwargs: { role: 'system', source: 'hook' },
3246
+ }),
3247
+ ]
3225
3248
  : [];
3226
3249
  outputs = [
3227
3250
  ...directOutputs,
@@ -3260,15 +3283,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3260
3283
  outputs =
3261
3284
  directAdditionalContexts.length > 0
3262
3285
  ? [
3263
- ...toolOutputs,
3264
- new HumanMessage({
3265
- content: directAdditionalContexts.join('\n\n'),
3266
- // Same system-role marker the event-driven path
3267
- // uses so direct vs dispatched is invisible to
3268
- // downstream consumers. Codex P2 [46].
3269
- additional_kwargs: { role: 'system', source: 'hook' },
3270
- }),
3271
- ]
3286
+ ...toolOutputs,
3287
+ new HumanMessage({
3288
+ content: directAdditionalContexts.join('\n\n'),
3289
+ // Same system-role marker the event-driven path
3290
+ // uses so direct vs dispatched is invisible to
3291
+ // downstream consumers. Codex P2 [46].
3292
+ additional_kwargs: { role: 'system', source: 'hook' },
3293
+ }),
3294
+ ]
3272
3295
  : toolOutputs;
3273
3296
  }
3274
3297
  }