@librechat/agents 3.6.16 → 3.7.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 (187) hide show
  1. package/dist/cjs/_virtual/_rolldown/runtime.cjs +10 -0
  2. package/dist/cjs/agents/AgentContext.cjs +11 -2
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/graphs/Graph.cjs +12 -9
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/lazyRequire.cjs +33 -0
  7. package/dist/cjs/lazyRequire.cjs.map +1 -0
  8. package/dist/cjs/llm/anthropic/index.cjs +13 -0
  9. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  10. package/dist/cjs/llm/bedrock/index.cjs +10 -1
  11. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  12. package/dist/cjs/llm/bedrock/toolCache.cjs +3 -2
  13. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  14. package/dist/cjs/llm/google/index.cjs +10 -1
  15. package/dist/cjs/llm/google/index.cjs.map +1 -1
  16. package/dist/cjs/llm/init.cjs +6 -4
  17. package/dist/cjs/llm/init.cjs.map +1 -1
  18. package/dist/cjs/llm/invoke.cjs +56 -9
  19. package/dist/cjs/llm/invoke.cjs.map +1 -1
  20. package/dist/cjs/llm/mistral/index.cjs +9 -0
  21. package/dist/cjs/llm/mistral/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openai/index.cjs +42 -2
  23. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  24. package/dist/cjs/llm/openrouter/index.cjs +12 -3
  25. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  26. package/dist/cjs/llm/openrouter/toolCache.cjs +3 -2
  27. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  28. package/dist/cjs/llm/providerRegistry.cjs +15 -3
  29. package/dist/cjs/llm/providerRegistry.cjs.map +1 -1
  30. package/dist/cjs/llm/providers.cjs +18 -22
  31. package/dist/cjs/llm/providers.cjs.map +1 -1
  32. package/dist/cjs/llm/providers.eager.cjs +21 -0
  33. package/dist/cjs/llm/providers.eager.cjs.map +1 -0
  34. package/dist/cjs/llm/vertexai/index.cjs +17 -0
  35. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  36. package/dist/cjs/main.cjs +19 -11
  37. package/dist/cjs/messages/content.cjs +8 -5
  38. package/dist/cjs/messages/content.cjs.map +1 -1
  39. package/dist/cjs/messages/core.cjs +9 -4
  40. package/dist/cjs/messages/core.cjs.map +1 -1
  41. package/dist/cjs/messages/format.cjs +15 -5
  42. package/dist/cjs/messages/format.cjs.map +1 -1
  43. package/dist/cjs/messages/index.cjs +2 -1
  44. package/dist/cjs/messages/projectionInvariant.cjs +74 -0
  45. package/dist/cjs/messages/projectionInvariant.cjs.map +1 -0
  46. package/dist/cjs/messages/provenance.cjs +30 -8
  47. package/dist/cjs/messages/provenance.cjs.map +1 -1
  48. package/dist/cjs/messages/prune.cjs +42 -8
  49. package/dist/cjs/messages/prune.cjs.map +1 -1
  50. package/dist/cjs/messages/recency.cjs +202 -7
  51. package/dist/cjs/messages/recency.cjs.map +1 -1
  52. package/dist/cjs/messages/toolResultTypes.cjs +1 -0
  53. package/dist/cjs/run.cjs +7 -2
  54. package/dist/cjs/run.cjs.map +1 -1
  55. package/dist/cjs/session/AgentSession.cjs +4 -23
  56. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  57. package/dist/cjs/session/deriveMessages.cjs +25 -0
  58. package/dist/cjs/session/deriveMessages.cjs.map +1 -0
  59. package/dist/cjs/session/index.cjs +1 -0
  60. package/dist/cjs/summarization/node.cjs +13 -6
  61. package/dist/cjs/summarization/node.cjs.map +1 -1
  62. package/dist/cjs/tools/Calculator.cjs +7 -4
  63. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  64. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  65. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +65 -8
  66. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -1
  67. package/dist/cjs/utils/llm.cjs +16 -0
  68. package/dist/cjs/utils/llm.cjs.map +1 -1
  69. package/dist/esm/_virtual/_rolldown/runtime.mjs +14 -0
  70. package/dist/esm/agents/AgentContext.mjs +11 -2
  71. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  72. package/dist/esm/graphs/Graph.mjs +12 -9
  73. package/dist/esm/graphs/Graph.mjs.map +1 -1
  74. package/dist/esm/lazyRequire.mjs +30 -0
  75. package/dist/esm/lazyRequire.mjs.map +1 -0
  76. package/dist/esm/llm/anthropic/index.mjs +6 -1
  77. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  78. package/dist/esm/llm/bedrock/index.mjs +4 -2
  79. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  80. package/dist/esm/llm/bedrock/toolCache.mjs +3 -2
  81. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  82. package/dist/esm/llm/google/index.mjs +4 -2
  83. package/dist/esm/llm/google/index.mjs.map +1 -1
  84. package/dist/esm/llm/init.mjs +7 -5
  85. package/dist/esm/llm/init.mjs.map +1 -1
  86. package/dist/esm/llm/invoke.mjs +56 -9
  87. package/dist/esm/llm/invoke.mjs.map +1 -1
  88. package/dist/esm/llm/mistral/index.mjs +3 -1
  89. package/dist/esm/llm/mistral/index.mjs.map +1 -1
  90. package/dist/esm/llm/openai/index.mjs +32 -10
  91. package/dist/esm/llm/openai/index.mjs.map +1 -1
  92. package/dist/esm/llm/openrouter/index.mjs +3 -1
  93. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  94. package/dist/esm/llm/openrouter/toolCache.mjs +3 -2
  95. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  96. package/dist/esm/llm/providerRegistry.mjs +15 -3
  97. package/dist/esm/llm/providerRegistry.mjs.map +1 -1
  98. package/dist/esm/llm/providers.eager.mjs +22 -0
  99. package/dist/esm/llm/providers.eager.mjs.map +1 -0
  100. package/dist/esm/llm/providers.mjs +19 -23
  101. package/dist/esm/llm/providers.mjs.map +1 -1
  102. package/dist/esm/llm/vertexai/index.mjs +8 -1
  103. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  104. package/dist/esm/main.mjs +10 -11
  105. package/dist/esm/messages/content.mjs +8 -6
  106. package/dist/esm/messages/content.mjs.map +1 -1
  107. package/dist/esm/messages/core.mjs +9 -5
  108. package/dist/esm/messages/core.mjs.map +1 -1
  109. package/dist/esm/messages/format.mjs +16 -6
  110. package/dist/esm/messages/format.mjs.map +1 -1
  111. package/dist/esm/messages/index.mjs +2 -1
  112. package/dist/esm/messages/projectionInvariant.mjs +72 -0
  113. package/dist/esm/messages/projectionInvariant.mjs.map +1 -0
  114. package/dist/esm/messages/provenance.mjs +30 -9
  115. package/dist/esm/messages/provenance.mjs.map +1 -1
  116. package/dist/esm/messages/prune.mjs +42 -9
  117. package/dist/esm/messages/prune.mjs.map +1 -1
  118. package/dist/esm/messages/recency.mjs +201 -8
  119. package/dist/esm/messages/recency.mjs.map +1 -1
  120. package/dist/esm/messages/toolResultTypes.mjs +1 -1
  121. package/dist/esm/run.mjs +8 -3
  122. package/dist/esm/run.mjs.map +1 -1
  123. package/dist/esm/session/AgentSession.mjs +4 -23
  124. package/dist/esm/session/AgentSession.mjs.map +1 -1
  125. package/dist/esm/session/deriveMessages.mjs +25 -0
  126. package/dist/esm/session/deriveMessages.mjs.map +1 -0
  127. package/dist/esm/session/index.mjs +1 -0
  128. package/dist/esm/summarization/node.mjs +14 -7
  129. package/dist/esm/summarization/node.mjs.map +1 -1
  130. package/dist/esm/tools/Calculator.mjs +7 -2
  131. package/dist/esm/tools/Calculator.mjs.map +1 -1
  132. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  133. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +65 -8
  134. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -1
  135. package/dist/esm/utils/llm.mjs +15 -1
  136. package/dist/esm/utils/llm.mjs.map +1 -1
  137. package/dist/types/agents/AgentContext.d.ts +6 -1
  138. package/dist/types/index.d.ts +3 -3
  139. package/dist/types/lazyRequire.d.ts +8 -0
  140. package/dist/types/llm/providerRegistry.d.ts +11 -0
  141. package/dist/types/llm/providers.eager.d.ts +1 -0
  142. package/dist/types/messages/content.d.ts +4 -1
  143. package/dist/types/messages/core.d.ts +2 -0
  144. package/dist/types/messages/format.d.ts +6 -0
  145. package/dist/types/messages/index.d.ts +1 -0
  146. package/dist/types/messages/projectionInvariant.d.ts +25 -0
  147. package/dist/types/messages/provenance.d.ts +10 -0
  148. package/dist/types/messages/prune.d.ts +5 -4
  149. package/dist/types/messages/recency.d.ts +30 -18
  150. package/dist/types/session/deriveMessages.d.ts +11 -0
  151. package/dist/types/session/index.d.ts +2 -0
  152. package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +13 -1
  153. package/dist/types/types/graph.d.ts +1 -1
  154. package/dist/types/types/subagentTasks.d.ts +22 -0
  155. package/dist/types/types/summarize.d.ts +15 -13
  156. package/dist/types/utils/llm.d.ts +10 -0
  157. package/package.json +60 -1
  158. package/src/agents/AgentContext.ts +19 -5
  159. package/src/graphs/Graph.ts +11 -9
  160. package/src/index.ts +3 -3
  161. package/src/lazyRequire.ts +67 -0
  162. package/src/llm/bedrock/toolCache.ts +6 -2
  163. package/src/llm/init.ts +15 -8
  164. package/src/llm/invoke.ts +102 -23
  165. package/src/llm/openrouter/toolCache.ts +8 -2
  166. package/src/llm/providerRegistry.ts +41 -1
  167. package/src/llm/providers.eager.ts +24 -0
  168. package/src/llm/providers.ts +63 -49
  169. package/src/messages/content.ts +20 -10
  170. package/src/messages/core.ts +16 -11
  171. package/src/messages/format.ts +29 -5
  172. package/src/messages/index.ts +1 -0
  173. package/src/messages/projectionInvariant.ts +134 -0
  174. package/src/messages/provenance.ts +60 -18
  175. package/src/messages/prune.ts +86 -11
  176. package/src/messages/recency.ts +429 -27
  177. package/src/run.ts +14 -6
  178. package/src/session/AgentSession.ts +4 -30
  179. package/src/session/deriveMessages.ts +37 -0
  180. package/src/session/index.ts +2 -0
  181. package/src/summarization/node.ts +37 -15
  182. package/src/tools/Calculator.ts +9 -2
  183. package/src/tools/subagent/InMemorySubagentTaskStore.ts +139 -6
  184. package/src/types/graph.ts +1 -0
  185. package/src/types/subagentTasks.ts +28 -0
  186. package/src/types/summarize.ts +15 -13
  187. package/src/utils/llm.ts +39 -0
@@ -5,6 +5,22 @@ import type { InjectedMessage } from './tools';
5
5
  export type SubagentTaskStatus = 'running' | 'completed' | 'error' | 'cancelled';
6
6
  /** Where a pending parent message may enter the child run. */
7
7
  export type SubagentTaskBoundary = 'preempt' | 'tool' | 'turn';
8
+ /**
9
+ * Lifecycle of one parent-to-child message after the task store accepts it.
10
+ * Hosts may render a transient `submitted` state before this authoritative
11
+ * receipt exists; that transport state is intentionally not persisted here.
12
+ */
13
+ export type SubagentTaskControlReceiptStatus = 'accepted' | 'applied' | 'rejected' | 'failed';
14
+ /** Bounded authoritative receipt for one steer, queue, or interrupt command. */
15
+ export interface SubagentTaskControlReceipt {
16
+ controlId: string;
17
+ action: 'steer' | 'queue' | 'interrupt';
18
+ status: SubagentTaskControlReceiptStatus;
19
+ createdAt: number;
20
+ updatedAt: number;
21
+ boundary?: SubagentTaskBoundary;
22
+ reason?: 'withdrawn' | 'task_completed' | 'task_cancelled' | 'task_failed';
23
+ }
8
24
  /** Parent-to-child control operations accepted while a task is running. */
9
25
  export type SubagentTaskControlCommand = {
10
26
  action: 'steer' | 'queue' | 'interrupt';
@@ -39,6 +55,12 @@ export interface SubagentTaskSnapshot {
39
55
  resultAvailable: boolean;
40
56
  resultClaimed: boolean;
41
57
  pendingControls: number;
58
+ /**
59
+ * Bounded receipts emitted by stores that support authoritative control
60
+ * tracking. Optional so legacy and custom stores remain compatible during
61
+ * rolling upgrades.
62
+ */
63
+ controlReceipts?: SubagentTaskControlReceipt[];
42
64
  progress?: SubagentTaskProgress;
43
65
  error?: string;
44
66
  }
@@ -5,25 +5,27 @@ export type SummarizationTrigger = {
5
5
  value: number;
6
6
  };
7
7
  /**
8
- * Controls how many recent messages are preserved verbatim during
9
- * compaction. The most recent user-led turn is always preserved
10
- * regardless of these caps, so a single oversized first message is
11
- * never destroyed by summarization.
8
+ * Controls how much recent context is preserved verbatim during compaction.
9
+ * User-turn boundaries are preferred. Under context pressure, older closed
10
+ * tool units inside an otherwise indivisible turn may be summarized while a
11
+ * token-priced recent tail is retained. A lone user payload stays intact.
12
12
  */
13
13
  export type RetainRecentConfig = {
14
14
  /**
15
- * Maximum number of recent user-led turns to keep in the tail. A turn
16
- * begins at a HumanMessage and includes every following AIMessage and
17
- * ToolMessage up to (but not including) the next HumanMessage. Cutting
18
- * at turn boundaries guarantees tool_use / tool_result pairs are never
19
- * split. Set to `0` to disable the recency window (legacy behavior:
20
- * summarize everything). Defaults to `2`.
15
+ * Maximum number of recent user-led turns to keep in the tail. A turn begins
16
+ * at a user-authored HumanMessage and includes every following AIMessage and
17
+ * tool result up to the next user-authored HumanMessage. Provider-native
18
+ * HumanMessages containing only tool results remain in the current turn.
19
+ * Set to `0` to disable the recency window (legacy behavior: summarize
20
+ * everything). Defaults to `2`.
21
21
  */
22
22
  turns?: number;
23
23
  /**
24
- * Optional cap on retained-recent tokens beyond the most recent turn.
25
- * Older turns are added whole only while cumulative tokens stay below
26
- * the cap. Defaults to undefined (no cap; bounded only by `turns`).
24
+ * Optional retained-recent token budget. Older turns are added whole only
25
+ * while cumulative tokens stay below the cap. If a tool-heavy history has
26
+ * no compactable turn-level head, this is also the minimum recent tail kept
27
+ * behind a pairing-balanced intra-turn cut. When omitted, that fallback
28
+ * retains 16% of the configured context window.
27
29
  */
28
30
  tokens?: number;
29
31
  };
@@ -1,4 +1,14 @@
1
1
  import type { ProviderName } from '@/types';
2
+ /** Walks the constructor chain for LangChain's `lc_name` serialization ids.
3
+ * Constructor identity is unusable across lazily loaded module formats — the
4
+ * CJS and ESM builds carry distinct classes — so guards match on the ids the
5
+ * relevant classes declare instead of on `instanceof`. */
6
+ export declare function constructorChainHasLcName(model: unknown, names: ReadonlySet<string>): boolean;
7
+ /** Matches exactly the instances that were `instanceof` this package's
8
+ * `ChatOpenAI`/`AzureChatOpenAI`: only those classes declare the
9
+ * `LibreChatOpenAI`/`LibreChatAzureOpenAI` ids, their subclasses keep them in
10
+ * the chain, and upstream `@langchain/openai` classes never carry them. */
11
+ export declare function isLibreChatOpenAIModel(model: unknown): model is import('@/llm/openai').ChatOpenAI | import('@/llm/openai').AzureChatOpenAI;
2
12
  export declare function isOpenAILike(provider?: ProviderName): boolean;
3
13
  export declare function isGoogleLike(provider?: ProviderName): boolean;
4
14
  /** Returns true for native Anthropic or Bedrock running a Claude model. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.6.16",
3
+ "version": "3.7.1",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -78,6 +78,41 @@
78
78
  "types": "./dist/types/langchain/utils/env.d.ts",
79
79
  "import": "./dist/esm/langchain/utils/env.mjs",
80
80
  "require": "./dist/cjs/langchain/utils/env.cjs"
81
+ },
82
+ "./llm/openai": {
83
+ "types": "./dist/types/llm/openai/index.d.ts",
84
+ "import": "./dist/esm/llm/openai/index.mjs",
85
+ "require": "./dist/cjs/llm/openai/index.cjs"
86
+ },
87
+ "./llm/mistral": {
88
+ "types": "./dist/types/llm/mistral/index.d.ts",
89
+ "import": "./dist/esm/llm/mistral/index.mjs",
90
+ "require": "./dist/cjs/llm/mistral/index.cjs"
91
+ },
92
+ "./llm/anthropic": {
93
+ "types": "./dist/types/llm/anthropic/index.d.ts",
94
+ "import": "./dist/esm/llm/anthropic/index.mjs",
95
+ "require": "./dist/cjs/llm/anthropic/index.cjs"
96
+ },
97
+ "./llm/google": {
98
+ "types": "./dist/types/llm/google/index.d.ts",
99
+ "import": "./dist/esm/llm/google/index.mjs",
100
+ "require": "./dist/cjs/llm/google/index.cjs"
101
+ },
102
+ "./llm/bedrock": {
103
+ "types": "./dist/types/llm/bedrock/index.d.ts",
104
+ "import": "./dist/esm/llm/bedrock/index.mjs",
105
+ "require": "./dist/cjs/llm/bedrock/index.cjs"
106
+ },
107
+ "./llm/vertexai": {
108
+ "types": "./dist/types/llm/vertexai/index.d.ts",
109
+ "import": "./dist/esm/llm/vertexai/index.mjs",
110
+ "require": "./dist/cjs/llm/vertexai/index.cjs"
111
+ },
112
+ "./llm/openrouter": {
113
+ "types": "./dist/types/llm/openrouter/index.d.ts",
114
+ "import": "./dist/esm/llm/openrouter/index.mjs",
115
+ "require": "./dist/cjs/llm/openrouter/index.cjs"
81
116
  }
82
117
  },
83
118
  "typesVersions": {
@@ -96,6 +131,27 @@
96
131
  ],
97
132
  "responses": [
98
133
  "dist/types/responses/index.d.ts"
134
+ ],
135
+ "llm/openai": [
136
+ "./dist/types/llm/openai/index.d.ts"
137
+ ],
138
+ "llm/mistral": [
139
+ "./dist/types/llm/mistral/index.d.ts"
140
+ ],
141
+ "llm/anthropic": [
142
+ "./dist/types/llm/anthropic/index.d.ts"
143
+ ],
144
+ "llm/google": [
145
+ "./dist/types/llm/google/index.d.ts"
146
+ ],
147
+ "llm/bedrock": [
148
+ "./dist/types/llm/bedrock/index.d.ts"
149
+ ],
150
+ "llm/vertexai": [
151
+ "./dist/types/llm/vertexai/index.d.ts"
152
+ ],
153
+ "llm/openrouter": [
154
+ "./dist/types/llm/openrouter/index.d.ts"
99
155
  ]
100
156
  }
101
157
  },
@@ -162,6 +218,9 @@
162
218
  "tool_search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tool_search.ts",
163
219
  "bench:cache": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-prompt-cache.ts",
164
220
  "bench:context-pressure": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-context-pressure-cache.ts",
221
+ "bench:compaction-range": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-compaction-range.ts",
222
+ "bench:provider-derivation": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-provider-derivation.ts",
223
+ "bench:provider-projection": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-provider-request-projection.ts",
165
224
  "bench:execution-world": "node --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/bench-execution-world.ts",
166
225
  "probe:overflow": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/context-overflow-probe.ts",
167
226
  "subagent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-subagent.ts",
@@ -357,6 +357,8 @@ export class AgentContext {
357
357
  * - `'none'`: no summary present
358
358
  */
359
359
  private _summaryLocation: 'system_prompt' | 'user_message' | 'none' = 'none';
360
+ /** Whether a mid-run summary must appear before every retained message. */
361
+ private summaryPrecedesMessages: boolean = false;
360
362
  /**
361
363
  * Durable summary that survives reset() calls. Set from initialSummary
362
364
  * during fromConfig() and updated by setSummary() so that the latest
@@ -365,6 +367,7 @@ export class AgentContext {
365
367
  */
366
368
  private _durableSummaryText?: string;
367
369
  private _durableSummaryTokenCount: number = 0;
370
+ private durableSummaryPrecedesMessages: boolean = false;
368
371
  /** Number of summarization cycles that have occurred for this agent context */
369
372
  private _summaryVersion: number = 0;
370
373
  /**
@@ -989,10 +992,10 @@ export class AgentContext {
989
992
  return messages;
990
993
  }
991
994
 
992
- const tailIndex = this.getPromptCacheDynamicTailIndex(
993
- messages,
994
- promptCacheProvider
995
- );
995
+ const tailIndex =
996
+ this._summaryLocation === 'user_message' && this.summaryPrecedesMessages
997
+ ? 0
998
+ : this.getPromptCacheDynamicTailIndex(messages, promptCacheProvider);
996
999
  const stablePrefix = messages.slice(0, tailIndex);
997
1000
  const trailingMessages = messages.slice(tailIndex);
998
1001
  const cacheablePrefix = this.addStablePromptCacheMarkers(
@@ -1204,6 +1207,7 @@ export class AgentContext {
1204
1207
 
1205
1208
  this.summaryText = this._durableSummaryText;
1206
1209
  this.summaryTokenCount = this._durableSummaryTokenCount;
1210
+ this.summaryPrecedesMessages = this.durableSummaryPrecedesMessages;
1207
1211
  this._lastSummarizationMsgCount = 0;
1208
1212
  this.lastCallUsage = undefined;
1209
1213
  this.totalTokensFresh = false;
@@ -1461,12 +1465,18 @@ export class AgentContext {
1461
1465
  }
1462
1466
  }
1463
1467
 
1464
- setSummary(text: string, tokenCount: number): void {
1468
+ setSummary(
1469
+ text: string,
1470
+ tokenCount: number,
1471
+ options?: { precedesMessages?: boolean }
1472
+ ): void {
1465
1473
  this.summaryText = text;
1466
1474
  this.summaryTokenCount = tokenCount;
1467
1475
  this._summaryLocation = 'user_message';
1476
+ this.summaryPrecedesMessages = options?.precedesMessages === true;
1468
1477
  this._durableSummaryText = text;
1469
1478
  this._durableSummaryTokenCount = tokenCount;
1479
+ this.durableSummaryPrecedesMessages = this.summaryPrecedesMessages;
1470
1480
  this._summaryVersion += 1;
1471
1481
  this.systemRunnableStale = true;
1472
1482
  this.pruneMessages = undefined;
@@ -1477,8 +1487,10 @@ export class AgentContext {
1477
1487
  this.summaryText = text;
1478
1488
  this.summaryTokenCount = tokenCount;
1479
1489
  this._summaryLocation = 'system_prompt';
1490
+ this.summaryPrecedesMessages = false;
1480
1491
  this._durableSummaryText = text;
1481
1492
  this._durableSummaryTokenCount = tokenCount;
1493
+ this.durableSummaryPrecedesMessages = false;
1482
1494
  this._summaryVersion += 1;
1483
1495
  this.systemRunnableStale = true;
1484
1496
  }
@@ -1683,6 +1695,8 @@ export class AgentContext {
1683
1695
  this.summaryTokenCount = 0;
1684
1696
  this._durableSummaryText = undefined;
1685
1697
  this._durableSummaryTokenCount = 0;
1698
+ this.summaryPrecedesMessages = false;
1699
+ this.durableSummaryPrecedesMessages = false;
1686
1700
  this._summaryLocation = 'none';
1687
1701
  this.systemRunnableStale = true;
1688
1702
  }
@@ -53,9 +53,8 @@ import {
53
53
  cloneMessage,
54
54
  CALIBRATION_RATIO_MAX,
55
55
  createPruneMessages,
56
- projectToolCallInputs,
56
+ projectToolMessagesForProvider,
57
57
  calculateMaxToolCallInputChars,
58
- projectToolStreamContentForProvider,
59
58
  syncBudgetDerivedFields,
60
59
  addTailCacheControl,
61
60
  resolvePromptCacheTtl,
@@ -69,6 +68,7 @@ import {
69
68
  makeIsDeferred,
70
69
  partitionAndMarkAnthropicToolCache,
71
70
  DEFAULT_RETAIN_RECENT_TURNS,
71
+ resolveIntraTurnRetainTokens,
72
72
  splitAtRecencyBoundary,
73
73
  convertInjectedMessages,
74
74
  coalesceAdjacentUserTurns,
@@ -3178,15 +3178,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3178
3178
  const maxProviderToolResultChars =
3179
3179
  agentContext.maxToolResultChars ??
3180
3180
  calculateMaxToolResultChars(agentContext.maxContextTokens);
3181
- const beforeToolStreamProjection = finalMessages;
3182
- finalMessages = trackProviderMessageOrigins(
3183
- beforeToolStreamProjection,
3184
- projectToolStreamContentForProvider(beforeToolStreamProjection)
3185
- );
3186
3181
  const beforeToolInputProjection = finalMessages;
3187
3182
  finalMessages = trackProviderMessageOrigins(
3188
3183
  beforeToolInputProjection,
3189
- projectToolCallInputs(
3184
+ projectToolMessagesForProvider(
3190
3185
  beforeToolInputProjection,
3191
3186
  calculateMaxToolCallInputChars(agentContext.maxContextTokens)
3192
3187
  )
@@ -3942,6 +3937,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3942
3937
  */
3943
3938
  const estimatedPromptTokens = getEstimatedPromptTokens(contextUsage);
3944
3939
 
3940
+ const recencyTokenCounter =
3941
+ agentContext.contextPressureTokenCounts?.count ??
3942
+ agentContext.tokenCounter;
3945
3943
  const canSummarizeOverflow =
3946
3944
  agentContext.summarizationEnabled === true &&
3947
3945
  splitAtRecencyBoundary(messages, {
@@ -3949,7 +3947,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3949
3947
  agentContext.summarizationConfig?.retainRecent?.turns ??
3950
3948
  DEFAULT_RETAIN_RECENT_TURNS,
3951
3949
  tokens: agentContext.summarizationConfig?.retainRecent?.tokens,
3952
- tokenCounter: agentContext.tokenCounter,
3950
+ tokenCounter: recencyTokenCounter,
3951
+ intraTurnTokens: resolveIntraTurnRetainTokens({
3952
+ tokens: agentContext.summarizationConfig?.retainRecent?.tokens,
3953
+ maxContextTokens: agentContext.maxContextTokens,
3954
+ }),
3953
3955
  }).head.length > 0;
3954
3956
 
3955
3957
  const getLocalProviderOverflowMeasurement = (
package/src/index.ts CHANGED
@@ -70,8 +70,9 @@ export type { Interrupt } from '@langchain/langgraph';
70
70
 
71
71
  /* LLM */
72
72
  export { markTokenCounterCacheCompatible } from './llm/tokenCounterCacheCompatibility';
73
- export { CustomOpenAIClient } from './llm/openai';
74
- export { ChatOpenRouter } from './llm/openrouter';
73
+ /** Provider chat-model classes moved off the root barrel: importing any of them here
74
+ * forced every host to pay that provider SDK's module init at boot. They remain
75
+ * available from their own entries, e.g. `@librechat/agents/llm/openai`. */
75
76
  export type {
76
77
  OpenRouterReasoning,
77
78
  OpenRouterReasoningEffort,
@@ -83,7 +84,6 @@ export type {
83
84
  ProviderFamily,
84
85
  ProviderRegistrationOptions,
85
86
  } from './provider-registration';
86
- export { CustomChatMistralAI } from './llm/mistral';
87
87
  export {
88
88
  smoothStream,
89
89
  resolveStreamDelay,
@@ -0,0 +1,67 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ /**
4
+ * Synchronous on-demand module loading: provider SDKs and other heavy dependencies
5
+ * load with their first request instead of at import time. This is the only module
6
+ * that touches `import.meta`; jest maps it to `test/stubs/lazyRequire.ts` so suites
7
+ * resolve source modules through their own resolver (the same precedent as the
8
+ * `@langchain/mistralai` stub).
9
+ *
10
+ * Internal modules load as format-matched siblings — `.cjs` neighbors from the CJS
11
+ * build and `.mjs` neighbors from the ESM build (Node's `require(esm)`, safe on the
12
+ * declared `>=24` engine) — so a lazily resolved provider shares one LangChain class
13
+ * graph with the code that requested it. Under a source-mode runner such as `tsx`,
14
+ * the seam resolves the TypeScript source directly through the active loader.
15
+ */
16
+ const moduleUrl = import.meta.url;
17
+ const requireModule = createRequire(moduleUrl);
18
+
19
+ function detectBuildExtension(url: string): '.cjs' | '.mjs' | null {
20
+ if (url.endsWith('.mjs')) {
21
+ return '.mjs';
22
+ }
23
+ if (url.endsWith('.cjs')) {
24
+ return '.cjs';
25
+ }
26
+ return null;
27
+ }
28
+
29
+ const buildExtension = detectBuildExtension(moduleUrl);
30
+
31
+ /** False when the package runs from TypeScript source, where dist siblings do
32
+ * not exist and lazily loadable modules must be registered up front. */
33
+ export function isBuiltRuntime(): boolean {
34
+ return buildExtension != null;
35
+ }
36
+
37
+ /** Source-mode commands run the TypeScript through an ESM loader that a synchronous
38
+ * CJS `require` can neither reach nor share module identity with, so lazily loadable
39
+ * modules are provided up front instead: `@/llm/providers.eager` imports them through
40
+ * the active loader and registers them here. */
41
+ const sourceModeModules = new Map<string, unknown>();
42
+
43
+ export function registerSourceModeModules(modules: Record<string, unknown>): void {
44
+ for (const [relativePath, moduleExports] of Object.entries(modules)) {
45
+ sourceModeModules.set(relativePath, moduleExports);
46
+ }
47
+ }
48
+
49
+ /** Loads a module of this package by its src-relative path, e.g. `llm/openai/index`. */
50
+ export function requireInternalModule<T>(relativePath: string): T {
51
+ if (buildExtension != null) {
52
+ return requireModule(`./${relativePath}${buildExtension}`) as T;
53
+ }
54
+ const provided = sourceModeModules.get(relativePath);
55
+ if (provided == null) {
56
+ throw new Error(
57
+ `Lazily loaded module "${relativePath}" is unavailable when running from source; ` +
58
+ 'import \'@/llm/providers.eager\' at the entrypoint before any model is used.'
59
+ );
60
+ }
61
+ return provided as T;
62
+ }
63
+
64
+ /** Loads a third-party package on first use; identity-sensitive callers must not use this. */
65
+ export function requireLazyModule<T>(specifier: string): T {
66
+ return requireModule(specifier) as T;
67
+ }
@@ -4,7 +4,11 @@ import type { OpenAIClient } from '@langchain/openai';
4
4
  import type { DocumentType } from '@smithy/types';
5
5
  import type { GraphTools } from '@/types';
6
6
  import { buildBedrockCachePoint, type PromptCacheTtl } from '@/messages/cache';
7
- import { _convertToOpenAITool } from '@/llm/openai';
7
+ import { requireInternalModule } from '@/lazyRequire';
8
+
9
+ /** Loads with the first Bedrock request rather than alongside every graph. */
10
+ const convertToOpenAITool: typeof import('@/llm/openai')._convertToOpenAITool = (...args) =>
11
+ requireInternalModule<typeof import('@/llm/openai')>('llm/openai/index')._convertToOpenAITool(...args);
8
12
 
9
13
  const BEDROCK_TOOL_CACHE_MARKER = '__lc_bedrock_cache_point_after';
10
14
  const BEDROCK_TOOL_CACHE_DISABLED_MARKER = '__lc_bedrock_skip_tool_cache';
@@ -77,7 +81,7 @@ function toBedrockTool(tool: unknown): BedrockToolWithCacheMarker {
77
81
  }
78
82
 
79
83
  return openAIToBedrockTool(
80
- _convertToOpenAITool(tool as BindToolsInput) as OpenAIFunctionTool
84
+ convertToOpenAITool(tool as BindToolsInput) as OpenAIFunctionTool
81
85
  ) as BedrockToolWithCacheMarker;
82
86
  }
83
87
 
package/src/llm/init.ts CHANGED
@@ -1,9 +1,7 @@
1
- import { ChatVertexAI } from '@langchain/google-vertexai';
2
1
  import type { Runnable } from '@langchain/core/runnables';
3
2
  import type * as t from '@/types';
4
- import { ChatOpenAI, AzureChatOpenAI } from '@/llm/openai';
5
3
  import { getChatModelClass } from '@/llm/providers';
6
- import { isOpenAILike } from '@/utils';
4
+ import { isOpenAILike, isLibreChatOpenAIModel, constructorChainHasLcName } from '@/utils';
7
5
  import { Providers } from '@/common';
8
6
 
9
7
  type InitializeModelParams<P extends t.ProviderName> = {
@@ -30,6 +28,18 @@ type InitializeModelParams<P extends t.ProviderName> = {
30
28
  })
31
29
  );
32
30
 
31
+ const VERTEX_LC_NAMES: ReadonlySet<string> = new Set(['ChatVertexAI']);
32
+
33
+ /** Structural stand-in for `instanceof ChatVertexAI`: matches both builds of
34
+ * `@langchain/google-vertexai`, while this package's own vertex class reports
35
+ * `LibreChatVertexAI` and keeps failing this guard exactly as it did under
36
+ * `instanceof`. */
37
+ function isLangchainVertexModel(
38
+ model: unknown
39
+ ): model is import('@langchain/google-vertexai').ChatVertexAI {
40
+ return constructorChainHasLcName(model, VERTEX_LC_NAMES);
41
+ }
42
+
33
43
  /**
34
44
  * Creates a chat model instance for a given built-in or host-registered
35
45
  * provider, applies provider-specific field assignments, and optionally binds
@@ -47,10 +57,7 @@ export function initializeModel<P extends t.ProviderName>({
47
57
  (clientOptions ?? {}) as t.ProviderOptionsFor<P>
48
58
  );
49
59
 
50
- if (
51
- isOpenAILike(provider) &&
52
- (model instanceof ChatOpenAI || model instanceof AzureChatOpenAI)
53
- ) {
60
+ if (isOpenAILike(provider) && isLibreChatOpenAIModel(model)) {
54
61
  const opts = clientOptions as t.OpenAIClientOptions | undefined;
55
62
  if (opts) {
56
63
  model.temperature = opts.temperature as number;
@@ -59,7 +66,7 @@ export function initializeModel<P extends t.ProviderName>({
59
66
  model.presencePenalty = opts.presencePenalty as number;
60
67
  model.n = opts.n as number;
61
68
  }
62
- } else if (provider === Providers.VERTEXAI && model instanceof ChatVertexAI) {
69
+ } else if (provider === Providers.VERTEXAI && isLangchainVertexModel(model)) {
63
70
  const opts = clientOptions as t.VertexAIClientOptions | undefined;
64
71
  if (opts) {
65
72
  model.temperature = opts.temperature as number;
package/src/llm/invoke.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { concat } from '@langchain/core/utils/stream';
2
2
  import { AIMessageChunk } from '@langchain/core/messages';
3
+ import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
3
4
  import { getCallbackManagerForConfig } from '@langchain/core/runnables';
4
5
  import {
5
6
  CallbackManager,
@@ -40,7 +41,12 @@ import { assertNotTruncatedToolCall } from '@/llm/truncation';
40
41
  import { safeDispatchCustomEvent } from '@/utils/events';
41
42
  import { getContextOverflowInfo } from '@/utils/errors';
42
43
  import { appendCallbacks } from '@/utils/callbacks';
43
- import { modifyDeltaProperties } from '@/messages';
44
+ import {
45
+ inspectProviderMessageProjection,
46
+ ProviderMessageProjectionInvariantError,
47
+ resolveProviderMessageProjectionInvariantMode,
48
+ modifyDeltaProperties,
49
+ } from '@/messages';
44
50
  import { canSealPreempt } from '@/llm/preempt';
45
51
  import { initializeModel } from '@/llm/init';
46
52
 
@@ -106,6 +112,79 @@ export type OnChunk = (
106
112
  /** Unique per-model-attempt sequence; see the stamp in `attemptInvoke`. */
107
113
  let streamLimitAttemptSeq = 0;
108
114
 
115
+ function createModelStartHandler({
116
+ config,
117
+ mode,
118
+ provider,
119
+ captureRunId,
120
+ }: {
121
+ config: RunnableConfig;
122
+ mode: ReturnType<typeof resolveProviderMessageProjectionInvariantMode>;
123
+ provider: t.ProviderName;
124
+ captureRunId?: (runId: string) => void;
125
+ }): BaseCallbackHandler {
126
+ let inspected = false;
127
+ const handler = BaseCallbackHandler.fromMethods({
128
+ handleChatModelStart: async (
129
+ _llm: Serialized,
130
+ messageBatches: BaseMessage[][],
131
+ runId: string
132
+ ): Promise<void> => {
133
+ captureRunId?.(runId);
134
+ if (mode === 'off' || inspected) {
135
+ return;
136
+ }
137
+ inspected = true;
138
+ const report = inspectProviderMessageProjection(messageBatches[0] ?? []);
139
+ if (report.valid) {
140
+ return;
141
+ }
142
+ if (mode === 'assert') {
143
+ throw new ProviderMessageProjectionInvariantError(report);
144
+ }
145
+ try {
146
+ const callbackManager = await getCallbackManagerForConfig(config);
147
+ await callbackManager?.handleCustomEvent?.(
148
+ GraphEvents.ON_AGENT_LOG,
149
+ {
150
+ level: 'warn',
151
+ scope: 'projection',
152
+ message: 'Provider message projection has provenance gaps',
153
+ data: { provider, report },
154
+ runId,
155
+ } satisfies t.AgentLogEvent,
156
+ runId
157
+ );
158
+ } catch {
159
+ return;
160
+ }
161
+ },
162
+ });
163
+ handler.name = 'provider-message-projection-invariant';
164
+ handler.raiseError = mode === 'assert';
165
+ handler.awaitHandlers = true;
166
+ return handler;
167
+ }
168
+
169
+ function withModelStartHandler({
170
+ config,
171
+ mode,
172
+ provider,
173
+ captureRunId,
174
+ }: {
175
+ config: RunnableConfig;
176
+ mode: ReturnType<typeof resolveProviderMessageProjectionInvariantMode>;
177
+ provider: t.ProviderName;
178
+ captureRunId?: (runId: string) => void;
179
+ }): RunnableConfig {
180
+ return {
181
+ ...config,
182
+ callbacks: appendCallbacks(config.callbacks, [
183
+ createModelStartHandler({ config, mode, provider, captureRunId }),
184
+ ]),
185
+ };
186
+ }
187
+
109
188
  function getManualToolStreamNormalizationProvider(
110
189
  provider: t.ProviderName
111
190
  ): t.ProviderName {
@@ -632,6 +711,24 @@ async function attemptInvokeBody(
632
711
  config: RunnableConfig
633
712
  ): Promise<Partial<t.BaseGraphState>> {
634
713
  const { model, messages: messagesForProvider, provider } = request;
714
+ const projectionInvariantMode =
715
+ resolveProviderMessageProjectionInvariantMode();
716
+ let sealedRunId: string | undefined;
717
+ let invocationConfig = config;
718
+ const captureModelRunId =
719
+ model.stream != null && context?.preemption != null;
720
+ if (projectionInvariantMode !== 'off' || captureModelRunId) {
721
+ invocationConfig = withModelStartHandler({
722
+ config,
723
+ mode: projectionInvariantMode,
724
+ provider,
725
+ captureRunId: captureModelRunId
726
+ ? (runId: string): void => {
727
+ sealedRunId ??= runId;
728
+ }
729
+ : undefined,
730
+ });
731
+ }
635
732
 
636
733
  /**
637
734
  * Stamp the provider that is ACTUALLY serving this invocation onto the
@@ -647,28 +744,10 @@ async function attemptInvokeBody(
647
744
  * Observed, not dictated. `handleChatModelStart` fires with the chat
648
745
  * model's real run id before the first chunk, which is the only way to
649
746
  * name the run a seal has to close — pinning `config.runId` does not
650
- * survive the bound runnable. Installed only when preemption is
651
- * configured, so a run that cannot seal carries no extra handler.
747
+ * survive the bound runnable. The same handler owns the opt-in projection
748
+ * invariant so enabled diagnostics do not stack a second model callback.
652
749
  */
653
- let sealedRunId: string | undefined;
654
- const streamConfig =
655
- context?.preemption == null
656
- ? config
657
- : {
658
- ...config,
659
- callbacks: appendCallbacks(config.callbacks, [
660
- {
661
- handleChatModelStart: (
662
- _llm: Serialized,
663
- _messages: BaseMessage[][],
664
- runId: string
665
- ): void => {
666
- sealedRunId ??= runId;
667
- },
668
- },
669
- ]),
670
- };
671
- const stream = await model.stream(messagesForProvider, streamConfig);
750
+ const stream = await model.stream(messagesForProvider, invocationConfig);
672
751
  let finalChunk: AIMessageChunk | undefined;
673
752
  let preempted = false;
674
753
  const registeredStreamHandler =
@@ -855,7 +934,7 @@ async function attemptInvokeBody(
855
934
  return { messages: [finalChunk as AIMessageChunk] };
856
935
  }
857
936
 
858
- const finalMessage = await model.invoke(messagesForProvider, config);
937
+ const finalMessage = await model.invoke(messagesForProvider, invocationConfig);
859
938
  if ((finalMessage.tool_calls?.length ?? 0) > 0) {
860
939
  finalMessage.tool_calls = finalMessage.tool_calls?.filter(
861
940
  (tool_call: ToolCall) => !!tool_call.name