@librechat/agents 3.1.73 → 3.1.75

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 (53) hide show
  1. package/README.md +66 -0
  2. package/dist/cjs/agents/AgentContext.cjs +146 -57
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -1
  5. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  6. package/dist/cjs/main.cjs +1 -0
  7. package/dist/cjs/main.cjs.map +1 -1
  8. package/dist/cjs/messages/cache.cjs +37 -3
  9. package/dist/cjs/messages/cache.cjs.map +1 -1
  10. package/dist/cjs/tools/BashExecutor.cjs +21 -11
  11. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  12. package/dist/cjs/tools/CodeExecutor.cjs +37 -10
  13. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  14. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -11
  15. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  16. package/dist/esm/agents/AgentContext.mjs +147 -58
  17. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  18. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -1
  19. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  20. package/dist/esm/main.mjs +1 -1
  21. package/dist/esm/messages/cache.mjs +37 -3
  22. package/dist/esm/messages/cache.mjs.map +1 -1
  23. package/dist/esm/tools/BashExecutor.mjs +22 -12
  24. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  25. package/dist/esm/tools/CodeExecutor.mjs +37 -11
  26. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  27. package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -12
  28. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  29. package/dist/types/agents/AgentContext.d.ts +29 -4
  30. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +46 -0
  31. package/dist/types/tools/CodeExecutor.d.ts +6 -0
  32. package/dist/types/types/graph.d.ts +3 -1
  33. package/dist/types/types/run.d.ts +2 -0
  34. package/dist/types/types/tools.d.ts +9 -0
  35. package/package.json +1 -1
  36. package/src/agents/AgentContext.ts +189 -71
  37. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +116 -0
  38. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +149 -0
  39. package/src/agents/__tests__/AgentContext.test.ts +333 -2
  40. package/src/agents/__tests__/promptCacheLiveHelpers.ts +165 -0
  41. package/src/llm/anthropic/utils/message_inputs.ts +6 -1
  42. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +77 -0
  43. package/src/messages/cache.test.ts +104 -3
  44. package/src/messages/cache.ts +54 -3
  45. package/src/specs/anthropic.simple.test.ts +61 -0
  46. package/src/specs/summarization.test.ts +7 -3
  47. package/src/tools/BashExecutor.ts +37 -13
  48. package/src/tools/CodeExecutor.ts +55 -11
  49. package/src/tools/ProgrammaticToolCalling.ts +29 -14
  50. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +60 -0
  51. package/src/types/graph.ts +3 -1
  52. package/src/types/run.ts +2 -0
  53. package/src/types/tools.ts +9 -0
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @librechat/agents
2
+
3
+ TypeScript utilities for building LibreChat agent workflows. The package provides graph orchestration, streaming event handling, tool execution, provider adapters, and message formatting for single-agent and multi-agent runs.
4
+
5
+ ## Features
6
+
7
+ - LangGraph-based single-agent and multi-agent workflows
8
+ - Streaming content aggregation and run-step event handlers
9
+ - Tool calling, tool search, subagent handoffs, and programmatic tool execution
10
+ - Provider adapters for Anthropic, Bedrock, Vertex AI, OpenAI-compatible providers, Google, Mistral, DeepSeek, and xAI
11
+ - Message formatting, context pruning, summarization, and cache-control helpers
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @librechat/agents
17
+ ```
18
+
19
+ ## Basic Usage
20
+
21
+ ```typescript
22
+ import { HumanMessage } from '@langchain/core/messages';
23
+ import { Providers, Run } from '@librechat/agents';
24
+
25
+ const run = await Run.create({
26
+ runId: crypto.randomUUID(),
27
+ graphConfig: {
28
+ type: 'standard',
29
+ instructions: 'You are a helpful assistant.',
30
+ llmConfig: {
31
+ provider: Providers.OPENAI,
32
+ model: 'gpt-4o-mini',
33
+ apiKey: process.env.OPENAI_API_KEY,
34
+ },
35
+ },
36
+ returnContent: true,
37
+ });
38
+
39
+ const content = await run.processStream(
40
+ { messages: [new HumanMessage('Hello')] },
41
+ {
42
+ runId: crypto.randomUUID(),
43
+ streamMode: 'values',
44
+ version: 'v2',
45
+ }
46
+ );
47
+ ```
48
+
49
+ ## Development
50
+
51
+ ```bash
52
+ npm ci
53
+ npm run build
54
+ npm test
55
+ npx tsc --noEmit
56
+ npx eslint src/
57
+ ```
58
+
59
+ ## Documentation
60
+
61
+ - [Multi-agent patterns](./docs/multi-agent-patterns.md)
62
+ - [Summarization behavior](./docs/summarization-behavior.md)
63
+
64
+ ## License
65
+
66
+ MIT
@@ -192,7 +192,7 @@ class AgentContext {
192
192
  summaryTokenCount = 0;
193
193
  /**
194
194
  * Where the summary should be injected:
195
- * - `'system_prompt'`: cross-run summary, included in `buildInstructionsString`
195
+ * - `'system_prompt'`: cross-run summary, included in the dynamic system tail
196
196
  * - `'user_message'`: mid-run compaction, injected as HumanMessage on clean slate
197
197
  * - `'none'`: no summary present
198
198
  */
@@ -298,15 +298,18 @@ class AgentContext {
298
298
  }
299
299
  /**
300
300
  * Gets the system runnable, creating it lazily if needed.
301
- * Includes instructions, additional instructions, and programmatic-only tools documentation.
301
+ * Includes stable instructions, dynamic additional instructions, and
302
+ * programmatic-only tools documentation.
302
303
  * Only rebuilds when marked stale (via markToolsAsDiscovered).
303
304
  */
304
305
  get systemRunnable() {
305
306
  if (!this.systemRunnableStale && this.cachedSystemRunnable !== undefined) {
306
307
  return this.cachedSystemRunnable;
307
308
  }
308
- const instructionsString = this.buildInstructionsString();
309
- this.cachedSystemRunnable = this.buildSystemRunnable(instructionsString);
309
+ this.cachedSystemRunnable = this.buildSystemRunnable({
310
+ stableInstructions: this.buildStableInstructionsString(),
311
+ dynamicInstructions: this.buildDynamicInstructionsString(),
312
+ });
310
313
  this.systemRunnableStale = false;
311
314
  return this.cachedSystemRunnable;
312
315
  }
@@ -316,16 +319,18 @@ class AgentContext {
316
319
  */
317
320
  initializeSystemRunnable() {
318
321
  if (this.systemRunnableStale || this.cachedSystemRunnable === undefined) {
319
- const instructionsString = this.buildInstructionsString();
320
- this.cachedSystemRunnable = this.buildSystemRunnable(instructionsString);
322
+ this.cachedSystemRunnable = this.buildSystemRunnable({
323
+ stableInstructions: this.buildStableInstructionsString(),
324
+ dynamicInstructions: this.buildDynamicInstructionsString(),
325
+ });
321
326
  this.systemRunnableStale = false;
322
327
  }
323
328
  }
324
329
  /**
325
- * Builds the raw instructions string (without creating SystemMessage).
330
+ * Builds the cacheable instructions string (without creating SystemMessage).
326
331
  * Includes agent identity preamble and handoff context when available.
327
332
  */
328
- buildInstructionsString() {
333
+ buildStableInstructionsString() {
329
334
  const parts = [];
330
335
  const identityPreamble = this.buildIdentityPreamble();
331
336
  if (identityPreamble) {
@@ -334,17 +339,27 @@ class AgentContext {
334
339
  if (this.instructions != null && this.instructions !== '') {
335
340
  parts.push(this.instructions);
336
341
  }
337
- if (this.additionalInstructions != null &&
338
- this.additionalInstructions !== '') {
339
- parts.push(this.additionalInstructions);
340
- }
341
342
  const programmaticToolsDoc = this.buildProgrammaticOnlyToolsInstructions();
342
343
  if (programmaticToolsDoc) {
343
344
  parts.push(programmaticToolsDoc);
344
345
  }
345
- // Cross-run summary: include in system prompt so the model has context
346
- // from the prior run. Mid-run summaries are injected as a HumanMessage
347
- // on the post-compaction clean slate instead (see buildSystemRunnable).
346
+ return parts.join('\n\n');
347
+ }
348
+ /**
349
+ * Builds the dynamic system-tail string (without creating SystemMessage).
350
+ * Keep this out of prompt-cache-marked content so volatile context does not
351
+ * invalidate the stable prefix.
352
+ */
353
+ buildDynamicInstructionsString() {
354
+ const parts = [];
355
+ if (this.additionalInstructions != null &&
356
+ this.additionalInstructions !== '') {
357
+ parts.push(this.additionalInstructions);
358
+ }
359
+ // Cross-run summary: include in the system tail so the model has context
360
+ // from the prior run without invalidating the cacheable prefix. Mid-run
361
+ // summaries are injected as a HumanMessage on the post-compaction clean
362
+ // slate instead (see buildSystemRunnable).
348
363
  if (this._summaryLocation === 'system_prompt' &&
349
364
  this.summaryText != null &&
350
365
  this.summaryText !== '') {
@@ -375,34 +390,20 @@ class AgentContext {
375
390
  * Build system runnable from pre-built instructions string.
376
391
  * Only called when content has actually changed.
377
392
  */
378
- buildSystemRunnable(instructionsString) {
393
+ buildSystemRunnable({ stableInstructions, dynamicInstructions, }) {
379
394
  const hasMidRunSummary = this._summaryLocation === 'user_message' &&
380
395
  this.summaryText != null &&
381
396
  this.summaryText !== '';
382
- if (!instructionsString && !hasMidRunSummary) {
397
+ if (!stableInstructions && !dynamicInstructions && !hasMidRunSummary) {
383
398
  this.systemMessageTokens = 0;
384
399
  return undefined;
385
400
  }
386
- let finalInstructions = instructionsString;
387
- let usePromptCache = false;
388
- if (this.provider === _enum.Providers.ANTHROPIC) {
389
- const anthropicOptions = this.clientOptions;
390
- if (anthropicOptions?.promptCache === true) {
391
- usePromptCache = true;
392
- finalInstructions = {
393
- content: [
394
- {
395
- type: 'text',
396
- text: instructionsString,
397
- cache_control: { type: 'ephemeral' },
398
- },
399
- ],
400
- };
401
- }
402
- }
403
- const systemMessage = instructionsString
404
- ? new messages.SystemMessage(finalInstructions)
405
- : undefined;
401
+ const usePromptCache = this.hasAnthropicPromptCache();
402
+ const systemMessage = this.buildSystemMessage({
403
+ stableInstructions,
404
+ dynamicInstructions,
405
+ usePromptCache,
406
+ });
406
407
  if (this.tokenCounter) {
407
408
  this.systemMessageTokens = systemMessage
408
409
  ? this.tokenCounter(systemMessage)
@@ -444,6 +445,52 @@ class AgentContext {
444
445
  return [...prefix, ...body];
445
446
  }).withConfig({ runName: 'prompt' });
446
447
  }
448
+ hasAnthropicPromptCache() {
449
+ if (this.provider !== _enum.Providers.ANTHROPIC) {
450
+ return false;
451
+ }
452
+ const anthropicOptions = this.clientOptions;
453
+ return anthropicOptions?.promptCache === true;
454
+ }
455
+ hasBedrockPromptCache() {
456
+ if (this.provider !== _enum.Providers.BEDROCK) {
457
+ return false;
458
+ }
459
+ const bedrockOptions = this.clientOptions;
460
+ return bedrockOptions?.promptCache === true;
461
+ }
462
+ buildSystemMessage({ stableInstructions, dynamicInstructions, usePromptCache, }) {
463
+ if (!stableInstructions && !dynamicInstructions) {
464
+ return undefined;
465
+ }
466
+ if (usePromptCache) {
467
+ const content = [];
468
+ if (stableInstructions) {
469
+ content.push({
470
+ type: 'text',
471
+ text: stableInstructions,
472
+ cache_control: { type: 'ephemeral' },
473
+ });
474
+ }
475
+ if (dynamicInstructions) {
476
+ content.push({ type: 'text', text: dynamicInstructions });
477
+ }
478
+ return new messages.SystemMessage({ content });
479
+ }
480
+ if (this.hasBedrockPromptCache() && stableInstructions) {
481
+ const content = [
482
+ { type: 'text', text: stableInstructions },
483
+ { cachePoint: { type: 'default' } },
484
+ ];
485
+ if (dynamicInstructions) {
486
+ content.push({ type: 'text', text: dynamicInstructions });
487
+ }
488
+ return new messages.SystemMessage({ content });
489
+ }
490
+ return new messages.SystemMessage([stableInstructions, dynamicInstructions]
491
+ .filter((part) => part !== '')
492
+ .join('\n\n'));
493
+ }
447
494
  /**
448
495
  * Reset context for a new run
449
496
  */
@@ -505,7 +552,44 @@ class AgentContext {
505
552
  if (!this.toolDefinitions) {
506
553
  return [];
507
554
  }
508
- return this.toolDefinitions.filter((def) => def.defer_loading !== true || this.discoveredToolNames.has(def.name));
555
+ /**
556
+ * Mirror `getEventDrivenToolsForBinding`'s gate: a definition is only
557
+ * bound to the model when its `allowed_callers` include `'direct'` and
558
+ * (if deferred) it has been discovered. Filtering by `defer_loading`
559
+ * alone left programmatic-only definitions counted in
560
+ * `toolSchemaTokens` even though they were never bound.
561
+ */
562
+ return this.toolDefinitions.filter((def) => {
563
+ const allowedCallers = def.allowed_callers ?? ['direct'];
564
+ if (!allowedCallers.includes('direct')) {
565
+ return false;
566
+ }
567
+ return (def.defer_loading !== true || this.discoveredToolNames.has(def.name));
568
+ });
569
+ }
570
+ /**
571
+ * Single source of truth for "which entries of `this.tools` should be
572
+ * treated as actually bound". Callers:
573
+ * - `getToolsForBinding` (non-event-driven branch)
574
+ * - `getEventDrivenToolsForBinding` (appends instance tools alongside
575
+ * schema-only definitions)
576
+ * - `calculateInstructionTokens` (counts schema bytes for accounting)
577
+ *
578
+ * In event-driven mode (`toolDefinitions` present) instance tools are
579
+ * appended unfiltered; outside event-driven mode they pass through
580
+ * `filterToolsForBinding`. Centralizing the decision here prevents the
581
+ * accounting/binding paths from drifting apart, which was the root
582
+ * cause of the original miscount.
583
+ */
584
+ getEffectiveInstanceTools() {
585
+ if (!this.tools) {
586
+ return undefined;
587
+ }
588
+ const isEventDriven = (this.toolDefinitions?.length ?? 0) > 0;
589
+ if (isEventDriven || !this.toolRegistry) {
590
+ return this.tools;
591
+ }
592
+ return this.filterToolsForBinding(this.tools);
509
593
  }
510
594
  /**
511
595
  * Calculate tool tokens and add to instruction tokens
@@ -520,9 +604,17 @@ class AgentContext {
520
604
  * populated after `fromConfig()` kicks off the initial calculation, so
521
605
  * callers that mutate `graphTools` must re-trigger this method to
522
606
  * refresh `toolSchemaTokens`.
607
+ *
608
+ * Use `getEffectiveInstanceTools()` so accounting reflects exactly the
609
+ * subset that `getToolsForBinding` would emit — preventing the
610
+ * worst-case-ceiling miscount that triggered spurious `empty_messages`
611
+ * preflight rejections at low `maxContextTokens`. Deferred and
612
+ * non-`'direct'` `toolDefinitions` are excluded by
613
+ * `getActiveToolDefinitions()` below.
523
614
  */
524
615
  const instanceTools = [
525
- ...(this.tools ?? []),
616
+ ...(this.getEffectiveInstanceTools() ??
617
+ []),
526
618
  ...(this.graphTools ?? []),
527
619
  ];
528
620
  if (instanceTools.length > 0) {
@@ -682,7 +774,16 @@ class AgentContext {
682
774
  */
683
775
  getTokenBudgetBreakdown(messages) {
684
776
  const maxContextTokens = this.maxContextTokens ?? 0;
685
- const toolCount = (this.tools?.length ?? 0) + this.getActiveToolDefinitions().length;
777
+ /**
778
+ * Derive `toolCount` from `getToolsForBinding()` so the diagnostic stays
779
+ * aligned with what is actually bound to the model — and with what
780
+ * `calculateInstructionTokens` counts into `toolSchemaTokens`. Using raw
781
+ * `this.tools.length` would inflate the count whenever the registry
782
+ * marks instance tools as deferred-undiscovered or non-`'direct'`,
783
+ * producing the same misleading "N tools" diagnostic this fix is meant
784
+ * to eliminate.
785
+ */
786
+ const toolCount = this.getToolsForBinding()?.length ?? 0;
686
787
  const messageCount = messages?.length ?? 0;
687
788
  let messageTokens = 0;
688
789
  if (messages != null) {
@@ -780,9 +881,7 @@ class AgentContext {
780
881
  if (this.toolDefinitions && this.toolDefinitions.length > 0) {
781
882
  return this.getEventDrivenToolsForBinding();
782
883
  }
783
- const filtered = !this.tools || !this.toolRegistry
784
- ? this.tools
785
- : this.filterToolsForBinding(this.tools);
884
+ const filtered = this.getEffectiveInstanceTools();
786
885
  if (this.graphTools && this.graphTools.length > 0) {
787
886
  return [...(filtered ?? []), ...this.graphTools];
788
887
  }
@@ -793,24 +892,14 @@ class AgentContext {
793
892
  if (!this.toolDefinitions) {
794
893
  return this.graphTools ?? [];
795
894
  }
796
- const defsToInclude = this.toolDefinitions.filter((def) => {
797
- const allowedCallers = def.allowed_callers ?? ['direct'];
798
- if (!allowedCallers.includes('direct')) {
799
- return false;
800
- }
801
- if (def.defer_loading === true &&
802
- !this.discoveredToolNames.has(def.name)) {
803
- return false;
804
- }
805
- return true;
806
- });
807
- const schemaTools = schema$1.createSchemaOnlyTools(defsToInclude);
895
+ const schemaTools = schema$1.createSchemaOnlyTools(this.getActiveToolDefinitions());
808
896
  const allTools = [...schemaTools];
809
897
  if (this.graphTools && this.graphTools.length > 0) {
810
898
  allTools.push(...this.graphTools);
811
899
  }
812
- if (this.tools && this.tools.length > 0) {
813
- allTools.push(...this.tools);
900
+ const instanceTools = this.getEffectiveInstanceTools();
901
+ if (instanceTools && instanceTools.length > 0) {
902
+ allTools.push(...instanceTools);
814
903
  }
815
904
  return allTools;
816
905
  }