@librechat/agents 3.1.67 → 3.1.68-dev.1

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 (185) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +23 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +16 -1
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +91 -0
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +36 -0
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/hooks/HookRegistry.cjs +162 -0
  10. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -0
  11. package/dist/cjs/hooks/executeHooks.cjs +276 -0
  12. package/dist/cjs/hooks/executeHooks.cjs.map +1 -0
  13. package/dist/cjs/hooks/matchers.cjs +256 -0
  14. package/dist/cjs/hooks/matchers.cjs.map +1 -0
  15. package/dist/cjs/hooks/types.cjs +27 -0
  16. package/dist/cjs/hooks/types.cjs.map +1 -0
  17. package/dist/cjs/main.cjs +54 -0
  18. package/dist/cjs/main.cjs.map +1 -1
  19. package/dist/cjs/messages/format.cjs +74 -12
  20. package/dist/cjs/messages/format.cjs.map +1 -1
  21. package/dist/cjs/run.cjs +111 -0
  22. package/dist/cjs/run.cjs.map +1 -1
  23. package/dist/cjs/summarization/index.cjs +41 -0
  24. package/dist/cjs/summarization/index.cjs.map +1 -1
  25. package/dist/cjs/summarization/node.cjs +165 -19
  26. package/dist/cjs/summarization/node.cjs.map +1 -1
  27. package/dist/cjs/tools/BashExecutor.cjs +165 -0
  28. package/dist/cjs/tools/BashExecutor.cjs.map +1 -0
  29. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +287 -0
  30. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -0
  31. package/dist/cjs/tools/CodeExecutor.cjs +0 -9
  32. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  33. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +7 -23
  34. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  35. package/dist/cjs/tools/ReadFile.cjs +43 -0
  36. package/dist/cjs/tools/ReadFile.cjs.map +1 -0
  37. package/dist/cjs/tools/SkillTool.cjs +50 -0
  38. package/dist/cjs/tools/SkillTool.cjs.map +1 -0
  39. package/dist/cjs/tools/SubagentTool.cjs +92 -0
  40. package/dist/cjs/tools/SubagentTool.cjs.map +1 -0
  41. package/dist/cjs/tools/ToolNode.cjs +304 -140
  42. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  43. package/dist/cjs/tools/ToolSearch.cjs +2 -13
  44. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  45. package/dist/cjs/tools/skillCatalog.cjs +84 -0
  46. package/dist/cjs/tools/skillCatalog.cjs.map +1 -0
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +511 -0
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -0
  49. package/dist/esm/agents/AgentContext.mjs +23 -3
  50. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  51. package/dist/esm/common/enum.mjs +15 -2
  52. package/dist/esm/common/enum.mjs.map +1 -1
  53. package/dist/esm/graphs/Graph.mjs +91 -0
  54. package/dist/esm/graphs/Graph.mjs.map +1 -1
  55. package/dist/esm/graphs/MultiAgentGraph.mjs +36 -0
  56. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  57. package/dist/esm/hooks/HookRegistry.mjs +160 -0
  58. package/dist/esm/hooks/HookRegistry.mjs.map +1 -0
  59. package/dist/esm/hooks/executeHooks.mjs +273 -0
  60. package/dist/esm/hooks/executeHooks.mjs.map +1 -0
  61. package/dist/esm/hooks/matchers.mjs +251 -0
  62. package/dist/esm/hooks/matchers.mjs.map +1 -0
  63. package/dist/esm/hooks/types.mjs +25 -0
  64. package/dist/esm/hooks/types.mjs.map +1 -0
  65. package/dist/esm/main.mjs +13 -2
  66. package/dist/esm/main.mjs.map +1 -1
  67. package/dist/esm/messages/format.mjs +66 -4
  68. package/dist/esm/messages/format.mjs.map +1 -1
  69. package/dist/esm/run.mjs +111 -0
  70. package/dist/esm/run.mjs.map +1 -1
  71. package/dist/esm/summarization/index.mjs +41 -1
  72. package/dist/esm/summarization/index.mjs.map +1 -1
  73. package/dist/esm/summarization/node.mjs +165 -19
  74. package/dist/esm/summarization/node.mjs.map +1 -1
  75. package/dist/esm/tools/BashExecutor.mjs +159 -0
  76. package/dist/esm/tools/BashExecutor.mjs.map +1 -0
  77. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +278 -0
  78. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -0
  79. package/dist/esm/tools/CodeExecutor.mjs +0 -9
  80. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  81. package/dist/esm/tools/ProgrammaticToolCalling.mjs +8 -24
  82. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  83. package/dist/esm/tools/ReadFile.mjs +38 -0
  84. package/dist/esm/tools/ReadFile.mjs.map +1 -0
  85. package/dist/esm/tools/SkillTool.mjs +45 -0
  86. package/dist/esm/tools/SkillTool.mjs.map +1 -0
  87. package/dist/esm/tools/SubagentTool.mjs +85 -0
  88. package/dist/esm/tools/SubagentTool.mjs.map +1 -0
  89. package/dist/esm/tools/ToolNode.mjs +306 -142
  90. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  91. package/dist/esm/tools/ToolSearch.mjs +3 -14
  92. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  93. package/dist/esm/tools/skillCatalog.mjs +82 -0
  94. package/dist/esm/tools/skillCatalog.mjs.map +1 -0
  95. package/dist/esm/tools/subagent/SubagentExecutor.mjs +505 -0
  96. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -0
  97. package/dist/types/agents/AgentContext.d.ts +6 -0
  98. package/dist/types/common/enum.d.ts +10 -2
  99. package/dist/types/graphs/Graph.d.ts +2 -0
  100. package/dist/types/graphs/MultiAgentGraph.d.ts +12 -0
  101. package/dist/types/hooks/HookRegistry.d.ts +56 -0
  102. package/dist/types/hooks/executeHooks.d.ts +79 -0
  103. package/dist/types/hooks/index.d.ts +6 -0
  104. package/dist/types/hooks/matchers.d.ts +95 -0
  105. package/dist/types/hooks/types.d.ts +320 -0
  106. package/dist/types/index.d.ts +8 -0
  107. package/dist/types/messages/format.d.ts +2 -1
  108. package/dist/types/run.d.ts +1 -0
  109. package/dist/types/summarization/index.d.ts +2 -0
  110. package/dist/types/summarization/node.d.ts +2 -0
  111. package/dist/types/tools/BashExecutor.d.ts +45 -0
  112. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +72 -0
  113. package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -9
  114. package/dist/types/tools/ReadFile.d.ts +28 -0
  115. package/dist/types/tools/SkillTool.d.ts +40 -0
  116. package/dist/types/tools/SubagentTool.d.ts +36 -0
  117. package/dist/types/tools/ToolNode.d.ts +24 -2
  118. package/dist/types/tools/ToolSearch.d.ts +2 -2
  119. package/dist/types/tools/skillCatalog.d.ts +19 -0
  120. package/dist/types/tools/subagent/SubagentExecutor.d.ts +137 -0
  121. package/dist/types/tools/subagent/index.d.ts +2 -0
  122. package/dist/types/types/graph.d.ts +61 -2
  123. package/dist/types/types/index.d.ts +1 -0
  124. package/dist/types/types/run.d.ts +20 -0
  125. package/dist/types/types/skill.d.ts +9 -0
  126. package/dist/types/types/tools.d.ts +38 -10
  127. package/package.json +5 -1
  128. package/src/agents/AgentContext.ts +26 -2
  129. package/src/common/enum.ts +15 -1
  130. package/src/graphs/Graph.ts +113 -0
  131. package/src/graphs/MultiAgentGraph.ts +39 -0
  132. package/src/graphs/__tests__/MultiAgentGraph.test.ts +91 -0
  133. package/src/hooks/HookRegistry.ts +208 -0
  134. package/src/hooks/__tests__/HookRegistry.test.ts +190 -0
  135. package/src/hooks/__tests__/compactHooks.test.ts +214 -0
  136. package/src/hooks/__tests__/executeHooks.test.ts +1013 -0
  137. package/src/hooks/__tests__/integration.test.ts +337 -0
  138. package/src/hooks/__tests__/matchers.test.ts +238 -0
  139. package/src/hooks/__tests__/toolHooks.test.ts +669 -0
  140. package/src/hooks/executeHooks.ts +375 -0
  141. package/src/hooks/index.ts +57 -0
  142. package/src/hooks/matchers.ts +280 -0
  143. package/src/hooks/types.ts +404 -0
  144. package/src/index.ts +10 -0
  145. package/src/messages/format.ts +74 -4
  146. package/src/messages/formatAgentMessages.skills.test.ts +334 -0
  147. package/src/run.ts +126 -0
  148. package/src/scripts/multi-agent-subagent.ts +246 -0
  149. package/src/scripts/programmatic_exec.ts +1 -10
  150. package/src/scripts/subagent-event-driven-debug.ts +190 -0
  151. package/src/scripts/subagent-tools-debug.ts +160 -0
  152. package/src/scripts/test_code_api.ts +0 -7
  153. package/src/scripts/tool_search.ts +1 -10
  154. package/src/specs/subagent.test.ts +305 -0
  155. package/src/summarization/__tests__/node.test.ts +42 -0
  156. package/src/summarization/__tests__/trigger.test.ts +100 -1
  157. package/src/summarization/index.ts +47 -0
  158. package/src/summarization/node.ts +202 -24
  159. package/src/tools/BashExecutor.ts +193 -0
  160. package/src/tools/BashProgrammaticToolCalling.ts +381 -0
  161. package/src/tools/CodeExecutor.ts +0 -11
  162. package/src/tools/ProgrammaticToolCalling.ts +4 -29
  163. package/src/tools/ReadFile.ts +39 -0
  164. package/src/tools/SkillTool.ts +46 -0
  165. package/src/tools/SubagentTool.ts +100 -0
  166. package/src/tools/ToolNode.ts +391 -169
  167. package/src/tools/ToolSearch.ts +3 -19
  168. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +7 -8
  169. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -1
  170. package/src/tools/__tests__/ReadFile.test.ts +44 -0
  171. package/src/tools/__tests__/SkillTool.test.ts +442 -0
  172. package/src/tools/__tests__/SubagentExecutor.test.ts +1148 -0
  173. package/src/tools/__tests__/SubagentTool.test.ts +149 -0
  174. package/src/tools/__tests__/ToolNode.session.test.ts +12 -12
  175. package/src/tools/__tests__/ToolSearch.integration.test.ts +7 -8
  176. package/src/tools/__tests__/skillCatalog.test.ts +161 -0
  177. package/src/tools/__tests__/subagentHooks.test.ts +215 -0
  178. package/src/tools/skillCatalog.ts +126 -0
  179. package/src/tools/subagent/SubagentExecutor.ts +676 -0
  180. package/src/tools/subagent/index.ts +13 -0
  181. package/src/types/graph.ts +80 -1
  182. package/src/types/index.ts +1 -0
  183. package/src/types/run.ts +20 -0
  184. package/src/types/skill.ts +11 -0
  185. package/src/types/tools.ts +41 -10
@@ -7,6 +7,7 @@ import {
7
7
  import type { RunnableConfig } from '@langchain/core/runnables';
8
8
  import type { UsageMetadata, BaseMessage } from '@langchain/core/messages';
9
9
  import type { AgentContext } from '@/agents/AgentContext';
10
+ import type { HookRegistry } from '@/hooks';
10
11
  import type { OnChunk } from '@/llm/invoke';
11
12
  import type * as t from '@/types';
12
13
  import { ContentTypes, GraphEvents, StepTypes, Providers } from '@/common';
@@ -17,6 +18,7 @@ import { getMaxOutputTokensKey } from '@/llm/request';
17
18
  import { addCacheControl } from '@/messages/cache';
18
19
  import { initializeModel } from '@/llm/init';
19
20
  import { getChunkContent } from '@/stream';
21
+ import { executeHooks } from '@/hooks';
20
22
 
21
23
  const SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);
22
24
 
@@ -364,6 +366,122 @@ type LogFn = (
364
366
  data?: Record<string, unknown>
365
367
  ) => void;
366
368
 
369
+ /**
370
+ * Extracts an HTTP status code from a thrown LLM-provider error. Returns
371
+ * `undefined` for non-object values (including `null` or `undefined`, both
372
+ * valid `throw` targets in JS) so callers never dereference a nullish
373
+ * value.
374
+ */
375
+ function extractHttpStatus(err: unknown): number | undefined {
376
+ if (err == null || typeof err !== 'object') {
377
+ return undefined;
378
+ }
379
+ const errRecord = err as Record<string, unknown>;
380
+ const direct = errRecord.status;
381
+ if (typeof direct === 'number') {
382
+ return direct;
383
+ }
384
+ const statusCode = errRecord.statusCode;
385
+ if (typeof statusCode === 'number') {
386
+ return statusCode;
387
+ }
388
+ const response = errRecord.response;
389
+ if (response != null && typeof response === 'object') {
390
+ const nested = (response as Record<string, unknown>).status;
391
+ if (typeof nested === 'number') {
392
+ return nested;
393
+ }
394
+ }
395
+ return undefined;
396
+ }
397
+
398
+ /**
399
+ * Formats a provider-level error for logging. Returns both a human-readable
400
+ * suffix (safe to include in the message string so it survives any host-side
401
+ * formatter) and a structured metadata bag for rich log backends.
402
+ */
403
+ function describeProviderError(
404
+ err: unknown,
405
+ provider: string,
406
+ modelName?: string
407
+ ): { suffix: string; data: Record<string, unknown> } {
408
+ const providerLabel = `${provider}/${modelName ?? '(no-model)'}`;
409
+ const errMsg = err instanceof Error ? err.message : String(err);
410
+
411
+ const data: Record<string, unknown> = {
412
+ provider,
413
+ model: modelName,
414
+ };
415
+ if (err instanceof Error) {
416
+ data.errorName = err.name;
417
+ data.errorStack = err.stack;
418
+ }
419
+
420
+ const status = extractHttpStatus(err);
421
+ const statusSuffix = status != null ? ` (HTTP ${status})` : '';
422
+ if (status != null) {
423
+ data.status = status;
424
+ }
425
+
426
+ return {
427
+ suffix: `[${providerLabel}]${statusSuffix}: ${errMsg}`,
428
+ data,
429
+ };
430
+ }
431
+
432
+ /**
433
+ * Formats an exhausted-fallback error. `tryFallbackProviders` throws the
434
+ * last fallback provider's error, which may be from any of the configured
435
+ * fallbacks — not the primary — so we label the log with the list of
436
+ * fallback providers attempted rather than mis-attributing to the primary.
437
+ *
438
+ * Entries in `fallbacks` are normally strongly typed, but we defend against
439
+ * malformed runtime config (null/undefined entries, missing `provider`
440
+ * field) so a recoverable summarization failure is never promoted to an
441
+ * uncaught exception from inside the logging path.
442
+ */
443
+ function describeFallbackError(
444
+ err: unknown,
445
+ fallbacks: unknown
446
+ ): { suffix: string; data: Record<string, unknown> } {
447
+ const errMsg = err instanceof Error ? err.message : String(err);
448
+ const list: ReadonlyArray<unknown> = Array.isArray(fallbacks)
449
+ ? fallbacks
450
+ : [];
451
+ const providerNames = list
452
+ .map((f) => {
453
+ if (f == null || typeof f !== 'object') {
454
+ return undefined;
455
+ }
456
+ const raw = (f as { provider?: unknown }).provider;
457
+ return raw != null ? String(raw) : undefined;
458
+ })
459
+ .filter((p): p is string => typeof p === 'string');
460
+ const label =
461
+ providerNames.length > 0
462
+ ? `fallbacks=[${providerNames.join(',')}]`
463
+ : 'no-fallbacks';
464
+
465
+ const data: Record<string, unknown> = {
466
+ fallbackProviders: providerNames,
467
+ fallbackCount: list.length,
468
+ };
469
+ if (err instanceof Error) {
470
+ data.errorName = err.name;
471
+ data.errorStack = err.stack;
472
+ }
473
+ const status = extractHttpStatus(err);
474
+ const statusSuffix = status != null ? ` (HTTP ${status})` : '';
475
+ if (status != null) {
476
+ data.status = status;
477
+ }
478
+
479
+ return {
480
+ suffix: `[${label}]${statusSuffix}: ${errMsg}`,
481
+ data,
482
+ };
483
+ }
484
+
367
485
  /**
368
486
  * Runs the summarization LLM call with primary + fallback providers,
369
487
  * falling back to a metadata stub when all calls fail.
@@ -387,18 +505,23 @@ async function executeSummarizationWithFallback(params: {
387
505
  log,
388
506
  } = params;
389
507
 
390
- const summarizationModel = initializeModel({
391
- provider: clientConfig.provider as Providers,
392
- clientOptions: clientConfig.clientOptions as t.ClientOptions,
393
- tools: agentContext.getToolsForBinding(),
394
- }) as t.ChatModel;
395
-
396
508
  const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';
397
509
 
398
510
  let summaryText = '';
399
511
  let summaryUsage: Partial<UsageMetadata> | undefined;
400
512
 
401
513
  try {
514
+ /**
515
+ * Initialize inside the try so that a misconfigured provider
516
+ * (e.g. an unrecognized summarization.provider) surfaces through the
517
+ * `log('error', ...)` path below rather than bubbling up silently.
518
+ */
519
+ const summarizationModel = initializeModel({
520
+ provider: clientConfig.provider as Providers,
521
+ clientOptions: clientConfig.clientOptions as t.ClientOptions,
522
+ tools: agentContext.getToolsForBinding(),
523
+ }) as t.ChatModel;
524
+
402
525
  const result = await summarizeWithCacheHit({
403
526
  model: summarizationModel,
404
527
  messages,
@@ -415,19 +538,20 @@ async function executeSummarizationWithFallback(params: {
415
538
  summaryText = result.text;
416
539
  summaryUsage = result.usage;
417
540
  } catch (primaryError) {
418
- log('error', 'Summarization LLM call failed', {
419
- error:
420
- primaryError instanceof Error
421
- ? primaryError.message
422
- : String(primaryError),
423
- provider: clientConfig.provider,
424
- model: clientConfig.modelName,
541
+ const primaryDescribed = describeProviderError(
542
+ primaryError,
543
+ clientConfig.provider,
544
+ clientConfig.modelName
545
+ );
546
+ log('error', `Summarization LLM call failed ${primaryDescribed.suffix}`, {
547
+ ...primaryDescribed.data,
425
548
  messagesToRefineCount: messages.length,
426
549
  });
427
550
 
428
- const fallbacks =
429
- (clientConfig.clientOptions as unknown as t.LLMConfig | undefined)
430
- ?.fallbacks ?? [];
551
+ const rawFallbacks = (
552
+ clientConfig.clientOptions as unknown as t.LLMConfig | undefined
553
+ )?.fallbacks;
554
+ const fallbacks = Array.isArray(rawFallbacks) ? rawFallbacks : [];
431
555
  if (fallbacks.length > 0) {
432
556
  try {
433
557
  const onChunk = createSummarizationChunkHandler({
@@ -460,18 +584,21 @@ async function executeSummarizationWithFallback(params: {
460
584
  );
461
585
  }
462
586
  } catch (fbErr) {
463
- log('warn', 'Fallback providers also failed', {
464
- error: fbErr instanceof Error ? fbErr.message : String(fbErr),
587
+ const fbDescribed = describeFallbackError(fbErr, fallbacks);
588
+ log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {
589
+ ...fbDescribed.data,
465
590
  });
466
591
  }
467
592
  }
468
593
  if (!summaryText) {
469
- log('warn', 'Summarization failed, falling back to metadata stub', {
470
- error:
471
- primaryError instanceof Error
472
- ? primaryError.message
473
- : String(primaryError),
474
- });
594
+ log(
595
+ 'warn',
596
+ `Summarization failed, falling back to metadata stub ${primaryDescribed.suffix}`,
597
+ {
598
+ ...primaryDescribed.data,
599
+ messagesToRefineCount: messages.length,
600
+ }
601
+ );
475
602
  summaryText = generateMetadataStub(messages);
476
603
  }
477
604
  }
@@ -530,6 +657,35 @@ async function dispatchCompletionEvents(params: {
530
657
  );
531
658
  }
532
659
 
660
+ const sessionId = graph.runId ?? '';
661
+ if (graph.hookRegistry?.hasHookFor('PostCompact', sessionId) === true) {
662
+ const threadId = (
663
+ runnableConfig?.configurable as Record<string, unknown> | undefined
664
+ )?.thread_id as string | undefined;
665
+ const firstBlock = summaryBlock.content?.[0];
666
+ const summaryText =
667
+ firstBlock != null &&
668
+ typeof firstBlock === 'object' &&
669
+ 'text' in firstBlock &&
670
+ typeof firstBlock.text === 'string'
671
+ ? firstBlock.text
672
+ : '';
673
+ await executeHooks({
674
+ registry: graph.hookRegistry,
675
+ input: {
676
+ hook_event_name: 'PostCompact',
677
+ runId: sessionId,
678
+ threadId,
679
+ agentId,
680
+ summary: summaryText,
681
+ messagesAfterCount: 0,
682
+ },
683
+ sessionId,
684
+ }).catch(() => {
685
+ /* PostCompact is observational — swallow errors */
686
+ });
687
+ }
688
+
533
689
  agentContext.rebuildTokenMapAfterSummarization({});
534
690
  }
535
691
 
@@ -545,6 +701,7 @@ interface CreateSummarizeNodeParams {
545
701
  config?: RunnableConfig;
546
702
  runId?: string;
547
703
  isMultiAgent: boolean;
704
+ hookRegistry?: HookRegistry;
548
705
  dispatchRunStep: (
549
706
  runStep: t.RunStep,
550
707
  config?: RunnableConfig
@@ -650,6 +807,27 @@ export function createSummarizeNode({
650
807
  );
651
808
  }
652
809
 
810
+ const sessionId = graph.runId ?? '';
811
+ if (graph.hookRegistry?.hasHookFor('PreCompact', sessionId) === true) {
812
+ const threadId = (
813
+ runnableConfig?.configurable as Record<string, unknown> | undefined
814
+ )?.thread_id as string | undefined;
815
+ await executeHooks({
816
+ registry: graph.hookRegistry,
817
+ input: {
818
+ hook_event_name: 'PreCompact',
819
+ runId: sessionId,
820
+ threadId,
821
+ agentId: request.agentId,
822
+ messagesBeforeCount: messagesToRefine.length,
823
+ trigger: agentContext.summarizationConfig?.trigger?.type ?? 'default',
824
+ },
825
+ sessionId,
826
+ }).catch(() => {
827
+ /* PreCompact is observational — swallow errors */
828
+ });
829
+ }
830
+
653
831
  const isSelfSummarizeModel =
654
832
  clientConfig.provider === (agentContext.provider as string);
655
833
  const hasPromptCache =
@@ -0,0 +1,193 @@
1
+ import { config } from 'dotenv';
2
+ import fetch, { RequestInit } from 'node-fetch';
3
+ import { HttpsProxyAgent } from 'https-proxy-agent';
4
+ import { tool, DynamicStructuredTool } from '@langchain/core/tools';
5
+ import type * as t from '@/types';
6
+ import { imageExtRegex, getCodeBaseURL } from './CodeExecutor';
7
+ import { Constants } from '@/common';
8
+
9
+ config();
10
+
11
+ const imageMessage = 'Image is already displayed to the user';
12
+ const otherMessage = 'File is already downloaded by the user';
13
+ const accessMessage =
14
+ 'Note: Files from previous executions are automatically available and can be modified.';
15
+ const emptyOutputMessage =
16
+ 'stdout: Empty. Ensure you\'re writing output explicitly.\n';
17
+
18
+ const baseEndpoint = getCodeBaseURL();
19
+ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
20
+
21
+ export const BashExecutionToolSchema = {
22
+ type: 'object',
23
+ properties: {
24
+ command: {
25
+ type: 'string',
26
+ description: `The bash command or script to execute.
27
+ - The environment is stateless; variables and state don't persist between executions.
28
+ - Generated files from previous executions are automatically available in "/mnt/data/".
29
+ - Files from previous executions are automatically available and can be modified in place.
30
+ - Input code **IS ALREADY** displayed to the user, so **DO NOT** repeat it in your response unless asked.
31
+ - Output code **IS NOT** displayed to the user, so **DO** write all desired output explicitly.
32
+ - IMPORTANT: You MUST explicitly print/output ALL results you want the user to see.
33
+ - Use \`echo\`, \`printf\`, or \`cat\` for all outputs.`,
34
+ },
35
+ args: {
36
+ type: 'array',
37
+ items: { type: 'string' },
38
+ description:
39
+ 'Additional arguments to execute the command with. This should only be used if the input command requires additional arguments to run.',
40
+ },
41
+ },
42
+ required: ['command'],
43
+ } as const;
44
+
45
+ export const BashExecutionToolDescription = `
46
+ Runs bash commands and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.
47
+
48
+ Usage:
49
+ - No network access available.
50
+ - Generated files are automatically delivered; **DO NOT** provide download links.
51
+ - NEVER use this tool to execute malicious commands.
52
+ `.trim();
53
+
54
+ export const BashExecutionToolName = Constants.BASH_TOOL;
55
+
56
+ export const BashExecutionToolDefinition = {
57
+ name: BashExecutionToolName,
58
+ description: BashExecutionToolDescription,
59
+ schema: BashExecutionToolSchema,
60
+ } as const;
61
+
62
+ function createBashExecutionTool(
63
+ params: t.BashExecutionToolParams = {}
64
+ ): DynamicStructuredTool {
65
+ return tool(
66
+ async (rawInput, config) => {
67
+ const { command, ...rest } = rawInput as {
68
+ command: string;
69
+ args?: string[];
70
+ };
71
+ const { session_id, _injected_files } = (config.toolCall ?? {}) as {
72
+ session_id?: string;
73
+ _injected_files?: t.CodeEnvFile[];
74
+ };
75
+
76
+ const postData: Record<string, unknown> = {
77
+ lang: 'bash',
78
+ code: command,
79
+ ...rest,
80
+ ...params,
81
+ };
82
+
83
+ if (_injected_files && _injected_files.length > 0) {
84
+ postData.files = _injected_files;
85
+ } else if (session_id != null && session_id.length > 0) {
86
+ try {
87
+ const filesEndpoint = `${baseEndpoint}/files/${session_id}?detail=full`;
88
+ const fetchOptions: RequestInit = {
89
+ method: 'GET',
90
+ headers: {
91
+ 'User-Agent': 'LibreChat/1.0',
92
+ },
93
+ };
94
+
95
+ if (process.env.PROXY != null && process.env.PROXY !== '') {
96
+ fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
97
+ }
98
+
99
+ const response = await fetch(filesEndpoint, fetchOptions);
100
+ if (!response.ok) {
101
+ throw new Error(
102
+ `Failed to fetch files for session: ${response.status}`
103
+ );
104
+ }
105
+
106
+ const files = await response.json();
107
+ if (Array.isArray(files) && files.length > 0) {
108
+ const fileReferences: t.CodeEnvFile[] = files.map((file) => {
109
+ const nameParts = file.name.split('/');
110
+ const id = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';
111
+
112
+ return {
113
+ session_id,
114
+ id,
115
+ name: file.metadata['original-filename'],
116
+ };
117
+ });
118
+
119
+ postData.files = fileReferences;
120
+ }
121
+ } catch {
122
+ // eslint-disable-next-line no-console
123
+ console.warn(`Failed to fetch files for session: ${session_id}`);
124
+ }
125
+ }
126
+
127
+ try {
128
+ const fetchOptions: RequestInit = {
129
+ method: 'POST',
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ 'User-Agent': 'LibreChat/1.0',
133
+ },
134
+ body: JSON.stringify(postData),
135
+ };
136
+
137
+ if (process.env.PROXY != null && process.env.PROXY !== '') {
138
+ fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
139
+ }
140
+ const response = await fetch(EXEC_ENDPOINT, fetchOptions);
141
+ if (!response.ok) {
142
+ throw new Error(`HTTP error! status: ${response.status}`);
143
+ }
144
+
145
+ const result: t.ExecuteResult = await response.json();
146
+ let formattedOutput = '';
147
+ if (result.stdout) {
148
+ formattedOutput += `stdout:\n${result.stdout}\n`;
149
+ } else {
150
+ formattedOutput += emptyOutputMessage;
151
+ }
152
+ if (result.stderr) formattedOutput += `stderr:\n${result.stderr}\n`;
153
+ if (result.files && result.files.length > 0) {
154
+ formattedOutput += 'Generated files:\n';
155
+
156
+ const fileCount = result.files.length;
157
+ for (let i = 0; i < fileCount; i++) {
158
+ const file = result.files[i];
159
+ const isImage = imageExtRegex.test(file.name);
160
+ formattedOutput += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
161
+
162
+ if (i < fileCount - 1) {
163
+ formattedOutput += fileCount <= 3 ? ', ' : ',\n';
164
+ }
165
+ }
166
+
167
+ formattedOutput += `\n\n${accessMessage}`;
168
+ return [
169
+ formattedOutput.trim(),
170
+ {
171
+ session_id: result.session_id,
172
+ files: result.files,
173
+ },
174
+ ];
175
+ }
176
+
177
+ return [formattedOutput.trim(), { session_id: result.session_id }];
178
+ } catch (error) {
179
+ throw new Error(
180
+ `Execution error:\n\n${(error as Error | undefined)?.message}`
181
+ );
182
+ }
183
+ },
184
+ {
185
+ name: BashExecutionToolName,
186
+ description: BashExecutionToolDescription,
187
+ schema: BashExecutionToolSchema,
188
+ responseFormat: Constants.CONTENT_AND_ARTIFACT,
189
+ }
190
+ );
191
+ }
192
+
193
+ export { createBashExecutionTool };