@librechat/agents 3.2.34 → 3.2.36

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 (128) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +119 -9
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +25 -0
  4. package/dist/cjs/agents/projection.cjs.map +1 -0
  5. package/dist/cjs/common/enum.cjs +13 -0
  6. package/dist/cjs/common/enum.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +106 -3
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +26 -4
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +20 -0
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/invoke.cjs +49 -8
  14. package/dist/cjs/llm/invoke.cjs.map +1 -1
  15. package/dist/cjs/main.cjs +7 -0
  16. package/dist/cjs/messages/budget.cjs +23 -0
  17. package/dist/cjs/messages/budget.cjs.map +1 -0
  18. package/dist/cjs/messages/cache.cjs +1 -0
  19. package/dist/cjs/messages/cache.cjs.map +1 -1
  20. package/dist/cjs/messages/content.cjs +12 -14
  21. package/dist/cjs/messages/content.cjs.map +1 -1
  22. package/dist/cjs/messages/index.cjs +1 -0
  23. package/dist/cjs/messages/prune.cjs +31 -13
  24. package/dist/cjs/messages/prune.cjs.map +1 -1
  25. package/dist/cjs/run.cjs +7 -2
  26. package/dist/cjs/run.cjs.map +1 -1
  27. package/dist/cjs/summarization/node.cjs +12 -1
  28. package/dist/cjs/summarization/node.cjs.map +1 -1
  29. package/dist/cjs/tools/search/format.cjs +91 -2
  30. package/dist/cjs/tools/search/format.cjs.map +1 -1
  31. package/dist/cjs/tools/search/tool.cjs +4 -3
  32. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +138 -2
  34. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  35. package/dist/cjs/utils/tokens.cjs +30 -0
  36. package/dist/cjs/utils/tokens.cjs.map +1 -1
  37. package/dist/esm/agents/AgentContext.mjs +121 -11
  38. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  39. package/dist/esm/agents/projection.mjs +25 -0
  40. package/dist/esm/agents/projection.mjs.map +1 -0
  41. package/dist/esm/common/enum.mjs +13 -0
  42. package/dist/esm/common/enum.mjs.map +1 -1
  43. package/dist/esm/graphs/Graph.mjs +107 -4
  44. package/dist/esm/graphs/Graph.mjs.map +1 -1
  45. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +26 -4
  46. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  47. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +20 -0
  48. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  49. package/dist/esm/llm/invoke.mjs +49 -8
  50. package/dist/esm/llm/invoke.mjs.map +1 -1
  51. package/dist/esm/main.mjs +6 -4
  52. package/dist/esm/messages/budget.mjs +23 -0
  53. package/dist/esm/messages/budget.mjs.map +1 -0
  54. package/dist/esm/messages/cache.mjs +1 -1
  55. package/dist/esm/messages/cache.mjs.map +1 -1
  56. package/dist/esm/messages/content.mjs +12 -15
  57. package/dist/esm/messages/content.mjs.map +1 -1
  58. package/dist/esm/messages/index.mjs +1 -0
  59. package/dist/esm/messages/prune.mjs +31 -13
  60. package/dist/esm/messages/prune.mjs.map +1 -1
  61. package/dist/esm/run.mjs +7 -2
  62. package/dist/esm/run.mjs.map +1 -1
  63. package/dist/esm/summarization/node.mjs +12 -1
  64. package/dist/esm/summarization/node.mjs.map +1 -1
  65. package/dist/esm/tools/search/format.mjs +91 -2
  66. package/dist/esm/tools/search/format.mjs.map +1 -1
  67. package/dist/esm/tools/search/tool.mjs +4 -3
  68. package/dist/esm/tools/search/tool.mjs.map +1 -1
  69. package/dist/esm/tools/subagent/SubagentExecutor.mjs +138 -2
  70. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  71. package/dist/esm/utils/tokens.mjs +30 -1
  72. package/dist/esm/utils/tokens.mjs.map +1 -1
  73. package/dist/types/agents/AgentContext.d.ts +37 -4
  74. package/dist/types/agents/projection.d.ts +26 -0
  75. package/dist/types/common/enum.d.ts +13 -0
  76. package/dist/types/graphs/Graph.d.ts +8 -1
  77. package/dist/types/index.d.ts +1 -0
  78. package/dist/types/llm/invoke.d.ts +1 -1
  79. package/dist/types/messages/budget.d.ts +11 -0
  80. package/dist/types/messages/cache.d.ts +7 -0
  81. package/dist/types/messages/content.d.ts +5 -0
  82. package/dist/types/messages/index.d.ts +1 -0
  83. package/dist/types/messages/prune.d.ts +4 -0
  84. package/dist/types/run.d.ts +1 -0
  85. package/dist/types/tools/search/format.d.ts +4 -1
  86. package/dist/types/tools/search/types.d.ts +7 -0
  87. package/dist/types/tools/subagent/SubagentExecutor.d.ts +11 -1
  88. package/dist/types/types/graph.d.ts +89 -3
  89. package/dist/types/types/run.d.ts +13 -0
  90. package/dist/types/utils/tokens.d.ts +7 -0
  91. package/package.json +1 -1
  92. package/src/agents/AgentContext.ts +172 -8
  93. package/src/agents/__tests__/AgentContext.test.ts +235 -2
  94. package/src/agents/__tests__/projection.test.ts +73 -0
  95. package/src/agents/projection.ts +46 -0
  96. package/src/common/enum.ts +13 -0
  97. package/src/graphs/Graph.ts +168 -0
  98. package/src/index.ts +3 -0
  99. package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +317 -0
  100. package/src/llm/anthropic/utils/message_inputs.ts +78 -16
  101. package/src/llm/bedrock/utils/cross-provider-reasoning.test.ts +131 -0
  102. package/src/llm/bedrock/utils/message_inputs.ts +35 -0
  103. package/src/llm/invoke.test.ts +79 -1
  104. package/src/llm/invoke.ts +58 -4
  105. package/src/messages/budget.ts +32 -0
  106. package/src/messages/cache.ts +1 -1
  107. package/src/messages/content.ts +24 -32
  108. package/src/messages/index.ts +1 -0
  109. package/src/messages/prune.ts +39 -2
  110. package/src/run.ts +5 -0
  111. package/src/scripts/subagent-usage-sink.ts +176 -0
  112. package/src/specs/context-accuracy.live.test.ts +409 -0
  113. package/src/specs/context-usage-event.test.ts +117 -0
  114. package/src/specs/context-usage.live.test.ts +297 -0
  115. package/src/specs/prune.test.ts +51 -1
  116. package/src/specs/subagent.test.ts +124 -1
  117. package/src/summarization/__tests__/node.test.ts +60 -1
  118. package/src/summarization/node.ts +20 -1
  119. package/src/tools/__tests__/SubagentExecutor.test.ts +443 -1
  120. package/src/tools/search/format.test.ts +242 -0
  121. package/src/tools/search/format.ts +122 -5
  122. package/src/tools/search/tool.ts +5 -1
  123. package/src/tools/search/types.ts +7 -0
  124. package/src/tools/subagent/SubagentExecutor.ts +221 -3
  125. package/src/types/graph.ts +94 -1
  126. package/src/types/run.ts +13 -0
  127. package/src/utils/__tests__/apportion.test.ts +32 -0
  128. package/src/utils/tokens.ts +33 -0
@@ -1312,16 +1312,36 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
1312
1312
  originalToolContent?: Map<number, string>;
1313
1313
  calibrationRatio?: number;
1314
1314
  resolvedInstructionOverhead?: number;
1315
+ /** Usable budget this call: maxTokens minus output reserve */
1316
+ contextBudget?: number;
1317
+ /** Calibrated instruction overhead actually applied this call */
1318
+ effectiveInstructionTokens?: number;
1315
1319
  } {
1316
1320
  if (params.messages.length === 0) {
1321
+ /** Post-compaction calls still invoke the model — report the same
1322
+ * reserve-adjusted budget fields as the populated paths */
1323
+ const emptyInstructionTokens =
1324
+ factoryParams.getInstructionTokens?.() ?? 0;
1325
+ const emptyReserveRatio =
1326
+ factoryParams.reserveRatio ?? DEFAULT_RESERVE_RATIO;
1327
+ const emptyBudget =
1328
+ factoryParams.maxTokens -
1329
+ (emptyReserveRatio > 0 && emptyReserveRatio < 1
1330
+ ? Math.round(factoryParams.maxTokens * emptyReserveRatio)
1331
+ : 0);
1317
1332
  return {
1318
1333
  context: [],
1319
1334
  indexTokenCountMap,
1320
1335
  messagesToRefine: [],
1321
1336
  prePruneContextTokens: 0,
1322
- remainingContextTokens: factoryParams.maxTokens,
1337
+ remainingContextTokens: Math.max(
1338
+ 0,
1339
+ emptyBudget - emptyInstructionTokens
1340
+ ),
1323
1341
  calibrationRatio,
1324
1342
  resolvedInstructionOverhead: bestInstructionOverhead,
1343
+ contextBudget: emptyBudget,
1344
+ effectiveInstructionTokens: emptyInstructionTokens,
1325
1345
  };
1326
1346
  }
1327
1347
 
@@ -1549,6 +1569,8 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
1549
1569
  pruningBudget > 0 ? calibratedTotalTokens / pruningBudget : 0,
1550
1570
  calibrationRatio,
1551
1571
  resolvedInstructionOverhead: bestInstructionOverhead,
1572
+ contextBudget: pruningBudget,
1573
+ effectiveInstructionTokens: currentInstructionTokens,
1552
1574
  };
1553
1575
  }
1554
1576
 
@@ -1752,6 +1774,8 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
1752
1774
  originalToolContent.size > 0 ? originalToolContent : undefined,
1753
1775
  calibrationRatio,
1754
1776
  resolvedInstructionOverhead: bestInstructionOverhead,
1777
+ contextBudget: pruningBudget,
1778
+ effectiveInstructionTokens: currentInstructionTokens,
1755
1779
  };
1756
1780
  }
1757
1781
 
@@ -2099,9 +2123,20 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2099
2123
  }
2100
2124
  }
2101
2125
 
2126
+ /** Scale raw-space remaining back to calibrated/provider units so it is
2127
+ * directly comparable with pruningBudget and prePruneContextTokens */
2128
+ const rawRemaining = Math.max(
2129
+ 0,
2130
+ initialRemainingContextTokens + reclaimedTokens
2131
+ );
2102
2132
  const remainingContextTokens = Math.max(
2103
2133
  0,
2104
- Math.min(pruningBudget, initialRemainingContextTokens + reclaimedTokens)
2134
+ Math.min(
2135
+ pruningBudget,
2136
+ calibrationRatio > 0
2137
+ ? Math.round(rawRemaining * calibrationRatio)
2138
+ : rawRemaining
2139
+ )
2105
2140
  );
2106
2141
 
2107
2142
  runThinkingStartIndex = thinkingStartIndex ?? -1;
@@ -2123,6 +2158,8 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2123
2158
  originalToolContent.size > 0 ? originalToolContent : undefined,
2124
2159
  calibrationRatio,
2125
2160
  resolvedInstructionOverhead: bestInstructionOverhead,
2161
+ contextBudget: pruningBudget,
2162
+ effectiveInstructionTokens: currentInstructionTokens,
2126
2163
  };
2127
2164
  };
2128
2165
  }
package/src/run.ts CHANGED
@@ -78,6 +78,7 @@ const CUSTOM_GRAPH_EVENTS = new Set<string>([
78
78
  GraphEvents.ON_SUMMARIZE_COMPLETE,
79
79
  GraphEvents.ON_SUBAGENT_UPDATE,
80
80
  GraphEvents.ON_AGENT_LOG,
81
+ GraphEvents.ON_CONTEXT_USAGE,
81
82
  GraphEvents.ON_CUSTOM_EVENT,
82
83
  ]);
83
84
 
@@ -129,6 +130,7 @@ export class Run<_T extends t.BaseGraphState> {
129
130
  private toolOutputReferences?: t.ToolOutputReferencesConfig;
130
131
  private eagerEventToolExecution?: t.EagerEventToolExecutionConfig;
131
132
  private toolExecution?: t.ToolExecutionConfig;
133
+ private subagentUsageSink?: t.SubagentUsageSink;
132
134
  private indexTokenCountMap?: Record<string, number>;
133
135
  calibrationRatio: number = 1;
134
136
  graphRunnable?: t.CompiledStateWorkflow;
@@ -176,6 +178,7 @@ export class Run<_T extends t.BaseGraphState> {
176
178
  this.toolOutputReferences = config.toolOutputReferences;
177
179
  this.eagerEventToolExecution = config.eagerEventToolExecution;
178
180
  this.toolExecution = config.toolExecution;
181
+ this.subagentUsageSink = config.subagentUsageSink;
179
182
 
180
183
  if (!config.graphConfig) {
181
184
  throw new Error('Graph config not provided');
@@ -249,6 +252,7 @@ export class Run<_T extends t.BaseGraphState> {
249
252
  tokenCounter: this.tokenCounter,
250
253
  indexTokenCountMap: this.indexTokenCountMap,
251
254
  calibrationRatio: this.calibrationRatio,
255
+ subagentUsageSink: this.subagentUsageSink,
252
256
  });
253
257
  /** Propagate compile options from graph config */
254
258
  standardGraph.compileOptions = this.applyHITLCheckpointerFallback(
@@ -276,6 +280,7 @@ export class Run<_T extends t.BaseGraphState> {
276
280
  tokenCounter: this.tokenCounter,
277
281
  indexTokenCountMap: this.indexTokenCountMap,
278
282
  calibrationRatio: this.calibrationRatio,
283
+ subagentUsageSink: this.subagentUsageSink,
279
284
  });
280
285
 
281
286
  multiAgentGraph.compileOptions =
@@ -0,0 +1,176 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import { HumanMessage } from '@langchain/core/messages';
5
+ import type { UsageMetadata } from '@langchain/core/messages';
6
+ import type * as t from '@/types';
7
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
8
+ import { Providers, GraphEvents } from '@/common';
9
+ import { Run } from '@/run';
10
+
11
+ /**
12
+ * Live verification for `subagentUsageSink` (host billing of subagent
13
+ * child-run model usage).
14
+ *
15
+ * Runs a supervisor that MUST delegate to a "researcher" subagent, then
16
+ * asserts:
17
+ * 1. The host's CHAT_MODEL_END handler collected the PARENT's calls only.
18
+ * 2. The sink received one event per CHILD model call, tagged with the
19
+ * subagent type, child run id, and the child's model/provider.
20
+ * 3. Child usage has real token counts (the previously-unbilled tokens).
21
+ *
22
+ * Usage:
23
+ * OPENAI_API_KEY=... npx ts-node -r tsconfig-paths/register src/scripts/subagent-usage-sink.ts
24
+ *
25
+ * Or with Anthropic:
26
+ * ANTHROPIC_API_KEY=... npx ts-node -r tsconfig-paths/register src/scripts/subagent-usage-sink.ts --provider anthropic
27
+ */
28
+
29
+ const useAnthropic =
30
+ process.argv.includes('--provider') &&
31
+ process.argv[process.argv.indexOf('--provider') + 1] === 'anthropic';
32
+
33
+ const provider = useAnthropic ? Providers.ANTHROPIC : Providers.OPENAI;
34
+ const apiKey = useAnthropic
35
+ ? process.env.ANTHROPIC_API_KEY
36
+ : process.env.OPENAI_API_KEY;
37
+ const modelName = useAnthropic ? 'claude-sonnet-4-20250514' : 'gpt-4o-mini';
38
+
39
+ if (!apiKey) {
40
+ console.error(
41
+ `Missing ${useAnthropic ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'} environment variable`
42
+ );
43
+ process.exit(1);
44
+ }
45
+
46
+ async function main(): Promise<void> {
47
+ console.log('=== Subagent Usage Sink Live Verification ===\n');
48
+ console.log(`Provider: ${provider}`);
49
+ console.log(`Model: ${modelName}\n`);
50
+
51
+ const parentAgent: t.AgentInputs = {
52
+ agentId: 'supervisor',
53
+ provider,
54
+ clientOptions: { modelName, apiKey },
55
+ instructions: `You are a supervisor agent. For ANY user question, you MUST delegate to the "researcher" subagent via the subagent tool — never answer directly. After the subagent returns, give the user a one-sentence final answer.`,
56
+ maxContextTokens: 16000,
57
+ subagentConfigs: [
58
+ {
59
+ type: 'researcher',
60
+ name: 'Research Specialist',
61
+ description: 'Researches questions and returns concise answers.',
62
+ agentInputs: {
63
+ agentId: 'researcher',
64
+ provider,
65
+ clientOptions: { modelName, apiKey },
66
+ instructions:
67
+ 'You are a research specialist. Answer the task in one or two sentences.',
68
+ maxContextTokens: 8000,
69
+ },
70
+ },
71
+ ],
72
+ };
73
+
74
+ const collectedUsage: UsageMetadata[] = [];
75
+ const sunkEvents: t.SubagentUsageEvent[] = [];
76
+
77
+ const runId = `usage-sink-live-${Date.now()}`;
78
+ const run = await Run.create<t.IState>({
79
+ runId,
80
+ graphConfig: {
81
+ type: 'standard',
82
+ agents: [parentAgent],
83
+ },
84
+ returnContent: true,
85
+ customHandlers: {
86
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
87
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
88
+ },
89
+ subagentUsageSink: (event) => {
90
+ sunkEvents.push(event);
91
+ },
92
+ });
93
+
94
+ const callerConfig = {
95
+ configurable: { thread_id: `usage-sink-${Date.now()}` },
96
+ streamMode: 'values' as const,
97
+ version: 'v2' as const,
98
+ };
99
+
100
+ await run.processStream(
101
+ {
102
+ messages: [
103
+ new HumanMessage(
104
+ 'In what year was the Eiffel Tower completed? Use the researcher subagent.'
105
+ ),
106
+ ],
107
+ },
108
+ callerConfig
109
+ );
110
+
111
+ console.log('\n--- Parent collectedUsage (CHAT_MODEL_END handler) ---');
112
+ console.dir(collectedUsage, { depth: null });
113
+
114
+ console.log('\n--- Subagent usage sink events ---');
115
+ console.dir(sunkEvents, { depth: null });
116
+
117
+ const failures: string[] = [];
118
+
119
+ if (collectedUsage.length < 2) {
120
+ failures.push(
121
+ `expected >= 2 parent model calls in collectedUsage, got ${collectedUsage.length}`
122
+ );
123
+ }
124
+ if (sunkEvents.length === 0) {
125
+ failures.push('sink received NO child usage events');
126
+ }
127
+ for (const event of sunkEvents) {
128
+ if (event.subagentType !== 'researcher') {
129
+ failures.push(`unexpected subagentType: ${event.subagentType}`);
130
+ }
131
+ if (event.runId !== runId) {
132
+ failures.push(`event.runId mismatch: ${event.runId}`);
133
+ }
134
+ if (!event.subagentRunId.startsWith(`${runId}_sub_`)) {
135
+ failures.push(`event.subagentRunId mismatch: ${event.subagentRunId}`);
136
+ }
137
+ if (event.provider !== provider) {
138
+ failures.push(`event.provider mismatch: ${event.provider}`);
139
+ }
140
+ if (event.model == null || event.model === '') {
141
+ failures.push('event.model missing');
142
+ }
143
+ const input = Number(event.usage.input_tokens) || 0;
144
+ const output = Number(event.usage.output_tokens) || 0;
145
+ if (input <= 0 || output <= 0) {
146
+ failures.push(
147
+ `child usage has non-positive tokens: input=${input} output=${output}`
148
+ );
149
+ }
150
+ }
151
+
152
+ const childTotal = sunkEvents.reduce(
153
+ (sum, e) =>
154
+ sum +
155
+ (Number(e.usage.input_tokens) || 0) +
156
+ (Number(e.usage.output_tokens) || 0),
157
+ 0
158
+ );
159
+ console.log(
160
+ `\nChild tokens that were previously invisible to billing: ${childTotal}`
161
+ );
162
+
163
+ if (failures.length > 0) {
164
+ console.error('\nFAIL:');
165
+ for (const failure of failures) {
166
+ console.error(` - ${failure}`);
167
+ }
168
+ process.exit(1);
169
+ }
170
+ console.log('\nPASS: subagent child usage reported through the sink.');
171
+ }
172
+
173
+ main().catch((error) => {
174
+ console.error(error);
175
+ process.exit(1);
176
+ });