@librechat/agents 3.7.21 → 3.8.0

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 (70) hide show
  1. package/dist/cjs/graphs/Graph.cjs +21 -4
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/langfuse.cjs +6 -2
  4. package/dist/cjs/langfuse.cjs.map +1 -1
  5. package/dist/cjs/llm/invoke.cjs +21 -6
  6. package/dist/cjs/llm/invoke.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +2 -0
  8. package/dist/cjs/stream.cjs +38 -1
  9. package/dist/cjs/stream.cjs.map +1 -1
  10. package/dist/cjs/tools/ArtifactDelivery.cjs +27 -0
  11. package/dist/cjs/tools/ArtifactDelivery.cjs.map +1 -0
  12. package/dist/cjs/tools/BashExecutor.cjs +6 -1
  13. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  14. package/dist/cjs/tools/CodeExecutor.cjs +6 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  16. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -1
  17. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  18. package/dist/cjs/tools/ToolNode.cjs +95 -18
  19. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  20. package/dist/cjs/tools/ToolSearch.cjs +130 -24
  21. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  22. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +1 -1
  23. package/dist/cjs/tools/preparedSubagents.cjs +129 -0
  24. package/dist/cjs/tools/preparedSubagents.cjs.map +1 -0
  25. package/dist/esm/graphs/Graph.mjs +21 -4
  26. package/dist/esm/graphs/Graph.mjs.map +1 -1
  27. package/dist/esm/langfuse.mjs +6 -2
  28. package/dist/esm/langfuse.mjs.map +1 -1
  29. package/dist/esm/llm/invoke.mjs +21 -6
  30. package/dist/esm/llm/invoke.mjs.map +1 -1
  31. package/dist/esm/main.mjs +2 -2
  32. package/dist/esm/stream.mjs +39 -2
  33. package/dist/esm/stream.mjs.map +1 -1
  34. package/dist/esm/tools/ArtifactDelivery.mjs +26 -0
  35. package/dist/esm/tools/ArtifactDelivery.mjs.map +1 -0
  36. package/dist/esm/tools/BashExecutor.mjs +6 -1
  37. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  38. package/dist/esm/tools/CodeExecutor.mjs +6 -1
  39. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  40. package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -1
  41. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  42. package/dist/esm/tools/ToolNode.mjs +95 -18
  43. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  44. package/dist/esm/tools/ToolSearch.mjs +129 -25
  45. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  46. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +1 -1
  47. package/dist/esm/tools/preparedSubagents.mjs +128 -0
  48. package/dist/esm/tools/preparedSubagents.mjs.map +1 -0
  49. package/dist/types/graphs/Graph.d.ts +6 -1
  50. package/dist/types/langfuse.d.ts +5 -0
  51. package/dist/types/tools/ArtifactDelivery.d.ts +4 -0
  52. package/dist/types/tools/ToolNode.d.ts +13 -1
  53. package/dist/types/tools/ToolSearch.d.ts +46 -2
  54. package/dist/types/tools/preparedSubagents.d.ts +26 -0
  55. package/dist/types/types/graph.d.ts +8 -0
  56. package/dist/types/types/tools.d.ts +28 -0
  57. package/package.json +1 -1
  58. package/src/graphs/Graph.ts +63 -5
  59. package/src/langfuse.ts +17 -2
  60. package/src/llm/invoke.ts +35 -8
  61. package/src/stream.ts +70 -1
  62. package/src/tools/ArtifactDelivery.ts +50 -0
  63. package/src/tools/BashExecutor.ts +18 -1
  64. package/src/tools/CodeExecutor.ts +18 -1
  65. package/src/tools/ProgrammaticToolCalling.ts +15 -1
  66. package/src/tools/ToolNode.ts +189 -43
  67. package/src/tools/ToolSearch.ts +278 -28
  68. package/src/tools/preparedSubagents.ts +228 -0
  69. package/src/types/graph.ts +8 -0
  70. package/src/types/tools.ts +29 -0
@@ -33,6 +33,10 @@ import {
33
33
  createCodeApiRunTimeoutSchema,
34
34
  resolveCodeApiRunTimeoutMs,
35
35
  } from './ptcTimeout';
36
+ import {
37
+ appendArtifactDeliveryWarning,
38
+ normalizeArtifactDeliveryFailure,
39
+ } from '@/tools/ArtifactDelivery';
36
40
  import {
37
41
  describeCodeApiError,
38
42
  logCodeApiDiagnostic,
@@ -871,12 +875,22 @@ export function formatCompletedResponse(
871
875
  }
872
876
 
873
877
  const outputWithReminder = appendTmpScratchReminder(formatted, sourceCode);
878
+ const artifactDelivery = normalizeArtifactDeliveryFailure(
879
+ response.artifact_delivery
880
+ );
881
+ const outputWithDeliveryWarning = appendArtifactDeliveryWarning(
882
+ outputWithReminder,
883
+ artifactDelivery
884
+ );
874
885
 
875
886
  return [
876
- appendCodeSessionFileSummary(outputWithReminder, response.files),
887
+ appendCodeSessionFileSummary(outputWithDeliveryWarning, response.files),
877
888
  {
878
889
  session_id: response.session_id,
879
890
  files: response.files,
891
+ ...(artifactDelivery != null
892
+ ? { artifact_delivery: artifactDelivery }
893
+ : {}),
880
894
  ...(response.runtime_session_id != null
881
895
  ? {
882
896
  runtime_session_id: response.runtime_session_id,
@@ -1,5 +1,6 @@
1
1
  import { nanoid } from 'nanoid';
2
2
  import { ToolCall } from '@langchain/core/messages/tool';
3
+ import { RunnableLambda } from '@langchain/core/runnables';
3
4
  import { AsyncLocalStorageProviderSingleton } from '@langchain/core/singletons';
4
5
  import {
5
6
  AIMessage,
@@ -46,8 +47,17 @@ import type {
46
47
  PostToolBatchEntry,
47
48
  ToolApprovalReplayKey,
48
49
  } from '@/hooks';
50
+ import type { PreparedSubagents } from '@/tools/preparedSubagents';
49
51
  import type { RunBreakerScope } from '@/llm/streamLimits';
50
52
  import type * as t from '@/types';
53
+ import {
54
+ type CallerCapabilityProjection,
55
+ createCallerCapabilityProjectionSnapshot,
56
+ isToolDefinitionActive,
57
+ isProgrammaticControlTool,
58
+ mergeCallerCapabilityDefinitions,
59
+ resolveCallerCapabilityProjection,
60
+ } from '@/tools/CallerCapabilities';
51
61
  import {
52
62
  cloneToolMessageWithContent,
53
63
  compactToolContent,
@@ -57,6 +67,13 @@ import {
57
67
  serializeStructuredValueBounded,
58
68
  serializeToolContentBounded,
59
69
  } from '@/utils/toolContent';
70
+ import {
71
+ buildToolExecutionRequestPlan,
72
+ coerceArgsForSchema,
73
+ coerceRecordArgs,
74
+ resolveRuntimeSessionHint,
75
+ recordArgsEqual,
76
+ } from '@/tools/eagerEventExecution';
60
77
  import {
61
78
  attachSubagentResumeManifest,
62
79
  SUBAGENT_PARENT_BATCH_CONFIG_KEY,
@@ -69,13 +86,6 @@ import {
69
86
  isIntentLabelProperty,
70
87
  outcomeFieldsFromResult,
71
88
  } from '@/tools/intentArg';
72
- import {
73
- buildToolExecutionRequestPlan,
74
- coerceArgsForSchema,
75
- coerceRecordArgs,
76
- resolveRuntimeSessionHint,
77
- recordArgsEqual,
78
- } from '@/tools/eagerEventExecution';
79
89
  import {
80
90
  resolveLangfuseRuntimeScope,
81
91
  withLangfuseRuntimeScope,
@@ -96,16 +106,9 @@ import {
96
106
  resolveLocalToolRegistry,
97
107
  resolveLocalExecutionTools,
98
108
  } from '@/tools/local';
99
- import {
100
- type CallerCapabilityProjection,
101
- createCallerCapabilityProjectionSnapshot,
102
- isToolDefinitionActive,
103
- isProgrammaticControlTool,
104
- mergeCallerCapabilityDefinitions,
105
- resolveCallerCapabilityProjection,
106
- } from '@/tools/CallerCapabilities';
107
109
  import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
108
110
  import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
111
+ import { PreparedSubagentError } from '@/tools/preparedSubagents';
109
112
  import { attachRunStepResumeState } from '@/tools/runStepResume';
110
113
 
111
114
  /** Host-facing batch requests must not carry the batch's breaker scope —
@@ -793,6 +796,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
793
796
  private getBreakerSignal?: () => AbortSignal | undefined;
794
797
  /** Owning graph's immutable run scope; captured once per batch. */
795
798
  private getRunScope?: () => RunBreakerScope;
799
+ private preparedSubagents?: PreparedSubagents;
796
800
  /**
797
801
  * Monotonic counter used to mint a unique scope id for anonymous
798
802
  * batches (ones invoked without a `run_id` in
@@ -842,6 +846,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
842
846
  fileCheckpointer,
843
847
  getBreakerSignal,
844
848
  getRunScope,
849
+ preparedSubagents,
845
850
  restoreRunStepResumeState,
846
851
  createRunStepResumeState,
847
852
  }: t.ToolNodeConstructorParams) {
@@ -927,6 +932,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
927
932
  this.toolExecution = toolExecution;
928
933
  this.getBreakerSignal = getBreakerSignal;
929
934
  this.getRunScope = getRunScope;
935
+ this.preparedSubagents = preparedSubagents;
930
936
  // Caller-provided checkpointer wins. Graphs use this to share a
931
937
  // single per-Run instance across every ToolNode they compile so
932
938
  // `Run.rewindFiles()` reaches the same snapshot store regardless
@@ -961,6 +967,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
961
967
  options?: Partial<RunnableConfig>
962
968
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
963
969
  ): Promise<any> {
970
+ return this.withToolScope(options, (scoped) => super.invoke(input, scoped));
971
+ }
972
+
973
+ private withToolScope<R>(
974
+ options: Partial<RunnableConfig> | undefined,
975
+ invoke: (config: Partial<RunnableConfig> | undefined) => Promise<R>
976
+ ): Promise<R> {
964
977
  // Explicit agent identity for tool callbacks: node-name parsing is
965
978
  // ambiguous when agent ids themselves embed node prefixes, so the
966
979
  // handler prefers this metadata (see `isForeignScope`).
@@ -995,7 +1008,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
995
1008
  // `LangfuseRuntimeContext.agentId`).
996
1009
  agentId: this.executingAgentId,
997
1010
  }),
998
- () => super.invoke(input, scopedOptions)
1011
+ () => invoke(scopedOptions)
999
1012
  );
1000
1013
  }
1001
1014
 
@@ -1279,6 +1292,146 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1279
1292
  }
1280
1293
  }
1281
1294
 
1295
+ /**
1296
+ * Forward the graph state as langgraph 1.4's `runtime.state` so
1297
+ * tools can read it off their second argument instead of the
1298
+ * deprecated `getCurrentTaskInput()` (which relies on
1299
+ * `node:async_hooks` and is browser-incompatible). Shape mirrors
1300
+ * langgraph's prebuilt ToolNode runtime exactly.
1301
+ */
1302
+ private invokeWithRuntime(
1303
+ tool: t.GenericTool,
1304
+ invokeParams: Record<string, unknown>,
1305
+ call: ToolCall,
1306
+ config: RunnableConfig,
1307
+ runInput?: RunToolBatchContext<T>['runInput']
1308
+ ): Promise<unknown> {
1309
+ const lgConfig = config as LangGraphRunnableConfig;
1310
+ const runtime: ToolRuntime<T> = {
1311
+ ...config,
1312
+ state: runInput as ToolRuntime<T>['state'],
1313
+ toolCallId: call.id ?? '',
1314
+ config,
1315
+ context: lgConfig.context as ToolRuntime<T>['context'],
1316
+ store: (lgConfig.store ?? null) as ToolRuntime<T>['store'],
1317
+ writer:
1318
+ lgConfig.writer ??
1319
+ (config.configurable?.writer as ToolRuntime<T>['writer']) ??
1320
+ null,
1321
+ };
1322
+ this.throwIfBreakerTripped(config);
1323
+ config.signal?.throwIfAborted();
1324
+ return tool.invoke(invokeParams, runtime);
1325
+ }
1326
+
1327
+ /** Only Graph's built-in subagent binding is allowed to call this seam. */
1328
+ prestartSubagent(
1329
+ call: ToolCall,
1330
+ attempt: string,
1331
+ config: RunnableConfig,
1332
+ capacity: number
1333
+ ): void {
1334
+ const runId = (config.configurable?.run_id as string | undefined) ?? '';
1335
+ if (
1336
+ call.name !== Constants.SUBAGENT ||
1337
+ call.id == null ||
1338
+ call.args.run_in_background === true ||
1339
+ call.args.subagent_thread_id != null ||
1340
+ this.humanInTheLoop?.enabled === true ||
1341
+ this.hookRegistry?.hasResultAlteringHooks(runId) === true ||
1342
+ this.eagerEventToolExecution?.excludeToolNames?.includes(call.name) ===
1343
+ true
1344
+ ) {
1345
+ return;
1346
+ }
1347
+ const tool = this.toolMap.get(call.name);
1348
+ if (tool == null || this.preparedSubagents == null) {
1349
+ return;
1350
+ }
1351
+ const scope = this.getRunScope?.();
1352
+ const capturedConfig = {
1353
+ ...config,
1354
+ configurable: {
1355
+ ...config.configurable,
1356
+ [RUN_BREAKER_SCOPE_CONFIG_KEY]: scope,
1357
+ [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: attempt,
1358
+ },
1359
+ };
1360
+ const args = JSON.parse(JSON.stringify(call.args)) as ToolCall['args'];
1361
+ const preparedCall = { ...call, args };
1362
+ this.preparedSubagents.start(
1363
+ attempt,
1364
+ this.executingAgentId ?? '',
1365
+ preparedCall,
1366
+ capacity,
1367
+ (signal) => {
1368
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
1369
+ this.toolUsageCount.set(call.name, turn + 1);
1370
+ this.directPathTurns.set(call.id!, turn);
1371
+ // An early call is its own dispatch, before the completed batch exists.
1372
+ // Give it a real chain parent instead of borrowing the model's callback
1373
+ // parent or attempting to reparent already-exported observations later.
1374
+ return this.withToolScope(capturedConfig, async (scopedConfig) => {
1375
+ let output: unknown;
1376
+ let failure: { error: unknown } | undefined;
1377
+ const dispatchOptions = {
1378
+ ...scopedConfig,
1379
+ runName: `tools=${this.executingAgentId ?? ''}`,
1380
+ };
1381
+ delete dispatchOptions.runId;
1382
+ delete dispatchOptions.signal;
1383
+ delete dispatchOptions.timeout;
1384
+ // Isolate ambient cancellation too: RunnableLambda races its signal,
1385
+ // but capacity must remain owned until the actual tool settles.
1386
+ await AsyncLocalStorageProviderSingleton.runWithConfig(
1387
+ dispatchOptions,
1388
+ () =>
1389
+ RunnableLambda.from(async (_input, dispatchConfig) => {
1390
+ try {
1391
+ output = await this.invokeWithRuntime(
1392
+ tool,
1393
+ {
1394
+ ...preparedCall,
1395
+ type: 'tool_call',
1396
+ turn,
1397
+ stepId: this.toolCallStepIds?.get(call.id!),
1398
+ },
1399
+ preparedCall,
1400
+ {
1401
+ ...dispatchConfig,
1402
+ signal: composeAbortSignals(
1403
+ signal,
1404
+ composeAbortSignals(
1405
+ config.signal,
1406
+ scope?.controller.signal
1407
+ )
1408
+ ),
1409
+ }
1410
+ );
1411
+ // Raw output belongs to the tool observation, whose redaction policy
1412
+ // knows the tool name. A chain's scalar output has no such identity.
1413
+ return { status: 'completed' };
1414
+ } catch (error) {
1415
+ failure = { error };
1416
+ return { status: 'failed' };
1417
+ }
1418
+ }).invoke(
1419
+ {
1420
+ messages: [
1421
+ new AIMessage({ content: '', tool_calls: [preparedCall] }),
1422
+ ],
1423
+ },
1424
+ dispatchOptions
1425
+ )
1426
+ );
1427
+ if (failure != null) {
1428
+ throw failure.error;
1429
+ }
1430
+ return output;
1431
+ });
1432
+ }
1433
+ );
1434
+ }
1282
1435
  /**
1283
1436
  * Runs a single tool call with error handling.
1284
1437
  *
@@ -1371,6 +1524,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1371
1524
  */
1372
1525
  const usageCount =
1373
1526
  batchContext.usageCount ??
1527
+ this.directPathTurns.get(call.id ?? '') ??
1374
1528
  ((): number => {
1375
1529
  const next = this.toolUsageCount.get(call.name) ?? 0;
1376
1530
  this.toolUsageCount.set(call.name, next + 1);
@@ -1493,31 +1647,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1493
1647
  }
1494
1648
  }
1495
1649
 
1496
- /**
1497
- * Forward the graph state as langgraph 1.4's `runtime.state` so
1498
- * tools can read it off their second argument instead of the
1499
- * deprecated `getCurrentTaskInput()` (which relies on
1500
- * `node:async_hooks` and is browser-incompatible). Shape mirrors
1501
- * langgraph's prebuilt ToolNode runtime exactly.
1502
- */
1503
- const lgConfig = config as LangGraphRunnableConfig;
1504
- const runtime: ToolRuntime<T> = {
1505
- ...config,
1506
- state: runInput as ToolRuntime<T>['state'],
1507
- toolCallId: call.id ?? '',
1508
- config,
1509
- context: lgConfig.context as ToolRuntime<T>['context'],
1510
- store: (lgConfig.store ?? null) as ToolRuntime<T>['store'],
1511
- writer:
1512
- lgConfig.writer ??
1513
- (config.configurable?.writer as ToolRuntime<T>['writer']) ??
1514
- null,
1515
- };
1516
- /** A sibling can trip the breaker while the PreToolUse hooks above
1517
- * are awaited; a tool that never inspects its runtime signal would
1518
- * still run. Recheck at the last moment before execution. */
1519
1650
  this.throwIfBreakerTripped(config);
1520
- const output = await tool.invoke(invokeParams, runtime);
1651
+ const output = await (this.preparedSubagents?.take(
1652
+ this.executingAgentId ?? '',
1653
+ { ...call, args }
1654
+ ) ?? this.invokeWithRuntime(tool, invokeParams, call, config, runInput));
1521
1655
  if (isCommand(output)) {
1522
1656
  return output;
1523
1657
  }
@@ -1633,7 +1767,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1633
1767
  * safety abort, not a tool failure to report back to the model. It
1634
1768
  * passes through like an interrupt so the whole run rejects.
1635
1769
  */
1636
- if (e instanceof StreamLimitExceededError) {
1770
+ if (
1771
+ e instanceof StreamLimitExceededError ||
1772
+ e instanceof PreparedSubagentError
1773
+ ) {
1637
1774
  throw e;
1638
1775
  }
1639
1776
  if (this.errorHandler) {
@@ -1777,6 +1914,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1777
1914
  config: RunnableConfig,
1778
1915
  batchContext: RunToolBatchContext<T> = {}
1779
1916
  ): Promise<BaseMessage | Command> {
1917
+ if (
1918
+ this.preparedSubagents?.owns(this.executingAgentId ?? '', call) === true &&
1919
+ this.hookRegistry?.hasResultAlteringHooks(
1920
+ (config.configurable?.run_id as string | undefined) ?? ''
1921
+ ) === true
1922
+ ) {
1923
+ this.preparedSubagents.clear();
1924
+ throw new PreparedSubagentError('Tool hooks changed after a subagent started.');
1925
+ }
1780
1926
  const replayController = (
1781
1927
  this.toolMap.get(call.name) as ReplayableSubagentTool | undefined
1782
1928
  )?.[SUBAGENT_REPLAY_CONTROLLER];
@@ -2735,7 +2881,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2735
2881
  const signal = config.signal;
2736
2882
  if (
2737
2883
  signal?.aborted === true &&
2738
- signal.reason instanceof StreamLimitExceededError
2884
+ (signal.reason instanceof StreamLimitExceededError || signal.reason instanceof PreparedSubagentError)
2739
2885
  ) {
2740
2886
  throw signal.reason;
2741
2887
  }
@@ -4435,7 +4581,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
4435
4581
  * otherwise perform side effects on a failed run. */
4436
4582
  if (
4437
4583
  composedSignal?.aborted === true &&
4438
- composedSignal.reason instanceof StreamLimitExceededError
4584
+ (composedSignal.reason instanceof StreamLimitExceededError || composedSignal.reason instanceof PreparedSubagentError)
4439
4585
  ) {
4440
4586
  throw composedSignal.reason;
4441
4587
  }