@librechat/agents 3.6.16 → 3.7.0

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 (108) hide show
  1. package/dist/cjs/_virtual/_rolldown/runtime.cjs +10 -0
  2. package/dist/cjs/graphs/Graph.cjs +5 -7
  3. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  4. package/dist/cjs/lazyRequire.cjs +33 -0
  5. package/dist/cjs/lazyRequire.cjs.map +1 -0
  6. package/dist/cjs/llm/anthropic/index.cjs +13 -0
  7. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  8. package/dist/cjs/llm/bedrock/index.cjs +10 -1
  9. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  10. package/dist/cjs/llm/bedrock/toolCache.cjs +3 -2
  11. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  12. package/dist/cjs/llm/google/index.cjs +10 -1
  13. package/dist/cjs/llm/google/index.cjs.map +1 -1
  14. package/dist/cjs/llm/init.cjs +6 -4
  15. package/dist/cjs/llm/init.cjs.map +1 -1
  16. package/dist/cjs/llm/mistral/index.cjs +9 -0
  17. package/dist/cjs/llm/mistral/index.cjs.map +1 -1
  18. package/dist/cjs/llm/openai/index.cjs +42 -2
  19. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  20. package/dist/cjs/llm/openrouter/index.cjs +12 -3
  21. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openrouter/toolCache.cjs +3 -2
  23. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  24. package/dist/cjs/llm/providerRegistry.cjs +15 -3
  25. package/dist/cjs/llm/providerRegistry.cjs.map +1 -1
  26. package/dist/cjs/llm/providers.cjs +18 -22
  27. package/dist/cjs/llm/providers.cjs.map +1 -1
  28. package/dist/cjs/llm/providers.eager.cjs +21 -0
  29. package/dist/cjs/llm/providers.eager.cjs.map +1 -0
  30. package/dist/cjs/llm/vertexai/index.cjs +17 -0
  31. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  32. package/dist/cjs/main.cjs +8 -10
  33. package/dist/cjs/messages/core.cjs +9 -4
  34. package/dist/cjs/messages/core.cjs.map +1 -1
  35. package/dist/cjs/messages/prune.cjs +42 -8
  36. package/dist/cjs/messages/prune.cjs.map +1 -1
  37. package/dist/cjs/run.cjs +7 -2
  38. package/dist/cjs/run.cjs.map +1 -1
  39. package/dist/cjs/tools/Calculator.cjs +7 -4
  40. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  41. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  42. package/dist/cjs/utils/llm.cjs +16 -0
  43. package/dist/cjs/utils/llm.cjs.map +1 -1
  44. package/dist/esm/_virtual/_rolldown/runtime.mjs +14 -0
  45. package/dist/esm/graphs/Graph.mjs +4 -6
  46. package/dist/esm/graphs/Graph.mjs.map +1 -1
  47. package/dist/esm/lazyRequire.mjs +30 -0
  48. package/dist/esm/lazyRequire.mjs.map +1 -0
  49. package/dist/esm/llm/anthropic/index.mjs +6 -1
  50. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  51. package/dist/esm/llm/bedrock/index.mjs +4 -2
  52. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  53. package/dist/esm/llm/bedrock/toolCache.mjs +3 -2
  54. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  55. package/dist/esm/llm/google/index.mjs +4 -2
  56. package/dist/esm/llm/google/index.mjs.map +1 -1
  57. package/dist/esm/llm/init.mjs +7 -5
  58. package/dist/esm/llm/init.mjs.map +1 -1
  59. package/dist/esm/llm/mistral/index.mjs +3 -1
  60. package/dist/esm/llm/mistral/index.mjs.map +1 -1
  61. package/dist/esm/llm/openai/index.mjs +32 -10
  62. package/dist/esm/llm/openai/index.mjs.map +1 -1
  63. package/dist/esm/llm/openrouter/index.mjs +3 -1
  64. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  65. package/dist/esm/llm/openrouter/toolCache.mjs +3 -2
  66. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  67. package/dist/esm/llm/providerRegistry.mjs +15 -3
  68. package/dist/esm/llm/providerRegistry.mjs.map +1 -1
  69. package/dist/esm/llm/providers.eager.mjs +22 -0
  70. package/dist/esm/llm/providers.eager.mjs.map +1 -0
  71. package/dist/esm/llm/providers.mjs +19 -23
  72. package/dist/esm/llm/providers.mjs.map +1 -1
  73. package/dist/esm/llm/vertexai/index.mjs +8 -1
  74. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  75. package/dist/esm/main.mjs +5 -8
  76. package/dist/esm/messages/core.mjs +9 -5
  77. package/dist/esm/messages/core.mjs.map +1 -1
  78. package/dist/esm/messages/prune.mjs +42 -9
  79. package/dist/esm/messages/prune.mjs.map +1 -1
  80. package/dist/esm/run.mjs +8 -3
  81. package/dist/esm/run.mjs.map +1 -1
  82. package/dist/esm/tools/Calculator.mjs +7 -2
  83. package/dist/esm/tools/Calculator.mjs.map +1 -1
  84. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  85. package/dist/esm/utils/llm.mjs +15 -1
  86. package/dist/esm/utils/llm.mjs.map +1 -1
  87. package/dist/types/index.d.ts +3 -3
  88. package/dist/types/lazyRequire.d.ts +8 -0
  89. package/dist/types/llm/providerRegistry.d.ts +11 -0
  90. package/dist/types/llm/providers.eager.d.ts +1 -0
  91. package/dist/types/messages/core.d.ts +2 -0
  92. package/dist/types/messages/prune.d.ts +5 -4
  93. package/dist/types/utils/llm.d.ts +10 -0
  94. package/package.json +58 -1
  95. package/src/graphs/Graph.ts +2 -8
  96. package/src/index.ts +3 -3
  97. package/src/lazyRequire.ts +67 -0
  98. package/src/llm/bedrock/toolCache.ts +6 -2
  99. package/src/llm/init.ts +15 -8
  100. package/src/llm/openrouter/toolCache.ts +8 -2
  101. package/src/llm/providerRegistry.ts +41 -1
  102. package/src/llm/providers.eager.ts +24 -0
  103. package/src/llm/providers.ts +63 -49
  104. package/src/messages/core.ts +16 -11
  105. package/src/messages/prune.ts +86 -11
  106. package/src/run.ts +14 -6
  107. package/src/tools/Calculator.ts +9 -2
  108. package/src/utils/llm.ts +39 -0
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;
@@ -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
 
@@ -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
  }
@@ -31,6 +31,7 @@ import { resolveContextPruningSettings } from './contextPruningSettings';
31
31
  import { hasUnsafeStructuredSerialization } from '@/utils/tokens';
32
32
  import { ContentTypes, Providers, Constants } from '@/common';
33
33
  import { getProviderFamily } from '@/llm/providerRegistry';
34
+ import { dropIncompleteToolStreamContent } from './core';
34
35
  import { applyContextPruning } from './contextPruning';
35
36
  import { toLangChainContent } from './langchain';
36
37
 
@@ -1412,6 +1413,47 @@ function cloneWithProjectedProperties<T extends object>(
1412
1413
  ) as T;
1413
1414
  }
1414
1415
 
1416
+ function cloneAIMessageWithProjectedStreamContent(
1417
+ message: AIMessage | AIMessageChunk,
1418
+ changes: Readonly<Record<string, unknown>>,
1419
+ streamContent: AIMessage['content']
1420
+ ): AIMessage | AIMessageChunk {
1421
+ const descriptors = Object.getOwnPropertyDescriptors(message) as Record<
1422
+ string,
1423
+ PropertyDescriptor | undefined
1424
+ >;
1425
+ for (const [key, projectedValue] of Object.entries(changes)) {
1426
+ const descriptor = descriptors[key];
1427
+ descriptors[key] = {
1428
+ configurable: descriptor?.configurable ?? true,
1429
+ enumerable: descriptor?.enumerable ?? true,
1430
+ value: projectedValue,
1431
+ writable: descriptor?.writable ?? true,
1432
+ };
1433
+ }
1434
+ const lcKwargs = descriptors.lc_kwargs;
1435
+ if (
1436
+ lcKwargs != null &&
1437
+ 'value' in lcKwargs &&
1438
+ typeof lcKwargs.value === 'object' &&
1439
+ lcKwargs.value != null
1440
+ ) {
1441
+ descriptors.lc_kwargs = {
1442
+ ...lcKwargs,
1443
+ value: {
1444
+ ...(lcKwargs.value as Record<string, unknown>),
1445
+ ...(message.response_metadata.output_version === 'v1'
1446
+ ? { content: undefined, contentBlocks: streamContent }
1447
+ : { content: streamContent }),
1448
+ },
1449
+ };
1450
+ }
1451
+ return Object.create(
1452
+ Object.getPrototypeOf(message),
1453
+ descriptors as PropertyDescriptorMap
1454
+ ) as AIMessage | AIMessageChunk;
1455
+ }
1456
+
1415
1457
  function createBoundedTruncationValue(
1416
1458
  preview: string,
1417
1459
  originalChars: number,
@@ -1875,9 +1917,10 @@ export function calculateMaxToolCallInputChars(
1875
1917
  * Returns the original array when no message changes and otherwise clones only
1876
1918
  * the array and AI messages whose inline input or `tool_calls` args changed.
1877
1919
  */
1878
- export function projectToolCallInputs(
1920
+ function projectToolCallInputsInternal(
1879
1921
  messages: BaseMessage[],
1880
- maxInputChars: number
1922
+ maxInputChars: number,
1923
+ dropIncompleteStreamContent: boolean
1881
1924
  ): BaseMessage[] {
1882
1925
  const normalizedMaxInputChars = normalizeToolInputLimit(maxInputChars);
1883
1926
  let projectedMessages: BaseMessage[] | undefined;
@@ -1889,16 +1932,21 @@ export function projectToolCallInputs(
1889
1932
  }
1890
1933
 
1891
1934
  const aiMessage = message as AIMessage | AIMessageChunk;
1892
- let projectedContent = aiMessage.content;
1893
- let contentChanged = false;
1894
- if (Array.isArray(aiMessage.content)) {
1895
- const originalContent = aiMessage.content as MessageContentComplex[];
1935
+ const streamContent = dropIncompleteStreamContent
1936
+ ? dropIncompleteToolStreamContent(aiMessage.content)
1937
+ : aiMessage.content;
1938
+ let projectedContent = streamContent;
1939
+ let contentChanged = streamContent !== aiMessage.content;
1940
+ if (Array.isArray(streamContent)) {
1941
+ const originalContent = streamContent as MessageContentComplex[];
1896
1942
  const mappedContent = originalContent.map((block) =>
1897
1943
  projectInlineToolInput(block, normalizedMaxInputChars)
1898
1944
  );
1899
- contentChanged = mappedContent.some(
1900
- (block, blockIndex) => block !== originalContent[blockIndex]
1901
- );
1945
+ contentChanged =
1946
+ contentChanged ||
1947
+ mappedContent.some(
1948
+ (block, blockIndex) => block !== originalContent[blockIndex]
1949
+ );
1902
1950
  projectedContent = toLangChainContent(mappedContent);
1903
1951
  }
1904
1952
 
@@ -2020,18 +2068,45 @@ export function projectToolCallInputs(
2020
2068
  continue;
2021
2069
  }
2022
2070
 
2023
- const projectedMessage = cloneWithProjectedProperties(aiMessage, {
2071
+ const changes = {
2024
2072
  content: projectedContent,
2025
2073
  tool_calls: projectedToolCalls.length > 0 ? projectedToolCalls : [],
2026
2074
  additional_kwargs: projectedAdditionalKwargs,
2027
2075
  response_metadata: projectedResponseMetadata,
2028
- });
2076
+ };
2077
+ const projectedMessage =
2078
+ streamContent === aiMessage.content
2079
+ ? cloneWithProjectedProperties(aiMessage, changes)
2080
+ : cloneAIMessageWithProjectedStreamContent(
2081
+ aiMessage,
2082
+ changes,
2083
+ streamContent
2084
+ );
2029
2085
  projectedMessages ??= [...messages];
2030
2086
  projectedMessages[i] = projectedMessage;
2031
2087
  }
2032
2088
  return projectedMessages ?? messages;
2033
2089
  }
2034
2090
 
2091
+ /** Projects all historical tool-call input representations to bounded values. */
2092
+ export function projectToolCallInputs(
2093
+ messages: BaseMessage[],
2094
+ maxInputChars: number
2095
+ ): BaseMessage[] {
2096
+ return projectToolCallInputsInternal(messages, maxInputChars, false);
2097
+ }
2098
+
2099
+ /**
2100
+ * Derives provider-safe tool history in one pass by dropping incomplete stream
2101
+ * content and bounding every provider-consumed tool-call input representation.
2102
+ */
2103
+ export function projectToolMessagesForProvider(
2104
+ messages: BaseMessage[],
2105
+ maxInputChars: number
2106
+ ): BaseMessage[] {
2107
+ return projectToolCallInputsInternal(messages, maxInputChars, true);
2108
+ }
2109
+
2035
2110
  export function preFlightTruncateToolCallInputs(params: {
2036
2111
  messages: BaseMessage[];
2037
2112
  maxContextTokens: number;
package/src/run.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  import { nanoid } from 'nanoid';
3
3
  import { PromptTemplate } from '@langchain/core/prompts';
4
4
  import { RunnableLambda } from '@langchain/core/runnables';
5
- import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
6
5
  import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
7
6
  import {
8
7
  Command,
@@ -36,6 +35,7 @@ import {
36
35
  ACTIVITY_PHASE_LABEL_RUN_NAME,
37
36
  DEFAULT_RECURSION_LIMIT,
38
37
  } from '@/common';
38
+ import { isBuiltRuntime } from '@/lazyRequire';
39
39
  import {
40
40
  requireValidSubagentResumeManifest,
41
41
  stripSubagentResumeManifest,
@@ -101,9 +101,19 @@ import { createGraph } from '@/graphs/createGraph';
101
101
  import { resolveMaxSeals } from '@/llm/preempt';
102
102
  import { initializeModel } from '@/llm/init';
103
103
  import { HandlerRegistry } from '@/events';
104
- import { isOpenAILike } from '@/utils/llm';
104
+ import { isOpenAILike, isLibreChatOpenAIModel } from '@/utils/llm';
105
105
  import { executeHooks } from '@/hooks';
106
106
 
107
+ /** Source-mode runs have no dist siblings for the lazy-loading seam, so every
108
+ * lazily loadable module is imported through the active loader before the
109
+ * first model resolves. The built package never enters this branch. */
110
+ async function ensureSourceModeProviders(): Promise<void> {
111
+ if (isBuiltRuntime()) {
112
+ return;
113
+ }
114
+ await import('@/llm/providers.eager');
115
+ }
116
+
107
117
  export const defaultOmitOptions = new Set([
108
118
  'stream',
109
119
  'thinking',
@@ -717,6 +727,7 @@ export class Run<_T extends t.BaseGraphState> {
717
727
  static async create<T extends t.BaseGraphState>(
718
728
  config: t.RunConfig
719
729
  ): Promise<Run<T>> {
730
+ await ensureSourceModeProviders();
720
731
  /** Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not */
721
732
  if (config.indexTokenCountMap && !config.tokenCounter) {
722
733
  const gc = config.graphConfig;
@@ -1974,10 +1985,7 @@ export class Run<_T extends t.BaseGraphState> {
1974
1985
  clientOptions,
1975
1986
  }) as t.ChatModelInstance;
1976
1987
 
1977
- if (
1978
- isOpenAILike(provider) &&
1979
- (model instanceof ChatOpenAI || model instanceof AzureChatOpenAI)
1980
- ) {
1988
+ if (isOpenAILike(provider) && isLibreChatOpenAIModel(model)) {
1981
1989
  model.temperature = (clientOptions as t.OpenAIClientOptions | undefined)
1982
1990
  ?.temperature as number;
1983
1991
  model.topP = (clientOptions as t.OpenAIClientOptions | undefined)
@@ -1,5 +1,12 @@
1
- import * as math from 'mathjs';
2
1
  import { Tool } from '@langchain/core/tools';
2
+ import { requireLazyModule } from '@/lazyRequire';
3
+
4
+ /** mathjs costs ~150ms of module init; a calculator that is never invoked never pays it. */
5
+ let math: typeof import('mathjs') | undefined;
6
+ const evaluateExpression = (input: string): string => {
7
+ math ??= requireLazyModule<typeof import('mathjs')>('mathjs');
8
+ return math.evaluate(input).toString();
9
+ };
3
10
 
4
11
  export const CalculatorToolName = 'calculator';
5
12
 
@@ -36,7 +43,7 @@ export class Calculator extends Tool {
36
43
 
37
44
  async _call(input: string): Promise<string> {
38
45
  try {
39
- return math.evaluate(input).toString();
46
+ return evaluateExpression(input);
40
47
  } catch {
41
48
  return 'I don\'t know how to do that.';
42
49
  }
package/src/utils/llm.ts CHANGED
@@ -3,6 +3,45 @@ import type { ProviderName } from '@/types';
3
3
  import { getProviderFamily } from '@/llm/providerRegistry';
4
4
  import { Providers } from '@/common';
5
5
 
6
+ const OPENAI_FAMILY_LC_NAMES: ReadonlySet<string> = new Set([
7
+ 'LibreChatOpenAI',
8
+ 'LibreChatAzureOpenAI',
9
+ ]);
10
+
11
+ /** Walks the constructor chain for LangChain's `lc_name` serialization ids.
12
+ * Constructor identity is unusable across lazily loaded module formats — the
13
+ * CJS and ESM builds carry distinct classes — so guards match on the ids the
14
+ * relevant classes declare instead of on `instanceof`. */
15
+ export function constructorChainHasLcName(
16
+ model: unknown,
17
+ names: ReadonlySet<string>
18
+ ): boolean {
19
+ if (typeof model !== 'object' || model == null) {
20
+ return false;
21
+ }
22
+ let ctor: unknown = model.constructor;
23
+ while (typeof ctor === 'function') {
24
+ const lcName = (ctor as { lc_name?: () => string }).lc_name?.();
25
+ if (lcName != null && names.has(lcName)) {
26
+ return true;
27
+ }
28
+ ctor = Object.getPrototypeOf(ctor);
29
+ }
30
+ return false;
31
+ }
32
+
33
+ /** Matches exactly the instances that were `instanceof` this package's
34
+ * `ChatOpenAI`/`AzureChatOpenAI`: only those classes declare the
35
+ * `LibreChatOpenAI`/`LibreChatAzureOpenAI` ids, their subclasses keep them in
36
+ * the chain, and upstream `@langchain/openai` classes never carry them. */
37
+ export function isLibreChatOpenAIModel(
38
+ model: unknown
39
+ ): model is
40
+ | import('@/llm/openai').ChatOpenAI
41
+ | import('@/llm/openai').AzureChatOpenAI {
42
+ return constructorChainHasLcName(model, OPENAI_FAMILY_LC_NAMES);
43
+ }
44
+
6
45
  export function isOpenAILike(provider?: ProviderName): boolean {
7
46
  if (provider == null) {
8
47
  return false;