@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,7 +5,13 @@ import {
5
5
  buildAnthropicCacheControl,
6
6
  type PromptCacheTtl,
7
7
  } from '@/messages/cache';
8
- import { _convertToOpenAITool } from '@/llm/openai';
8
+ import { requireInternalModule } from '@/lazyRequire';
9
+
10
+ /** Loads with the first OpenRouter request rather than alongside every graph. */
11
+ const convertToOpenAITool = (
12
+ tool: BindToolsInput
13
+ ): OpenAIClient.ChatCompletionTool =>
14
+ requireInternalModule<typeof import('@/llm/openai')>('llm/openai/index')._convertToOpenAITool(tool);
9
15
 
10
16
  type OpenRouterCacheControl = { type: 'ephemeral'; ttl?: '1h' };
11
17
 
@@ -38,7 +44,7 @@ function hasDeferredMarker(tool: unknown): boolean {
38
44
  }
39
45
 
40
46
  function toOpenRouterTool(tool: unknown): OpenRouterToolWithCacheControl {
41
- const converted = _convertToOpenAITool(
47
+ const converted = convertToOpenAITool(
42
48
  tool as BindToolsInput
43
49
  ) as OpenRouterToolWithCacheControl;
44
50
 
@@ -5,8 +5,13 @@ import type {
5
5
  } from '../provider-registration';
6
6
  import type { ProviderModelConstructor, ProviderName } from '@/types';
7
7
 
8
+ type ProviderModelClass = new (config: never) => BaseChatModel;
9
+
8
10
  interface StoredProviderRegistration {
9
- model: new (config: never) => BaseChatModel;
11
+ model?: ProviderModelClass;
12
+ /** Built-ins defer their provider SDK import until the first model request;
13
+ * the resolved class is validated once and memoized into `model`. */
14
+ loadModel?: () => ProviderModelClass;
10
15
  family: ProviderFamily;
11
16
  manualToolStream: boolean;
12
17
  strictAlternation: boolean;
@@ -107,6 +112,31 @@ export function registerBuiltInProvider<
107
112
  builtInProviders.set(provider, createRegistration(provider, options));
108
113
  }
109
114
 
115
+ interface BuiltInProviderLoaderOptions {
116
+ provider: ProviderName;
117
+ loadModel: () => ProviderModelClass;
118
+ family?: ProviderFamily;
119
+ manualToolStream?: boolean;
120
+ strictAlternation?: boolean;
121
+ }
122
+
123
+ /** Registers a built-in whose provider SDK loads on first use, not at import time. */
124
+ export function registerBuiltInProviderLoader(
125
+ options: BuiltInProviderLoaderOptions
126
+ ): void {
127
+ const provider = normalizeProvider(options.provider);
128
+ if (builtInProviders.has(provider)) {
129
+ throw new Error(`LLM provider already registered: ${provider}`);
130
+ }
131
+ builtInProviders.set(provider, {
132
+ loadModel: options.loadModel,
133
+ family: options.family ?? 'generic',
134
+ manualToolStream: options.manualToolStream ?? false,
135
+ strictAlternation: options.strictAlternation ?? false,
136
+ owner: Symbol(provider),
137
+ });
138
+ }
139
+
110
140
  export function getRegisteredChatModelClass<P extends ProviderName>(
111
141
  provider: P
112
142
  ): ProviderModelConstructor<P> {
@@ -114,6 +144,16 @@ export function getRegisteredChatModelClass<P extends ProviderName>(
114
144
  if (!registration) {
115
145
  throw new Error(`Unsupported LLM provider: ${provider}`);
116
146
  }
147
+ if (registration.model == null && registration.loadModel != null) {
148
+ const loaded = registration.loadModel();
149
+ if (typeof loaded !== 'function' || !isConstructible(loaded)) {
150
+ throw new TypeError(`LLM provider constructor is invalid: ${provider}`);
151
+ }
152
+ registration.model = loaded;
153
+ }
154
+ if (registration.model == null) {
155
+ throw new Error(`Unsupported LLM provider: ${provider}`);
156
+ }
117
157
  return registration.model as ProviderModelConstructor<P>;
118
158
  }
119
159
 
@@ -0,0 +1,24 @@
1
+ import * as openai from '@/llm/openai';
2
+ import * as google from '@/llm/google';
3
+ import * as bedrock from '@/llm/bedrock';
4
+ import * as mistral from '@/llm/mistral';
5
+ import * as vertexai from '@/llm/vertexai';
6
+ import * as anthropic from '@/llm/anthropic';
7
+ import * as openrouter from '@/llm/openrouter';
8
+ import { registerSourceModeModules } from '@/lazyRequire';
9
+
10
+ /**
11
+ * Source-mode counterpart of the lazy provider loading the built package uses: commands
12
+ * that run the TypeScript directly import this module first, so every lazily loadable
13
+ * provider module is loaded through the active ESM loader — one module graph, one set of
14
+ * class identities — and registered with the lazy-require seam.
15
+ */
16
+ registerSourceModeModules({
17
+ 'llm/openai/index': openai,
18
+ 'llm/google/index': google,
19
+ 'llm/bedrock/index': bedrock,
20
+ 'llm/mistral/index': mistral,
21
+ 'llm/vertexai/index': vertexai,
22
+ 'llm/anthropic/index': anthropic,
23
+ 'llm/openrouter/index': openrouter,
24
+ });
@@ -1,81 +1,95 @@
1
- import type {
2
- ChatModelConstructorMap,
3
- ProviderModelConstructor,
4
- ProviderName,
5
- } from '@/types';
1
+ import type { ProviderModelConstructor, ProviderName } from '@/types';
2
+ import { requireInternalModule } from '@/lazyRequire';
6
3
  import type { ProviderFamily } from '../provider-registration';
7
- import {
8
- AzureChatOpenAI,
9
- ChatDeepSeek,
10
- ChatMoonshot,
11
- ChatOpenAI,
12
- ChatXAI,
13
- } from '@/llm/openai';
4
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
14
5
  import {
15
6
  getRegisteredChatModelClass,
16
- registerBuiltInProvider,
7
+ registerBuiltInProviderLoader,
17
8
  } from '@/llm/providerRegistry';
18
- import { CustomChatGoogleGenerativeAI } from '@/llm/google';
19
- import { CustomChatBedrockConverse } from '@/llm/bedrock';
20
- import { CustomChatMistralAI } from '@/llm/mistral';
21
- import { CustomAnthropic } from '@/llm/anthropic';
22
- import { ChatOpenRouter } from '@/llm/openrouter';
23
- import { ChatVertexAI } from '@/llm/vertexai';
24
9
  import { Providers } from '@/common';
25
10
 
11
+ /**
12
+ * Built-in provider SDKs load on first model request, not at import time: eagerly
13
+ * importing every provider cost hundreds of milliseconds of boot in hosts that
14
+ * configure one or two of them. `createRequire` keeps resolution synchronous and
15
+ * correct from both the CJS and ESM builds.
16
+ */
17
+
18
+ type ProviderModelClass = new (config: never) => BaseChatModel;
19
+
26
20
  type BuiltInProviderTraits = {
27
21
  family: ProviderFamily;
28
22
  manualToolStream?: boolean;
29
23
  strictAlternation?: boolean;
30
24
  };
31
25
 
32
- function initializeBuiltInProvider<P extends Providers>(
33
- provider: P,
34
- model: ChatModelConstructorMap[P],
26
+ function initializeBuiltInProvider(
27
+ provider: Providers,
28
+ loadModel: () => ProviderModelClass,
35
29
  traits: BuiltInProviderTraits
36
30
  ): void {
37
- registerBuiltInProvider({
31
+ registerBuiltInProviderLoader({
38
32
  provider,
39
- model,
33
+ loadModel,
40
34
  ...traits,
41
35
  });
42
36
  }
43
37
 
44
- initializeBuiltInProvider(Providers.XAI, ChatXAI, { family: 'openai' });
45
- initializeBuiltInProvider(Providers.OPENAI, ChatOpenAI, { family: 'openai' });
46
- initializeBuiltInProvider(Providers.AZURE, AzureChatOpenAI, {
38
+ const fromOpenAI =
39
+ (name: string) => (): ProviderModelClass =>
40
+ (requireInternalModule<Record<string, ProviderModelClass>>('llm/openai/index'))[
41
+ name
42
+ ];
43
+
44
+ initializeBuiltInProvider(Providers.XAI, fromOpenAI('ChatXAI'), { family: 'openai' });
45
+ initializeBuiltInProvider(Providers.OPENAI, fromOpenAI('ChatOpenAI'), { family: 'openai' });
46
+ initializeBuiltInProvider(Providers.AZURE, fromOpenAI('AzureChatOpenAI'), {
47
47
  family: 'openai',
48
48
  });
49
- initializeBuiltInProvider(Providers.VERTEXAI, ChatVertexAI, {
50
- family: 'google',
51
- });
52
- initializeBuiltInProvider(Providers.DEEPSEEK, ChatDeepSeek, {
49
+ initializeBuiltInProvider(
50
+ Providers.VERTEXAI,
51
+ () => (requireInternalModule<typeof import('@/llm/vertexai')>('llm/vertexai/index')).ChatVertexAI,
52
+ { family: 'google' }
53
+ );
54
+ initializeBuiltInProvider(Providers.DEEPSEEK, fromOpenAI('ChatDeepSeek'), {
53
55
  family: 'openai',
54
56
  });
55
- initializeBuiltInProvider(Providers.MISTRALAI, CustomChatMistralAI, {
57
+ const loadMistral = (): ProviderModelClass =>
58
+ (requireInternalModule<typeof import('@/llm/mistral')>('llm/mistral/index')).CustomChatMistralAI;
59
+ initializeBuiltInProvider(Providers.MISTRALAI, loadMistral, {
56
60
  family: 'mistral',
57
61
  strictAlternation: true,
58
62
  });
59
- initializeBuiltInProvider(Providers.MISTRAL, CustomChatMistralAI, {
63
+ initializeBuiltInProvider(Providers.MISTRAL, loadMistral, {
60
64
  family: 'mistral',
61
65
  strictAlternation: true,
62
66
  });
63
- initializeBuiltInProvider(Providers.ANTHROPIC, CustomAnthropic, {
64
- family: 'anthropic',
65
- manualToolStream: true,
66
- });
67
- initializeBuiltInProvider(Providers.OPENROUTER, ChatOpenRouter, {
68
- family: 'openai',
69
- });
70
- initializeBuiltInProvider(Providers.BEDROCK, CustomChatBedrockConverse, {
71
- family: 'bedrock',
72
- manualToolStream: true,
73
- strictAlternation: true,
74
- });
75
- initializeBuiltInProvider(Providers.GOOGLE, CustomChatGoogleGenerativeAI, {
76
- family: 'google',
77
- });
78
- initializeBuiltInProvider(Providers.MOONSHOT, ChatMoonshot, {
67
+ initializeBuiltInProvider(
68
+ Providers.ANTHROPIC,
69
+ () => (requireInternalModule<typeof import('@/llm/anthropic')>('llm/anthropic/index')).CustomAnthropic,
70
+ { family: 'anthropic', manualToolStream: true }
71
+ );
72
+ initializeBuiltInProvider(
73
+ Providers.OPENROUTER,
74
+ () =>
75
+ (requireInternalModule<typeof import('@/llm/openrouter')>('llm/openrouter/index')).ChatOpenRouter,
76
+ { family: 'openai' }
77
+ );
78
+ initializeBuiltInProvider(
79
+ Providers.BEDROCK,
80
+ () =>
81
+ (requireInternalModule<typeof import('@/llm/bedrock')>('llm/bedrock/index'))
82
+ .CustomChatBedrockConverse,
83
+ { family: 'bedrock', manualToolStream: true, strictAlternation: true }
84
+ );
85
+ initializeBuiltInProvider(
86
+ Providers.GOOGLE,
87
+ () =>
88
+ (requireInternalModule<typeof import('@/llm/google')>('llm/google/index'))
89
+ .CustomChatGoogleGenerativeAI,
90
+ { family: 'google' }
91
+ );
92
+ initializeBuiltInProvider(Providers.MOONSHOT, fromOpenAI('ChatMoonshot'), {
79
93
  family: 'generic',
80
94
  });
81
95
 
@@ -22,6 +22,20 @@ export const isLegacyConvertible = (message: BaseMessage): boolean => {
22
22
  return message.content.every((block) => block.type === ContentTypes.TEXT);
23
23
  };
24
24
 
25
+ /** Joins the text of {@link isLegacyConvertible} content blocks into the exact
26
+ * string {@link formatContentStrings} has always produced for them. */
27
+ export const flattenLegacyContent = (
28
+ blocks: MessageContentComplex[]
29
+ ): string => {
30
+ const content = blocks.reduce((acc, curr) => {
31
+ if (curr.type === ContentTypes.TEXT) {
32
+ return `${acc}${curr[ContentTypes.TEXT] ?? ''}\n`;
33
+ }
34
+ return acc;
35
+ }, '');
36
+ return content.trim();
37
+ };
38
+
25
39
  /**
26
40
  * Formats an array of messages for LangChain, making sure all content fields are strings
27
41
  * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.
@@ -39,16 +53,12 @@ export const formatContentStrings = (
39
53
  continue;
40
54
  }
41
55
 
42
- // Reduce text types to a single string
43
- const blocks = message.content as MessageContentComplex[];
44
- const content = blocks.reduce((acc, curr) => {
45
- if (curr.type === ContentTypes.TEXT) {
46
- return `${acc}${curr[ContentTypes.TEXT] ?? ''}\n`;
47
- }
48
- return acc;
49
- }, '');
50
-
51
- result.push(cloneMessage(message, content.trim()));
56
+ result.push(
57
+ cloneMessage(
58
+ message,
59
+ flattenLegacyContent(message.content as MessageContentComplex[])
60
+ )
61
+ );
52
62
  }
53
63
 
54
64
  return result;
@@ -980,6 +980,19 @@ function isCompleteToolStreamContentBlock(block: unknown): boolean {
980
980
  }
981
981
  }
982
982
 
983
+ /** Drops incomplete streamed text blocks before provider-facing tool shaping. */
984
+ export function dropIncompleteToolStreamContent(
985
+ content: AIMessage['content']
986
+ ): AIMessage['content'] {
987
+ if (!Array.isArray(content)) {
988
+ return content;
989
+ }
990
+ const completeContent = content.filter(isCompleteToolStreamContentBlock);
991
+ return completeContent.length === content.length
992
+ ? content
993
+ : toLangChainContent(completeContent);
994
+ }
995
+
983
996
  function projectPreemptedResponsesV1Content(
984
997
  content: AIMessage['content'],
985
998
  reasoning: unknown,
@@ -2026,20 +2039,12 @@ export function projectToolStreamContentForProvider(
2026
2039
  }
2027
2040
  continue;
2028
2041
  }
2029
- if (!Array.isArray(assistantMessage.content)) {
2030
- continue;
2031
- }
2032
- const content = assistantMessage.content.filter(
2033
- isCompleteToolStreamContentBlock
2034
- );
2035
- if (content.length === assistantMessage.content.length) {
2042
+ const content = dropIncompleteToolStreamContent(assistantMessage.content);
2043
+ if (content === assistantMessage.content) {
2036
2044
  continue;
2037
2045
  }
2038
2046
  projected ??= [...messages];
2039
- projected[i] = cloneAIMessageWithContent(
2040
- assistantMessage,
2041
- toLangChainContent(content)
2042
- );
2047
+ projected[i] = cloneAIMessageWithContent(assistantMessage, content);
2043
2048
  }
2044
2049
  return projected ?? messages;
2045
2050
  }
@@ -46,6 +46,7 @@ import {
46
46
  hasBijectiveProviderContentPartMapping,
47
47
  inspectProviderMessageProvenance,
48
48
  inspectProviderSourceMessageIds,
49
+ setFreshProviderMessageProvenance,
49
50
  setInvalidProviderMessageProvenance,
50
51
  setProviderMessageProvenance,
51
52
  } from './provenance';
@@ -57,6 +58,7 @@ import {
57
58
  } from '@/utils/toolContent';
58
59
  import { normalizeAnthropicToolCallId } from '@/llm/anthropic/utils/message_inputs';
59
60
  import { toLangChainContent, toLangChainMessageFields } from './langchain';
61
+ import { flattenLegacyContent, isLegacyConvertible } from './content';
60
62
  import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
61
63
  import { Providers, ContentTypes, Constants } from '@/common';
62
64
  import { emitAgentLog } from '@/utils/events';
@@ -378,6 +380,12 @@ type SourceContentPartIndices = number | readonly number[];
378
380
 
379
381
  interface FormatAgentMessagesOptions {
380
382
  provider?: ProviderName;
383
+ /** Emit flattenable text content as the joined string the legacy-content
384
+ * projection would produce, so the per-request `formatContentStrings` pass
385
+ * finds nothing to convert and every history message keeps its identity —
386
+ * which is what lets exact-count reuse skip re-tokenizing it. Set this if
387
+ * and only if the run's provider uses legacy string content. */
388
+ legacyContent?: boolean;
381
389
  /** Reconstruct hidden `reasoning_content` from `THINK` parts onto prior
382
390
  * tool-call messages. Explicit opt-in for OpenAI-compatible endpoints that
383
391
  * replay reasoning across turns; defaults to on for DeepSeek thinking-mode. */
@@ -1380,7 +1388,7 @@ function stampSourceMessageIdentity(
1380
1388
  },
1381
1389
  ];
1382
1390
  }
1383
- setProviderMessageProvenance(message, partsToStamp);
1391
+ setFreshProviderMessageProvenance(message, partsToStamp);
1384
1392
  if (sourceMessageId == null || derivedIndex !== 0) {
1385
1393
  return;
1386
1394
  }
@@ -1996,6 +2004,22 @@ export const formatAgentMessages = (
1996
2004
  * assistant turn. Held rather than emitted so an entry that produces
1997
2005
  * nothing cannot leave the anchor stranded as the final turn.
1998
2006
  */
2007
+ const legacyContentEnabled = options?.legacyContent === true;
2008
+ /** Emission choke point: every formatted message enters the result here, so
2009
+ * the legacy flatten happens once per message with no closing rescan. The
2010
+ * summary boundary slices payload entries before formatting, and nothing
2011
+ * mutates an emitted message's content afterwards, so flattening at
2012
+ * emission and flattening at return are equivalent. */
2013
+ const emitFormattedMessage = (message: (typeof messages)[number]): void => {
2014
+ if (legacyContentEnabled && isLegacyConvertible(message)) {
2015
+ const flattened = flattenLegacyContent(
2016
+ message.content as MessageContentComplex[]
2017
+ );
2018
+ message.content = flattened;
2019
+ message.lc_kwargs.content = flattened;
2020
+ }
2021
+ messages.push(message);
2022
+ };
1999
2023
  let pendingSteerAnchor = false;
2000
2024
  /**
2001
2025
  * Emits the deferred anchor ahead of `next` — the message about to be
@@ -2018,7 +2042,7 @@ export const formatAgentMessages = (
2018
2042
  'assistant'
2019
2043
  );
2020
2044
  stampSourceMessageIdentity(anchor, undefined, 0, 'synthetic');
2021
- messages.push(anchor);
2045
+ emitFormattedMessage(anchor);
2022
2046
  };
2023
2047
  // If indexTokenCountMap is provided, create a new map to track the updated indices
2024
2048
  const updatedIndexTokenCountMap: Record<number, number> = {};
@@ -2098,7 +2122,7 @@ export const formatAgentMessages = (
2098
2122
  provenanceParts
2099
2123
  );
2100
2124
  flushSteerAnchor(formattedMessage);
2101
- messages.push(formattedMessage);
2125
+ emitFormattedMessage(formattedMessage);
2102
2126
 
2103
2127
  // Update the index mapping for this message
2104
2128
  indexMapping[i] = [messages.length - 1];
@@ -2359,7 +2383,7 @@ export const formatAgentMessages = (
2359
2383
  flushSteerAnchor(formattedMessages[0]);
2360
2384
  }
2361
2385
  for (const formattedMessage of formattedMessages) {
2362
- messages.push(formattedMessage);
2386
+ emitFormattedMessage(formattedMessage);
2363
2387
  }
2364
2388
  if (endsWithSteerMessage(formattedMessages)) {
2365
2389
  pendingSteerAnchor = true;
@@ -2390,7 +2414,7 @@ export const formatAgentMessages = (
2390
2414
  'user'
2391
2415
  );
2392
2416
  stampSourceMessageIdentity(skillMessage, undefined, 0, 'synthetic');
2393
- messages.push(skillMessage);
2417
+ emitFormattedMessage(skillMessage);
2394
2418
  }
2395
2419
  }
2396
2420
  }
@@ -16,3 +16,4 @@ export * from './reducer';
16
16
  export * from './recency';
17
17
  export * from './assistantPhase';
18
18
  export * from './provenance';
19
+ export * from './projectionInvariant';
@@ -0,0 +1,134 @@
1
+ import type { BaseMessage } from '@langchain/core/messages';
2
+ import { inspectProviderMessageProvenance } from './provenance';
3
+
4
+ export type ProviderMessageProjectionInvariantMode =
5
+ | 'off'
6
+ | 'observe'
7
+ | 'assert';
8
+
9
+ export type ProviderMessageProjectionIssueCode =
10
+ | 'absent_provenance'
11
+ | 'invalid_provenance'
12
+ | 'unsourced_non_synthetic_part';
13
+
14
+ export interface ProviderMessageProjectionInvariantIssue {
15
+ readonly code: ProviderMessageProjectionIssueCode;
16
+ readonly messageIndex: number;
17
+ readonly messageType: string;
18
+ }
19
+
20
+ export interface ProviderMessageProjectionInvariantReport {
21
+ readonly valid: boolean;
22
+ readonly messageCount: number;
23
+ readonly sourceBackedMessageCount: number;
24
+ readonly syntheticMessageCount: number;
25
+ readonly gapMessageCount: number;
26
+ readonly issues: readonly ProviderMessageProjectionInvariantIssue[];
27
+ }
28
+
29
+ const MAX_REPORTED_PROJECTION_ISSUES = 64;
30
+
31
+ function getMessageType(message: BaseMessage): string {
32
+ try {
33
+ return typeof message.type === 'string' ? message.type : 'unknown';
34
+ } catch {
35
+ return 'unknown';
36
+ }
37
+ }
38
+
39
+ function appendIssue(
40
+ issues: ProviderMessageProjectionInvariantIssue[],
41
+ code: ProviderMessageProjectionIssueCode,
42
+ messageIndex: number,
43
+ messageType: string
44
+ ): void {
45
+ if (issues.length >= MAX_REPORTED_PROJECTION_ISSUES) {
46
+ return;
47
+ }
48
+ issues.push(Object.freeze({ code, messageIndex, messageType }));
49
+ }
50
+
51
+ /** Resolves the opt-in provider projection invariant without enabling it for
52
+ * unrecognized values. */
53
+ export function resolveProviderMessageProjectionInvariantMode(
54
+ value = process.env.AGENT_MESSAGE_PROJECTION_INVARIANT
55
+ ): ProviderMessageProjectionInvariantMode {
56
+ if (value === 'observe' || value === 'assert') {
57
+ return value;
58
+ }
59
+ return 'off';
60
+ }
61
+
62
+ /** Inspects provider-bound lineage without reading message content or source ids. */
63
+ export function inspectProviderMessageProjection(
64
+ messages: readonly BaseMessage[]
65
+ ): ProviderMessageProjectionInvariantReport {
66
+ const issues: ProviderMessageProjectionInvariantIssue[] = [];
67
+ let sourceBackedMessageCount = 0;
68
+ let syntheticMessageCount = 0;
69
+ let gapMessageCount = 0;
70
+
71
+ for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
72
+ const message = messages[messageIndex];
73
+ const messageType = getMessageType(message);
74
+ const state = inspectProviderMessageProvenance(message);
75
+ if (state.status !== 'valid') {
76
+ gapMessageCount++;
77
+ appendIssue(
78
+ issues,
79
+ state.status === 'absent'
80
+ ? 'absent_provenance'
81
+ : 'invalid_provenance',
82
+ messageIndex,
83
+ messageType
84
+ );
85
+ continue;
86
+ }
87
+
88
+ let hasSourceBackedPart = false;
89
+ let hasGap = false;
90
+ for (const part of state.provenance.parts) {
91
+ if (part.sourceMessageId != null) {
92
+ hasSourceBackedPart = true;
93
+ continue;
94
+ }
95
+ if (part.attribution === 'synthetic') {
96
+ continue;
97
+ }
98
+ hasGap = true;
99
+ appendIssue(
100
+ issues,
101
+ 'unsourced_non_synthetic_part',
102
+ messageIndex,
103
+ messageType
104
+ );
105
+ }
106
+
107
+ if (hasGap) {
108
+ gapMessageCount++;
109
+ } else if (hasSourceBackedPart) {
110
+ sourceBackedMessageCount++;
111
+ } else {
112
+ syntheticMessageCount++;
113
+ }
114
+ }
115
+
116
+ return Object.freeze({
117
+ valid: gapMessageCount === 0,
118
+ messageCount: messages.length,
119
+ sourceBackedMessageCount,
120
+ syntheticMessageCount,
121
+ gapMessageCount,
122
+ issues: Object.freeze(issues),
123
+ });
124
+ }
125
+
126
+ export class ProviderMessageProjectionInvariantError extends Error {
127
+ readonly report: ProviderMessageProjectionInvariantReport;
128
+
129
+ constructor(report: ProviderMessageProjectionInvariantReport) {
130
+ super('Provider message projection invariant failed');
131
+ this.name = 'ProviderMessageProjectionInvariantError';
132
+ this.report = report;
133
+ }
134
+ }
@@ -766,11 +766,14 @@ function publishProviderMessageProvenance(
766
766
  }
767
767
  }
768
768
 
769
- /** Replaces typed provenance and synchronizes its stable plural source ids. */
770
- export function setProviderMessageProvenance(
771
- message: BaseMessage,
769
+ interface ResolvedProvenancePublication {
770
+ provenance: ProviderMessageProvenance;
771
+ sourceMessageIds?: readonly string[];
772
+ }
773
+
774
+ function resolveProvenancePublication(
772
775
  parts: readonly ProviderMessageProvenancePart[]
773
- ): void {
776
+ ): ResolvedProvenancePublication {
774
777
  const normalizedParts = normalizeProvenanceParts(parts);
775
778
  const provenance: ProviderMessageProvenance = Object.freeze({
776
779
  version: PROVIDER_MESSAGE_PROVENANCE_VERSION,
@@ -782,21 +785,60 @@ export function setProviderMessageProvenance(
782
785
  for (const part of normalizedParts) {
783
786
  appendSourceMessageId(sourceMessageIds, seen, part.sourceMessageId);
784
787
  }
785
- if (sourceMessageIds.length > 0) {
786
- const immutableSourceMessageIds = Object.freeze(sourceMessageIds);
787
- immutableProviderSourceMessageIds.add(immutableSourceMessageIds);
788
- immutableProviderMessageSourceIds.set(
789
- provenance,
790
- immutableSourceMessageIds
791
- );
792
- publishProviderMessageProvenance(
793
- message,
794
- provenance,
795
- immutableSourceMessageIds
796
- );
797
- return;
788
+ if (sourceMessageIds.length === 0) {
789
+ return { provenance };
798
790
  }
799
- publishProviderMessageProvenance(message, provenance);
791
+ const immutableSourceMessageIds = Object.freeze(sourceMessageIds);
792
+ immutableProviderSourceMessageIds.add(immutableSourceMessageIds);
793
+ immutableProviderMessageSourceIds.set(provenance, immutableSourceMessageIds);
794
+ return { provenance, sourceMessageIds: immutableSourceMessageIds };
795
+ }
796
+
797
+ /** Replaces typed provenance and synchronizes its stable plural source ids. */
798
+ export function setProviderMessageProvenance(
799
+ message: BaseMessage,
800
+ parts: readonly ProviderMessageProvenancePart[]
801
+ ): void {
802
+ const resolved = resolveProvenancePublication(parts);
803
+ publishProviderMessageProvenance(
804
+ message,
805
+ resolved.provenance,
806
+ resolved.sourceMessageIds
807
+ );
808
+ }
809
+
810
+ /**
811
+ * {@link setProviderMessageProvenance} for a message the caller itself just
812
+ * constructed from locally built plain objects. Such a message cannot carry
813
+ * proxies, accessors, or foreign aliases in `additional_kwargs`/`lc_kwargs`,
814
+ * so the publication skips the hardened descriptor walks while producing the
815
+ * same end state: fresh replacement objects on both slots, with the
816
+ * serialization mirror aliasing the live kwargs. Never call this with a
817
+ * message received across a seam — that is what the hardened variant is for.
818
+ */
819
+ export function setFreshProviderMessageProvenance(
820
+ message: BaseMessage,
821
+ parts: readonly ProviderMessageProvenancePart[]
822
+ ): void {
823
+ const resolved = resolveProvenancePublication(parts);
824
+ const replacement: UntrustedProviderMessageAdditionalKwargs = {
825
+ ...message.additional_kwargs,
826
+ };
827
+ replacement.provenance = resolved.provenance;
828
+ const sourceMessageIds = resolved.sourceMessageIds;
829
+ if (sourceMessageIds != null && sourceMessageIds.length > 0) {
830
+ replacement.sourceMessageIds = sourceMessageIds;
831
+ replacement.sourceMessageId = sourceMessageIds[sourceMessageIds.length - 1];
832
+ } else {
833
+ delete replacement.sourceMessageIds;
834
+ delete replacement.sourceMessageId;
835
+ }
836
+ const serializedReplacement: BaseMessage['lc_kwargs'] = {
837
+ ...message.lc_kwargs,
838
+ };
839
+ serializedReplacement.additional_kwargs = replacement;
840
+ message.additional_kwargs = replacement;
841
+ message.lc_kwargs = serializedReplacement;
800
842
  }
801
843
 
802
844
  /** Publishes the canonical fail-closed marker for malformed provenance. */