@librechat/agents 3.6.15 → 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 (180) hide show
  1. package/README.md +41 -0
  2. package/dist/cjs/_virtual/_rolldown/runtime.cjs +10 -0
  3. package/dist/cjs/agents/AgentContext.cjs +1 -1
  4. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +13 -13
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/lazyRequire.cjs +33 -0
  8. package/dist/cjs/lazyRequire.cjs.map +1 -0
  9. package/dist/cjs/llm/anthropic/index.cjs +13 -0
  10. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/index.cjs +10 -1
  12. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  13. package/dist/cjs/llm/bedrock/toolCache.cjs +3 -2
  14. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  15. package/dist/cjs/llm/google/index.cjs +10 -1
  16. package/dist/cjs/llm/google/index.cjs.map +1 -1
  17. package/dist/cjs/llm/init.cjs +7 -4
  18. package/dist/cjs/llm/init.cjs.map +1 -1
  19. package/dist/cjs/llm/invoke.cjs +9 -2
  20. package/dist/cjs/llm/invoke.cjs.map +1 -1
  21. package/dist/cjs/llm/mistral/index.cjs +9 -0
  22. package/dist/cjs/llm/mistral/index.cjs.map +1 -1
  23. package/dist/cjs/llm/openai/index.cjs +41 -1
  24. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  25. package/dist/cjs/llm/openrouter/index.cjs +12 -3
  26. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  27. package/dist/cjs/llm/openrouter/toolCache.cjs +3 -2
  28. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  29. package/dist/cjs/llm/prepareProviderRequest.cjs +6 -3
  30. package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
  31. package/dist/cjs/llm/providerRegistry.cjs +81 -0
  32. package/dist/cjs/llm/providerRegistry.cjs.map +1 -0
  33. package/dist/cjs/llm/providers.cjs +37 -28
  34. package/dist/cjs/llm/providers.cjs.map +1 -1
  35. package/dist/cjs/llm/providers.eager.cjs +21 -0
  36. package/dist/cjs/llm/providers.eager.cjs.map +1 -0
  37. package/dist/cjs/llm/request.cjs +6 -4
  38. package/dist/cjs/llm/request.cjs.map +1 -1
  39. package/dist/cjs/llm/truncation.cjs +2 -1
  40. package/dist/cjs/llm/truncation.cjs.map +1 -1
  41. package/dist/cjs/llm/vertexai/index.cjs +17 -0
  42. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  43. package/dist/cjs/main.cjs +19 -18
  44. package/dist/cjs/messages/core.cjs +9 -4
  45. package/dist/cjs/messages/core.cjs.map +1 -1
  46. package/dist/cjs/messages/format.cjs.map +1 -1
  47. package/dist/cjs/messages/prune.cjs +50 -12
  48. package/dist/cjs/messages/prune.cjs.map +1 -1
  49. package/dist/cjs/provider-registration.cjs +4 -0
  50. package/dist/cjs/run.cjs +9 -4
  51. package/dist/cjs/run.cjs.map +1 -1
  52. package/dist/cjs/stream.cjs +1 -1
  53. package/dist/cjs/stream.cjs.map +1 -1
  54. package/dist/cjs/summarization/node.cjs +2 -2
  55. package/dist/cjs/summarization/node.cjs.map +1 -1
  56. package/dist/cjs/tools/Calculator.cjs +7 -4
  57. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  58. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  59. package/dist/cjs/utils/llm.cjs +22 -4
  60. package/dist/cjs/utils/llm.cjs.map +1 -1
  61. package/dist/esm/_virtual/_rolldown/runtime.mjs +14 -0
  62. package/dist/esm/agents/AgentContext.mjs +1 -1
  63. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  64. package/dist/esm/graphs/Graph.mjs +13 -13
  65. package/dist/esm/graphs/Graph.mjs.map +1 -1
  66. package/dist/esm/lazyRequire.mjs +30 -0
  67. package/dist/esm/lazyRequire.mjs.map +1 -0
  68. package/dist/esm/llm/anthropic/index.mjs +6 -1
  69. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  70. package/dist/esm/llm/bedrock/index.mjs +4 -2
  71. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  72. package/dist/esm/llm/bedrock/toolCache.mjs +3 -2
  73. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  74. package/dist/esm/llm/google/index.mjs +4 -2
  75. package/dist/esm/llm/google/index.mjs.map +1 -1
  76. package/dist/esm/llm/init.mjs +8 -5
  77. package/dist/esm/llm/init.mjs.map +1 -1
  78. package/dist/esm/llm/invoke.mjs +9 -2
  79. package/dist/esm/llm/invoke.mjs.map +1 -1
  80. package/dist/esm/llm/mistral/index.mjs +3 -1
  81. package/dist/esm/llm/mistral/index.mjs.map +1 -1
  82. package/dist/esm/llm/openai/index.mjs +31 -9
  83. package/dist/esm/llm/openai/index.mjs.map +1 -1
  84. package/dist/esm/llm/openrouter/index.mjs +3 -1
  85. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  86. package/dist/esm/llm/openrouter/toolCache.mjs +3 -2
  87. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  88. package/dist/esm/llm/prepareProviderRequest.mjs +7 -4
  89. package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
  90. package/dist/esm/llm/providerRegistry.mjs +76 -0
  91. package/dist/esm/llm/providerRegistry.mjs.map +1 -0
  92. package/dist/esm/llm/providers.eager.mjs +22 -0
  93. package/dist/esm/llm/providers.eager.mjs.map +1 -0
  94. package/dist/esm/llm/providers.mjs +38 -28
  95. package/dist/esm/llm/providers.mjs.map +1 -1
  96. package/dist/esm/llm/request.mjs +6 -4
  97. package/dist/esm/llm/request.mjs.map +1 -1
  98. package/dist/esm/llm/truncation.mjs +2 -1
  99. package/dist/esm/llm/truncation.mjs.map +1 -1
  100. package/dist/esm/llm/vertexai/index.mjs +8 -1
  101. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  102. package/dist/esm/main.mjs +12 -13
  103. package/dist/esm/messages/core.mjs +9 -5
  104. package/dist/esm/messages/core.mjs.map +1 -1
  105. package/dist/esm/messages/format.mjs.map +1 -1
  106. package/dist/esm/messages/prune.mjs +50 -13
  107. package/dist/esm/messages/prune.mjs.map +1 -1
  108. package/dist/esm/provider-registration.mjs +3 -0
  109. package/dist/esm/run.mjs +10 -5
  110. package/dist/esm/run.mjs.map +1 -1
  111. package/dist/esm/stream.mjs +1 -1
  112. package/dist/esm/stream.mjs.map +1 -1
  113. package/dist/esm/summarization/node.mjs +2 -2
  114. package/dist/esm/summarization/node.mjs.map +1 -1
  115. package/dist/esm/tools/Calculator.mjs +7 -2
  116. package/dist/esm/tools/Calculator.mjs.map +1 -1
  117. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  118. package/dist/esm/utils/llm.mjs +21 -5
  119. package/dist/esm/utils/llm.mjs.map +1 -1
  120. package/dist/types/agents/AgentContext.d.ts +5 -5
  121. package/dist/types/index.d.ts +5 -3
  122. package/dist/types/lazyRequire.d.ts +8 -0
  123. package/dist/types/llm/contextOverflowRecovery.d.ts +2 -2
  124. package/dist/types/llm/init.d.ts +21 -14
  125. package/dist/types/llm/invoke.d.ts +5 -6
  126. package/dist/types/llm/prepareProviderRequest.d.ts +5 -6
  127. package/dist/types/llm/providerRegistry.d.ts +22 -0
  128. package/dist/types/llm/providers.d.ts +4 -5
  129. package/dist/types/llm/providers.eager.d.ts +1 -0
  130. package/dist/types/llm/request.d.ts +2 -3
  131. package/dist/types/llm/truncation.d.ts +2 -2
  132. package/dist/types/messages/core.d.ts +3 -2
  133. package/dist/types/messages/format.d.ts +3 -3
  134. package/dist/types/messages/prune.d.ts +8 -6
  135. package/dist/types/provider-registration.d.ts +15 -0
  136. package/dist/types/stream.d.ts +1 -2
  137. package/dist/types/summarization/node.d.ts +1 -2
  138. package/dist/types/types/activityLabel.d.ts +3 -4
  139. package/dist/types/types/graph.d.ts +4 -5
  140. package/dist/types/types/llm.d.ts +54 -13
  141. package/dist/types/types/reasoningLabel.d.ts +2 -3
  142. package/dist/types/types/run.d.ts +3 -3
  143. package/dist/types/types/summarize.d.ts +2 -2
  144. package/dist/types/utils/errors.d.ts +3 -3
  145. package/dist/types/utils/llm.d.ts +14 -4
  146. package/dist/types/utils/llmConfig.d.ts +2 -2
  147. package/package.json +66 -1
  148. package/src/agents/AgentContext.ts +27 -26
  149. package/src/graphs/Graph.ts +16 -22
  150. package/src/index.ts +8 -3
  151. package/src/lazyRequire.ts +67 -0
  152. package/src/llm/bedrock/toolCache.ts +6 -2
  153. package/src/llm/contextOverflowRecovery.ts +2 -2
  154. package/src/llm/init.ts +55 -25
  155. package/src/llm/invoke.ts +36 -21
  156. package/src/llm/openrouter/toolCache.ts +8 -2
  157. package/src/llm/prepareProviderRequest.ts +19 -18
  158. package/src/llm/providerRegistry.ts +174 -0
  159. package/src/llm/providers.eager.ts +24 -0
  160. package/src/llm/providers.ts +99 -42
  161. package/src/llm/request.ts +10 -6
  162. package/src/llm/truncation.ts +9 -3
  163. package/src/messages/core.ts +18 -13
  164. package/src/messages/format.ts +14 -16
  165. package/src/messages/prune.ts +108 -28
  166. package/src/provider-registration.ts +29 -0
  167. package/src/run.ts +15 -7
  168. package/src/session/AgentSession.ts +1 -1
  169. package/src/stream.ts +7 -8
  170. package/src/summarization/node.ts +7 -7
  171. package/src/tools/Calculator.ts +9 -2
  172. package/src/types/activityLabel.ts +3 -4
  173. package/src/types/graph.ts +5 -5
  174. package/src/types/llm.ts +95 -13
  175. package/src/types/reasoningLabel.ts +2 -3
  176. package/src/types/run.ts +3 -3
  177. package/src/types/summarize.ts +2 -2
  178. package/src/utils/errors.ts +3 -3
  179. package/src/utils/llm.ts +61 -15
  180. package/src/utils/llmConfig.ts +4 -4
@@ -1,4 +1,5 @@
1
1
  import type * as t from '@/types';
2
+ import { getProviderFamily } from '@/llm/providerRegistry';
2
3
  import { Providers } from '@/common';
3
4
 
4
5
  /**
@@ -7,20 +8,21 @@ import { Providers } from '@/common';
7
8
  * and OpenAI-compat (modelKwargs.thinking).
8
9
  */
9
10
  export function isThinkingEnabled(
10
- provider: Providers,
11
+ provider: t.ProviderName,
11
12
  clientOptions?: t.ClientOptions
12
13
  ): boolean {
13
14
  if (!clientOptions) return false;
15
+ const family = getProviderFamily(provider);
14
16
 
15
17
  if (
16
- provider === Providers.ANTHROPIC &&
18
+ (provider === Providers.ANTHROPIC || family === 'anthropic') &&
17
19
  (clientOptions as t.AnthropicClientOptions).thinking != null
18
20
  ) {
19
21
  return true;
20
22
  }
21
23
 
22
24
  if (
23
- provider === Providers.BEDROCK &&
25
+ (provider === Providers.BEDROCK || family === 'bedrock') &&
24
26
  (clientOptions as t.BedrockAnthropicInput).additionalModelRequestFields?.[
25
27
  'thinking'
26
28
  ] != null
@@ -29,7 +31,7 @@ export function isThinkingEnabled(
29
31
  }
30
32
 
31
33
  if (
32
- provider === Providers.OPENAI &&
34
+ (provider === Providers.OPENAI || family === 'openai') &&
33
35
  (
34
36
  (clientOptions as t.OpenAIClientOptions).modelKwargs
35
37
  ?.thinking as t.AnthropicClientOptions['thinking']
@@ -47,9 +49,11 @@ export function isThinkingEnabled(
47
49
  * use `maxTokens`.
48
50
  */
49
51
  export function getMaxOutputTokensKey(
50
- provider: Providers | string
52
+ provider: t.ProviderName
51
53
  ): 'maxOutputTokens' | 'maxTokens' {
52
- return provider === Providers.GOOGLE || provider === Providers.VERTEXAI
54
+ return provider === Providers.GOOGLE ||
55
+ provider === Providers.VERTEXAI ||
56
+ getProviderFamily(provider) === 'google'
53
57
  ? 'maxOutputTokens'
54
58
  : 'maxTokens';
55
59
  }
@@ -1,4 +1,6 @@
1
1
  import type { AIMessageChunk, BaseMessage } from '@langchain/core/messages';
2
+ import type { ProviderName } from '@/types';
3
+ import { getProviderFamily } from '@/llm/providerRegistry';
2
4
  import { Providers } from '@/common';
3
5
 
4
6
  /**
@@ -7,7 +9,7 @@ import { Providers } from '@/common';
7
9
  * `functionCall` whole and seal it on arrival, so a `MAX_TOKENS` finish does
8
10
  * NOT imply the arguments were cut off — the truncation guard must skip them.
9
11
  */
10
- const ATOMIC_TOOL_CALL_ARG_PROVIDERS = new Set<Providers>([
12
+ const ATOMIC_TOOL_CALL_ARG_PROVIDERS = new Set<ProviderName>([
11
13
  Providers.GOOGLE,
12
14
  Providers.VERTEXAI,
13
15
  ]);
@@ -162,12 +164,16 @@ function collectToolCallNames(message: AIMessageChunk | BaseMessage): string[] {
162
164
  */
163
165
  export function assertNotTruncatedToolCall(
164
166
  message: AIMessageChunk | BaseMessage | undefined | null,
165
- provider?: Providers
167
+ provider?: ProviderName
166
168
  ): void {
167
169
  if (message == null) {
168
170
  return;
169
171
  }
170
- if (provider != null && ATOMIC_TOOL_CALL_ARG_PROVIDERS.has(provider)) {
172
+ if (
173
+ provider != null &&
174
+ (ATOMIC_TOOL_CALL_ARG_PROVIDERS.has(provider) ||
175
+ getProviderFamily(provider) === 'google')
176
+ ) {
171
177
  return;
172
178
  }
173
179
  const stopReason = getTruncationStopReason(message);
@@ -90,7 +90,7 @@ const modifyContent = ({
90
90
  messageType,
91
91
  content,
92
92
  }: {
93
- provider: Providers;
93
+ provider: t.ProviderName;
94
94
  messageType: string;
95
95
  content: t.ExtendedMessageContent[];
96
96
  }): (t.ExtendedMessageContent | null)[] => {
@@ -243,7 +243,7 @@ function hasReasoningContent(content: BaseMessage['content']): boolean {
243
243
  }
244
244
 
245
245
  export function modifyDeltaProperties(
246
- provider: Providers,
246
+ provider: t.ProviderName,
247
247
  obj?: AIMessageChunk
248
248
  ): AIMessageChunk | undefined {
249
249
  if (!obj || typeof obj !== 'object') return obj;
@@ -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
  }
@@ -27,6 +27,7 @@ import type {
27
27
  ToolCallPart,
28
28
  TPayload,
29
29
  TMessage,
30
+ ProviderName,
30
31
  } from '@/types';
31
32
  import type {
32
33
  ProviderMessageAttribution,
@@ -366,7 +367,7 @@ export const formatFromLangChain = (
366
367
  interface FormatAssistantMessageOptions {
367
368
  preserveUnpairedServerToolUses?: boolean;
368
369
  preserveReasoningContent?: boolean;
369
- provider?: Providers;
370
+ provider?: ProviderName;
370
371
  sourceMessageId?: string;
371
372
  sourceContentPartOffset?: number;
372
373
  sourceContentPartIndices?: readonly SourceContentPartIndices[];
@@ -376,7 +377,7 @@ interface FormatAssistantMessageOptions {
376
377
  type SourceContentPartIndices = number | readonly number[];
377
378
 
378
379
  interface FormatAgentMessagesOptions {
379
- provider?: Providers;
380
+ provider?: ProviderName;
380
381
  /** Reconstruct hidden `reasoning_content` from `THINK` parts onto prior
381
382
  * tool-call messages. Explicit opt-in for OpenAI-compatible endpoints that
382
383
  * replay reasoning across turns; defaults to on for DeepSeek thinking-mode. */
@@ -478,10 +479,7 @@ function collectTrustedToolResultSourceContentPartIndices(
478
479
  let trusted: Set<number> | undefined;
479
480
  let previousPart: MessageContentComplex | null | undefined;
480
481
  for (let index = 0; index < content.length; index++) {
481
- const part = content[index] as
482
- | MessageContentComplex
483
- | null
484
- | undefined;
482
+ const part = content[index] as MessageContentComplex | null | undefined;
485
483
  if (part == null) {
486
484
  previousPart = part;
487
485
  continue;
@@ -509,13 +507,18 @@ function sourceContentPartIndicesAreTrustedToolResult(
509
507
  trustedToolSourceContentPartIndices: ReadonlySet<number> | undefined
510
508
  ): boolean {
511
509
  if (typeof sourceContentPartIndices === 'number') {
512
- return trustedToolSourceContentPartIndices?.has(sourceContentPartIndices) === true;
510
+ return (
511
+ trustedToolSourceContentPartIndices?.has(sourceContentPartIndices) ===
512
+ true
513
+ );
513
514
  }
514
515
  if (sourceContentPartIndices.length === 0) {
515
516
  return false;
516
517
  }
517
518
  for (const sourceContentPartIndex of sourceContentPartIndices) {
518
- if (trustedToolSourceContentPartIndices?.has(sourceContentPartIndex) !== true) {
519
+ if (
520
+ trustedToolSourceContentPartIndices?.has(sourceContentPartIndex) !== true
521
+ ) {
519
522
  return false;
520
523
  }
521
524
  }
@@ -1377,10 +1380,7 @@ function stampSourceMessageIdentity(
1377
1380
  },
1378
1381
  ];
1379
1382
  }
1380
- setProviderMessageProvenance(
1381
- message,
1382
- partsToStamp
1383
- );
1383
+ setProviderMessageProvenance(message, partsToStamp);
1384
1384
  if (sourceMessageId == null || derivedIndex !== 0) {
1385
1385
  return;
1386
1386
  }
@@ -2759,9 +2759,7 @@ function getSyntheticProviderContextProvenanceParts(
2759
2759
  ): ProviderMessageProvenancePart[] | null {
2760
2760
  /** Fold labels are generated context, while retained source bytes keep their
2761
2761
  * original attribution so downstream policy can still route them exactly. */
2762
- const parts: ProviderMessageProvenancePart[] = [
2763
- { attribution: 'synthetic' },
2764
- ];
2762
+ const parts: ProviderMessageProvenancePart[] = [{ attribution: 'synthetic' }];
2765
2763
  for (const source of sourceMessages) {
2766
2764
  const {
2767
2765
  message: sourceMessage,
@@ -3311,7 +3309,7 @@ function appendToolCalls(
3311
3309
  */
3312
3310
  export function ensureThinkingBlockInMessages(
3313
3311
  messages: BaseMessage[],
3314
- _provider: Providers,
3312
+ _provider: ProviderName,
3315
3313
  config?: RunnableConfig,
3316
3314
  runStartIndex?: number
3317
3315
  ): BaseMessage[] {
@@ -13,6 +13,7 @@ import type {
13
13
  } from '@/types/stream';
14
14
  import type { ContextPruningConfig } from '@/types/graph';
15
15
  import type { TokenCounter } from '@/types/run';
16
+ import type { ProviderName } from '@/types';
16
17
  import {
17
18
  cloneToolMessageWithContent,
18
19
  compactToolContent,
@@ -29,6 +30,8 @@ import {
29
30
  import { resolveContextPruningSettings } from './contextPruningSettings';
30
31
  import { hasUnsafeStructuredSerialization } from '@/utils/tokens';
31
32
  import { ContentTypes, Providers, Constants } from '@/common';
33
+ import { getProviderFamily } from '@/llm/providerRegistry';
34
+ import { dropIncompleteToolStreamContent } from './core';
32
35
  import { applyContextPruning } from './contextPruning';
33
36
  import { toLangChainContent } from './langchain';
34
37
 
@@ -130,7 +133,7 @@ export function clampCalibrationRatio(ratio: number): number {
130
133
  }
131
134
 
132
135
  export type PruneMessagesFactoryParams = {
133
- provider?: Providers;
136
+ provider?: ProviderName;
134
137
  maxTokens: number;
135
138
  /** Per-tool-result character cap applied while reconciling cached counts. */
136
139
  maxToolResultChars?: number;
@@ -1410,6 +1413,47 @@ function cloneWithProjectedProperties<T extends object>(
1410
1413
  ) as T;
1411
1414
  }
1412
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
+
1413
1457
  function createBoundedTruncationValue(
1414
1458
  preview: string,
1415
1459
  originalChars: number,
@@ -1873,9 +1917,10 @@ export function calculateMaxToolCallInputChars(
1873
1917
  * Returns the original array when no message changes and otherwise clones only
1874
1918
  * the array and AI messages whose inline input or `tool_calls` args changed.
1875
1919
  */
1876
- export function projectToolCallInputs(
1920
+ function projectToolCallInputsInternal(
1877
1921
  messages: BaseMessage[],
1878
- maxInputChars: number
1922
+ maxInputChars: number,
1923
+ dropIncompleteStreamContent: boolean
1879
1924
  ): BaseMessage[] {
1880
1925
  const normalizedMaxInputChars = normalizeToolInputLimit(maxInputChars);
1881
1926
  let projectedMessages: BaseMessage[] | undefined;
@@ -1887,16 +1932,21 @@ export function projectToolCallInputs(
1887
1932
  }
1888
1933
 
1889
1934
  const aiMessage = message as AIMessage | AIMessageChunk;
1890
- let projectedContent = aiMessage.content;
1891
- let contentChanged = false;
1892
- if (Array.isArray(aiMessage.content)) {
1893
- 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[];
1894
1942
  const mappedContent = originalContent.map((block) =>
1895
1943
  projectInlineToolInput(block, normalizedMaxInputChars)
1896
1944
  );
1897
- contentChanged = mappedContent.some(
1898
- (block, blockIndex) => block !== originalContent[blockIndex]
1899
- );
1945
+ contentChanged =
1946
+ contentChanged ||
1947
+ mappedContent.some(
1948
+ (block, blockIndex) => block !== originalContent[blockIndex]
1949
+ );
1900
1950
  projectedContent = toLangChainContent(mappedContent);
1901
1951
  }
1902
1952
 
@@ -2018,18 +2068,45 @@ export function projectToolCallInputs(
2018
2068
  continue;
2019
2069
  }
2020
2070
 
2021
- const projectedMessage = cloneWithProjectedProperties(aiMessage, {
2071
+ const changes = {
2022
2072
  content: projectedContent,
2023
2073
  tool_calls: projectedToolCalls.length > 0 ? projectedToolCalls : [],
2024
2074
  additional_kwargs: projectedAdditionalKwargs,
2025
2075
  response_metadata: projectedResponseMetadata,
2026
- });
2076
+ };
2077
+ const projectedMessage =
2078
+ streamContent === aiMessage.content
2079
+ ? cloneWithProjectedProperties(aiMessage, changes)
2080
+ : cloneAIMessageWithProjectedStreamContent(
2081
+ aiMessage,
2082
+ changes,
2083
+ streamContent
2084
+ );
2027
2085
  projectedMessages ??= [...messages];
2028
2086
  projectedMessages[i] = projectedMessage;
2029
2087
  }
2030
2088
  return projectedMessages ?? messages;
2031
2089
  }
2032
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
+
2033
2110
  export function preFlightTruncateToolCallInputs(params: {
2034
2111
  messages: BaseMessage[];
2035
2112
  maxContextTokens: number;
@@ -2065,6 +2142,15 @@ type ThinkingBlocks = {
2065
2142
  };
2066
2143
 
2067
2144
  export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2145
+ const providerFamily =
2146
+ factoryParams.provider == null
2147
+ ? undefined
2148
+ : getProviderFamily(factoryParams.provider);
2149
+ const usesBedrockThinking =
2150
+ factoryParams.provider === Providers.BEDROCK ||
2151
+ providerFamily === 'bedrock';
2152
+ const usesOpenAIThinking =
2153
+ factoryParams.provider === Providers.OPENAI || providerFamily === 'openai';
2068
2154
  const indexTokenCountMap = { ...factoryParams.indexTokenCountMap };
2069
2155
  let lastTurnStartIndex = factoryParams.startIndex;
2070
2156
  let lastCutOffIndex = 0;
@@ -2152,10 +2238,7 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2152
2238
  };
2153
2239
  }
2154
2240
 
2155
- if (
2156
- factoryParams.provider === Providers.OPENAI &&
2157
- factoryParams.thinkingEnabled === true
2158
- ) {
2241
+ if (usesOpenAIThinking && factoryParams.thinkingEnabled === true) {
2159
2242
  for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
2160
2243
  const m = params.messages[i];
2161
2244
  if (
@@ -2729,10 +2812,9 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2729
2812
  thinkingEnabled: factoryParams.thinkingEnabled,
2730
2813
  tokenCounter: factoryParams.tokenCounter,
2731
2814
  instructionTokens: rawSpaceInstructionTokens,
2732
- reasoningType:
2733
- factoryParams.provider === Providers.BEDROCK
2734
- ? ContentTypes.REASONING_CONTENT
2735
- : ContentTypes.THINKING,
2815
+ reasoningType: usesBedrockThinking
2816
+ ? ContentTypes.REASONING_CONTENT
2817
+ : ContentTypes.THINKING,
2736
2818
  thinkingStartIndex:
2737
2819
  factoryParams.thinkingEnabled === true
2738
2820
  ? runThinkingStartIndex
@@ -2826,10 +2908,9 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2826
2908
  thinkingEnabled: factoryParams.thinkingEnabled,
2827
2909
  tokenCounter: factoryParams.tokenCounter,
2828
2910
  instructionTokens: currentInstructionTokens,
2829
- reasoningType:
2830
- factoryParams.provider === Providers.BEDROCK
2831
- ? ContentTypes.REASONING_CONTENT
2832
- : ContentTypes.THINKING,
2911
+ reasoningType: usesBedrockThinking
2912
+ ? ContentTypes.REASONING_CONTENT
2913
+ : ContentTypes.THINKING,
2833
2914
  thinkingStartIndex:
2834
2915
  factoryParams.thinkingEnabled === true
2835
2916
  ? runThinkingStartIndex
@@ -2963,10 +3044,9 @@ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
2963
3044
  thinkingEnabled: factoryParams.thinkingEnabled,
2964
3045
  tokenCounter: factoryParams.tokenCounter,
2965
3046
  instructionTokens: currentInstructionTokens,
2966
- reasoningType:
2967
- factoryParams.provider === Providers.BEDROCK
2968
- ? ContentTypes.REASONING_CONTENT
2969
- : ContentTypes.THINKING,
3047
+ reasoningType: usesBedrockThinking
3048
+ ? ContentTypes.REASONING_CONTENT
3049
+ : ContentTypes.THINKING,
2970
3050
  thinkingStartIndex:
2971
3051
  factoryParams.thinkingEnabled === true
2972
3052
  ? runThinkingStartIndex
@@ -0,0 +1,29 @@
1
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
+
3
+ declare const CUSTOM_PROVIDER_OPTIONS_TYPE: unique symbol;
4
+
5
+ /** Declaration-merge this map to type host-registered provider options. */
6
+ export interface CustomProviderOptionsMap {
7
+ readonly [CUSTOM_PROVIDER_OPTIONS_TYPE]?: never;
8
+ }
9
+
10
+ export type ProviderFamily =
11
+ | 'openai'
12
+ | 'anthropic'
13
+ | 'bedrock'
14
+ | 'google'
15
+ | 'mistral'
16
+ | 'generic';
17
+
18
+ export interface ProviderRegistrationOptions<
19
+ TOptions extends object,
20
+ TModel extends BaseChatModel,
21
+ > {
22
+ provider: string;
23
+ model: new (config: TOptions) => TModel;
24
+ family?: ProviderFamily;
25
+ manualToolStream?: boolean;
26
+ strictAlternation?: boolean;
27
+ }
28
+
29
+ export { registerProvider } from './llm/providers';
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',
@@ -452,7 +462,7 @@ export class Run<_T extends t.BaseGraphState> {
452
462
  provider,
453
463
  clientOptions,
454
464
  agentId: 'default',
455
- };
465
+ } as t.AgentInputs;
456
466
  signal = legacySignal;
457
467
  }
458
468
 
@@ -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)
@@ -588,7 +588,7 @@ function createAgentInputFromGraphConfig(
588
588
  provider,
589
589
  clientOptions,
590
590
  agentId: 'default',
591
- };
591
+ } as t.AgentInputs;
592
592
  }
593
593
  const summarizationConfig: t.SummarizationConfig = {
594
594
  ...(agent.summarizationConfig ?? {}),
package/src/stream.ts CHANGED
@@ -820,12 +820,11 @@ function startEagerToolExecutions(args: {
820
820
  toolCalls: entries.map((entry) => entry.request),
821
821
  userId: graph.config?.configurable?.user_id as string | undefined,
822
822
  agentId: agentContext?.agentId,
823
- callerCapabilityProjection:
824
- (
825
- agentContext as
826
- | Partial<Pick<AgentContext, 'getCallerCapabilityProjectionSnapshot'>>
827
- | undefined
828
- )?.getCallerCapabilityProjectionSnapshot?.(),
823
+ callerCapabilityProjection: (
824
+ agentContext as
825
+ | Partial<Pick<AgentContext, 'getCallerCapabilityProjectionSnapshot'>>
826
+ | undefined
827
+ )?.getCallerCapabilityProjectionSnapshot?.(),
829
828
  configurable: graph.config?.configurable as
830
829
  | Record<string, unknown>
831
830
  | undefined,
@@ -1327,7 +1326,7 @@ export function getChunkContent({
1327
1326
  reasoningKey,
1328
1327
  }: {
1329
1328
  chunk?: Partial<AIMessageChunk>;
1330
- provider?: Providers;
1329
+ provider?: t.ProviderName;
1331
1330
  reasoningKey: 'reasoning_content' | 'reasoning';
1332
1331
  }): string | t.MessageContentComplex[] | undefined {
1333
1332
  if (
@@ -1851,7 +1850,7 @@ export class ChatModelStreamHandler implements t.EventHandler {
1851
1850
 
1852
1851
  if (Array.isArray(content) && content.every(isTextContentPart)) {
1853
1852
  const contentGroups = splitAssistantTextContentByPhase(content);
1854
- const currentStepId = graph.stepKeyIds?.get(stepKey)?.at(-1);
1853
+ const currentStepId = graph.stepKeyIds.get(stepKey)?.at(-1);
1855
1854
  const currentStep =
1856
1855
  currentStepId == null ? undefined : graph.getRunStep(currentStepId);
1857
1856
  const currentPhase =
@@ -657,7 +657,7 @@ async function executeSummarizationWithFallback(params: {
657
657
  * `log('error', ...)` path below rather than bubbling up silently.
658
658
  */
659
659
  const summarizationModel = initializeModel({
660
- provider: clientConfig.provider as Providers,
660
+ provider: clientConfig.provider,
661
661
  clientOptions: clientConfig.clientOptions as t.ClientOptions,
662
662
  tools: agentContext.getToolsForBinding(),
663
663
  }) as t.ChatModel;
@@ -670,13 +670,13 @@ async function executeSummarizationWithFallback(params: {
670
670
  priorSummaryText,
671
671
  config: summarizeConfig,
672
672
  stepId,
673
- provider: clientConfig.provider as Providers,
673
+ provider: clientConfig.provider,
674
674
  reasoningKey: agentContext.reasoningKey,
675
675
  graph,
676
676
  usePromptCache,
677
677
  promptCacheTtl:
678
- (clientConfig.provider as Providers) === Providers.ANTHROPIC ||
679
- (clientConfig.provider as Providers) === Providers.OPENROUTER
678
+ clientConfig.provider === Providers.ANTHROPIC ||
679
+ clientConfig.provider === Providers.OPENROUTER
680
680
  ? resolvePromptCacheTtl(
681
681
  (
682
682
  clientConfig.clientOptions as {
@@ -737,7 +737,7 @@ async function executeSummarizationWithFallback(params: {
737
737
  const onChunk = createSummarizationChunkHandler({
738
738
  stepId,
739
739
  config: traceConfig(summarizeConfig, 'cache_hit_compaction'),
740
- provider: clientConfig.provider as Providers,
740
+ provider: clientConfig.provider,
741
741
  reasoningKey: agentContext.reasoningKey,
742
742
  graph,
743
743
  });
@@ -1509,7 +1509,7 @@ export function createSummarizationChunkHandler({
1509
1509
  }: {
1510
1510
  stepId?: string;
1511
1511
  config?: RunnableConfig;
1512
- provider?: Providers;
1512
+ provider?: t.ProviderName;
1513
1513
  reasoningKey?: 'reasoning_content' | 'reasoning';
1514
1514
  graph?: StreamLimitState & { getBreakerController?: () => AbortController };
1515
1515
  }): OnChunk | undefined {
@@ -1622,7 +1622,7 @@ async function summarizeWithCacheHit({
1622
1622
  priorSummaryText: string;
1623
1623
  config?: RunnableConfig;
1624
1624
  stepId?: string;
1625
- provider: Providers;
1625
+ provider: t.ProviderName;
1626
1626
  reasoningKey?: 'reasoning_content' | 'reasoning';
1627
1627
  graph?: StreamLimitState & { getBreakerSignal?: () => AbortSignal };
1628
1628
  usePromptCache?: boolean;