@librechat/agents 3.1.51 → 3.1.53

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 (105) hide show
  1. package/dist/cjs/graphs/Graph.cjs +43 -16
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/google/index.cjs.map +1 -1
  4. package/dist/cjs/llm/openrouter/index.cjs +59 -5
  5. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  6. package/dist/cjs/llm/vertexai/index.cjs +16 -2
  7. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  8. package/dist/cjs/main.cjs +2 -0
  9. package/dist/cjs/main.cjs.map +1 -1
  10. package/dist/cjs/run.cjs +32 -2
  11. package/dist/cjs/run.cjs.map +1 -1
  12. package/dist/cjs/utils/run.cjs +3 -1
  13. package/dist/cjs/utils/run.cjs.map +1 -1
  14. package/dist/esm/graphs/Graph.mjs +43 -16
  15. package/dist/esm/graphs/Graph.mjs.map +1 -1
  16. package/dist/esm/llm/google/index.mjs.map +1 -1
  17. package/dist/esm/llm/openrouter/index.mjs +59 -5
  18. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  19. package/dist/esm/llm/vertexai/index.mjs +16 -2
  20. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  21. package/dist/esm/main.mjs +1 -0
  22. package/dist/esm/main.mjs.map +1 -1
  23. package/dist/esm/run.mjs +32 -2
  24. package/dist/esm/run.mjs.map +1 -1
  25. package/dist/esm/utils/run.mjs +3 -1
  26. package/dist/esm/utils/run.mjs.map +1 -1
  27. package/dist/types/graphs/Graph.d.ts +7 -0
  28. package/dist/types/index.d.ts +2 -0
  29. package/dist/types/llm/google/index.d.ts +2 -3
  30. package/dist/types/llm/openrouter/index.d.ts +21 -1
  31. package/dist/types/llm/vertexai/index.d.ts +2 -1
  32. package/dist/types/run.d.ts +1 -0
  33. package/dist/types/types/llm.d.ts +7 -2
  34. package/dist/types/types/run.d.ts +2 -0
  35. package/package.json +1 -1
  36. package/src/graphs/Graph.ts +49 -20
  37. package/src/index.ts +6 -0
  38. package/src/llm/google/index.ts +2 -3
  39. package/src/llm/openrouter/index.ts +117 -6
  40. package/src/llm/openrouter/reasoning.test.ts +207 -0
  41. package/src/llm/vertexai/index.ts +20 -3
  42. package/src/run.ts +40 -2
  43. package/src/scripts/ant_web_search.ts +1 -0
  44. package/src/scripts/ant_web_search_edge_case.ts +1 -0
  45. package/src/scripts/ant_web_search_error_edge_case.ts +1 -0
  46. package/src/scripts/bedrock-content-aggregation-test.ts +1 -0
  47. package/src/scripts/bedrock-parallel-tools-test.ts +1 -0
  48. package/src/scripts/caching.ts +1 -0
  49. package/src/scripts/code_exec.ts +1 -0
  50. package/src/scripts/code_exec_files.ts +1 -0
  51. package/src/scripts/code_exec_multi_session.ts +1 -0
  52. package/src/scripts/code_exec_ptc.ts +1 -0
  53. package/src/scripts/code_exec_session.ts +1 -0
  54. package/src/scripts/code_exec_simple.ts +1 -0
  55. package/src/scripts/content.ts +1 -0
  56. package/src/scripts/image.ts +1 -0
  57. package/src/scripts/memory.ts +16 -6
  58. package/src/scripts/multi-agent-chain.ts +1 -0
  59. package/src/scripts/multi-agent-conditional.ts +1 -0
  60. package/src/scripts/multi-agent-document-review-chain.ts +1 -0
  61. package/src/scripts/multi-agent-hybrid-flow.ts +1 -0
  62. package/src/scripts/multi-agent-parallel-start.ts +1 -0
  63. package/src/scripts/multi-agent-parallel.ts +1 -0
  64. package/src/scripts/multi-agent-sequence.ts +1 -0
  65. package/src/scripts/multi-agent-supervisor.ts +1 -0
  66. package/src/scripts/multi-agent-test.ts +1 -0
  67. package/src/scripts/parallel-asymmetric-tools-test.ts +1 -0
  68. package/src/scripts/parallel-full-metadata-test.ts +1 -0
  69. package/src/scripts/parallel-tools-test.ts +1 -0
  70. package/src/scripts/programmatic_exec_agent.ts +1 -0
  71. package/src/scripts/search.ts +1 -0
  72. package/src/scripts/sequential-full-metadata-test.ts +1 -0
  73. package/src/scripts/simple.ts +1 -0
  74. package/src/scripts/single-agent-metadata-test.ts +1 -0
  75. package/src/scripts/stream.ts +1 -0
  76. package/src/scripts/test-handoff-preamble.ts +1 -0
  77. package/src/scripts/test-handoff-steering.ts +3 -0
  78. package/src/scripts/test-multi-agent-list-handoff.ts +1 -0
  79. package/src/scripts/test-parallel-agent-labeling.ts +2 -0
  80. package/src/scripts/test-parallel-handoffs.ts +1 -0
  81. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -0
  82. package/src/scripts/test-thinking-handoff.ts +1 -0
  83. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +1 -0
  84. package/src/scripts/test-tool-before-handoff-role-order.ts +1 -0
  85. package/src/scripts/test-tools-before-handoff.ts +1 -0
  86. package/src/scripts/thinking-bedrock.ts +1 -0
  87. package/src/scripts/thinking.ts +1 -0
  88. package/src/scripts/tools.ts +1 -0
  89. package/src/specs/agent-handoffs.test.ts +1 -0
  90. package/src/specs/anthropic.simple.test.ts +4 -0
  91. package/src/specs/azure.simple.test.ts +142 -3
  92. package/src/specs/cache.simple.test.ts +8 -0
  93. package/src/specs/custom-event-await.test.ts +2 -0
  94. package/src/specs/deepseek.simple.test.ts +3 -0
  95. package/src/specs/moonshot.simple.test.ts +5 -0
  96. package/src/specs/openai.simple.test.ts +3 -0
  97. package/src/specs/openrouter.simple.test.ts +164 -2
  98. package/src/specs/prune.test.ts +1 -0
  99. package/src/specs/reasoning.test.ts +1 -0
  100. package/src/specs/thinking-handoff.test.ts +1 -0
  101. package/src/specs/tool-error.test.ts +1 -0
  102. package/src/types/llm.ts +7 -2
  103. package/src/types/run.ts +2 -0
  104. package/src/utils/llmConfig.ts +3 -4
  105. package/src/utils/run.ts +4 -2
@@ -10,9 +10,8 @@ import type {
10
10
  } from '@google/generative-ai';
11
11
  import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
12
12
  import type { BaseMessage, UsageMetadata } from '@langchain/core/messages';
13
- import type { GeminiGenerationConfig } from '@langchain/google-common';
14
13
  import type { GeminiApiUsageMetadata, InputTokenDetails } from './types';
15
- import type { GoogleClientOptions } from '@/types';
14
+ import type { GoogleClientOptions, GoogleThinkingConfig } from '@/types';
16
15
  import {
17
16
  convertResponseContentToChatGenerationChunk,
18
17
  convertBaseMessagesToContent,
@@ -20,7 +19,7 @@ import {
20
19
  } from './utils/common';
21
20
 
22
21
  export class CustomChatGoogleGenerativeAI extends ChatGoogleGenerativeAI {
23
- thinkingConfig?: GeminiGenerationConfig['thinkingConfig'];
22
+ thinkingConfig?: GoogleThinkingConfig;
24
23
 
25
24
  /**
26
25
  * Override to add gemini-3 model support for multimodal and function calling thought signatures
@@ -29,24 +29,135 @@ type OpenAIRoleEnum =
29
29
  | 'function'
30
30
  | 'tool';
31
31
 
32
- export interface ChatOpenRouterCallOptions extends ChatOpenAICallOptions {
32
+ export type OpenRouterReasoningEffort =
33
+ | 'xhigh'
34
+ | 'high'
35
+ | 'medium'
36
+ | 'low'
37
+ | 'minimal'
38
+ | 'none';
39
+
40
+ export interface OpenRouterReasoning {
41
+ effort?: OpenRouterReasoningEffort;
42
+ max_tokens?: number;
43
+ exclude?: boolean;
44
+ enabled?: boolean;
45
+ }
46
+
47
+ export interface ChatOpenRouterCallOptions
48
+ extends Omit<ChatOpenAICallOptions, 'reasoning'> {
49
+ /** @deprecated Use `reasoning` object instead */
33
50
  include_reasoning?: boolean;
51
+ reasoning?: OpenRouterReasoning;
34
52
  modelKwargs?: OpenAIChatInput['modelKwargs'];
35
53
  }
54
+
55
+ /** invocationParams return type extended with OpenRouter reasoning */
56
+ export type OpenRouterInvocationParams = Omit<
57
+ OpenAIClient.Chat.ChatCompletionCreateParams,
58
+ 'messages'
59
+ > & {
60
+ reasoning?: OpenRouterReasoning;
61
+ };
36
62
  export class ChatOpenRouter extends ChatOpenAI {
63
+ private openRouterReasoning?: OpenRouterReasoning;
64
+ /** @deprecated Use `reasoning` object instead */
65
+ private includeReasoning?: boolean;
66
+
37
67
  constructor(_fields: Partial<ChatOpenRouterCallOptions>) {
38
- const { include_reasoning, modelKwargs = {}, ...fields } = _fields;
68
+ const {
69
+ include_reasoning,
70
+ reasoning: openRouterReasoning,
71
+ modelKwargs = {},
72
+ ...fields
73
+ } = _fields;
74
+
75
+ // Extract reasoning from modelKwargs if provided there (e.g., from LLMConfig)
76
+ const { reasoning: mkReasoning, ...restModelKwargs } = modelKwargs as {
77
+ reasoning?: OpenRouterReasoning;
78
+ } & Record<string, unknown>;
79
+
39
80
  super({
40
81
  ...fields,
41
- modelKwargs: {
42
- ...modelKwargs,
43
- include_reasoning,
44
- },
82
+ modelKwargs: restModelKwargs,
45
83
  });
84
+
85
+ // Merge reasoning config: modelKwargs.reasoning < constructor reasoning
86
+ if (mkReasoning != null || openRouterReasoning != null) {
87
+ this.openRouterReasoning = {
88
+ ...mkReasoning,
89
+ ...openRouterReasoning,
90
+ };
91
+ }
92
+
93
+ this.includeReasoning = include_reasoning;
46
94
  }
47
95
  static lc_name(): 'LibreChatOpenRouter' {
48
96
  return 'LibreChatOpenRouter';
49
97
  }
98
+
99
+ // @ts-expect-error - OpenRouter reasoning extends OpenAI Reasoning with additional
100
+ // effort levels ('xhigh' | 'none' | 'minimal') not in ReasoningEffort.
101
+ // The parent's generic conditional return type cannot be widened in an override.
102
+ override invocationParams(
103
+ options?: this['ParsedCallOptions'],
104
+ extra?: { streaming?: boolean }
105
+ ): OpenRouterInvocationParams {
106
+ type MutableParams = Omit<
107
+ OpenAIClient.Chat.ChatCompletionCreateParams,
108
+ 'messages'
109
+ > & { reasoning_effort?: string; reasoning?: OpenRouterReasoning };
110
+
111
+ const params = super.invocationParams(options, extra) as MutableParams;
112
+
113
+ // Remove the OpenAI-native reasoning_effort that the parent sets;
114
+ // OpenRouter uses a `reasoning` object instead
115
+ delete params.reasoning_effort;
116
+
117
+ // Build the OpenRouter reasoning config
118
+ const reasoning = this.buildOpenRouterReasoning(options);
119
+ if (reasoning != null) {
120
+ params.reasoning = reasoning;
121
+ } else {
122
+ delete params.reasoning;
123
+ }
124
+
125
+ return params;
126
+ }
127
+
128
+ private buildOpenRouterReasoning(
129
+ options?: this['ParsedCallOptions']
130
+ ): OpenRouterReasoning | undefined {
131
+ let reasoning: OpenRouterReasoning | undefined;
132
+
133
+ // 1. Instance-level reasoning config (from constructor)
134
+ if (this.openRouterReasoning != null) {
135
+ reasoning = { ...this.openRouterReasoning };
136
+ }
137
+
138
+ // 2. LangChain-style reasoning params (from parent's `this.reasoning`)
139
+ const lcReasoning = this.getReasoningParams(options);
140
+ if (lcReasoning?.effort != null) {
141
+ reasoning = {
142
+ ...reasoning,
143
+ effort: lcReasoning.effort as OpenRouterReasoningEffort,
144
+ };
145
+ }
146
+
147
+ // 3. Call-level reasoning override
148
+ const callReasoning = (options as ChatOpenRouterCallOptions | undefined)
149
+ ?.reasoning;
150
+ if (callReasoning != null) {
151
+ reasoning = { ...reasoning, ...callReasoning };
152
+ }
153
+
154
+ // 4. Legacy include_reasoning backward compatibility
155
+ if (reasoning == null && this.includeReasoning === true) {
156
+ reasoning = { enabled: true };
157
+ }
158
+
159
+ return reasoning;
160
+ }
50
161
  protected override _convertOpenAIDeltaToBaseMessageChunk(
51
162
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
163
  delta: Record<string, any>,
@@ -0,0 +1,207 @@
1
+ import { ChatOpenRouter } from './index';
2
+ import type { OpenRouterReasoning, ChatOpenRouterCallOptions } from './index';
3
+ import type { OpenAIChatInput } from '@langchain/openai';
4
+
5
+ type CreateRouterOptions = Partial<
6
+ ChatOpenRouterCallOptions & Pick<OpenAIChatInput, 'model' | 'apiKey'>
7
+ >;
8
+
9
+ function createRouter(overrides: CreateRouterOptions = {}): ChatOpenRouter {
10
+ return new ChatOpenRouter({
11
+ model: 'openrouter/test-model',
12
+ apiKey: 'test-key',
13
+ ...overrides,
14
+ });
15
+ }
16
+
17
+ describe('ChatOpenRouter reasoning handling', () => {
18
+ // ---------------------------------------------------------------
19
+ // 1. Constructor reasoning config
20
+ // ---------------------------------------------------------------
21
+ describe('constructor reasoning config', () => {
22
+ it('stores reasoning when passed directly', () => {
23
+ const router = createRouter({ reasoning: { effort: 'high' } });
24
+ const params = router.invocationParams();
25
+ expect(params.reasoning).toEqual({ effort: 'high' });
26
+ });
27
+ });
28
+
29
+ // ---------------------------------------------------------------
30
+ // 2. modelKwargs reasoning extraction
31
+ // ---------------------------------------------------------------
32
+ describe('modelKwargs reasoning extraction', () => {
33
+ it('extracts reasoning from modelKwargs and places it into params.reasoning', () => {
34
+ const router = createRouter({
35
+ modelKwargs: { reasoning: { effort: 'medium' } },
36
+ });
37
+ const params = router.invocationParams();
38
+ expect(params.reasoning).toEqual({ effort: 'medium' });
39
+ });
40
+
41
+ it('does not leak reasoning into modelKwargs that reach the parent', () => {
42
+ const router = createRouter({
43
+ modelKwargs: {
44
+ reasoning: { effort: 'medium' },
45
+ },
46
+ });
47
+ const params = router.invocationParams();
48
+ // reasoning should be the structured OpenRouter object, not buried in modelKwargs
49
+ expect(params.reasoning).toEqual({ effort: 'medium' });
50
+ });
51
+ });
52
+
53
+ // ---------------------------------------------------------------
54
+ // 3. Reasoning merge precedence
55
+ // ---------------------------------------------------------------
56
+ describe('reasoning merge precedence', () => {
57
+ it('constructor reasoning overrides modelKwargs.reasoning', () => {
58
+ const router = createRouter({
59
+ reasoning: { effort: 'high' },
60
+ modelKwargs: { reasoning: { effort: 'low' } },
61
+ });
62
+ const params = router.invocationParams();
63
+ expect(params.reasoning).toEqual({ effort: 'high' });
64
+ });
65
+
66
+ it('merges non-overlapping keys from modelKwargs.reasoning and constructor reasoning', () => {
67
+ const router = createRouter({
68
+ reasoning: { effort: 'high' },
69
+ modelKwargs: { reasoning: { max_tokens: 5000 } },
70
+ });
71
+ const params = router.invocationParams();
72
+ expect(params.reasoning).toEqual({ effort: 'high', max_tokens: 5000 });
73
+ });
74
+ });
75
+
76
+ // ---------------------------------------------------------------
77
+ // 4. invocationParams output
78
+ // ---------------------------------------------------------------
79
+ describe('invocationParams output', () => {
80
+ it('includes reasoning object in params', () => {
81
+ const router = createRouter({ reasoning: { effort: 'high' } });
82
+ const params = router.invocationParams();
83
+ expect(params.reasoning).toBeDefined();
84
+ expect(params.reasoning).toEqual({ effort: 'high' });
85
+ });
86
+
87
+ it('does NOT include reasoning_effort in params', () => {
88
+ const router = createRouter({ reasoning: { effort: 'high' } });
89
+ const params = router.invocationParams();
90
+ expect(params.reasoning_effort).toBeUndefined();
91
+ });
92
+
93
+ it('does not include reasoning when none is configured', () => {
94
+ const router = createRouter();
95
+ const params = router.invocationParams();
96
+ expect(params.reasoning).toBeUndefined();
97
+ expect(params.reasoning_effort).toBeUndefined();
98
+ });
99
+ });
100
+
101
+ // ---------------------------------------------------------------
102
+ // 5. Legacy include_reasoning
103
+ // ---------------------------------------------------------------
104
+ describe('legacy include_reasoning', () => {
105
+ it('produces { enabled: true } when only include_reasoning is true', () => {
106
+ const router = createRouter({ include_reasoning: true });
107
+ const params = router.invocationParams();
108
+ expect(params.reasoning).toEqual({ enabled: true });
109
+ });
110
+
111
+ it('does not produce reasoning when include_reasoning is false', () => {
112
+ const router = createRouter({ include_reasoning: false });
113
+ const params = router.invocationParams();
114
+ expect(params.reasoning).toBeUndefined();
115
+ });
116
+ });
117
+
118
+ // ---------------------------------------------------------------
119
+ // 6. Legacy include_reasoning ignored when reasoning is provided
120
+ // ---------------------------------------------------------------
121
+ describe('legacy include_reasoning ignored when reasoning provided', () => {
122
+ it('reasoning wins over include_reasoning', () => {
123
+ const router = createRouter({
124
+ reasoning: { effort: 'medium' },
125
+ include_reasoning: true,
126
+ });
127
+ const params = router.invocationParams();
128
+ // Should use the structured reasoning, NOT fall back to { enabled: true }
129
+ expect(params.reasoning).toEqual({ effort: 'medium' });
130
+ });
131
+
132
+ it('reasoning from modelKwargs also wins over include_reasoning', () => {
133
+ const router = createRouter({
134
+ modelKwargs: { reasoning: { effort: 'low' } },
135
+ include_reasoning: true,
136
+ });
137
+ const params = router.invocationParams();
138
+ expect(params.reasoning).toEqual({ effort: 'low' });
139
+ });
140
+ });
141
+
142
+ // ---------------------------------------------------------------
143
+ // 7. Various effort levels (OpenRouter-specific)
144
+ // ---------------------------------------------------------------
145
+ describe('various effort levels', () => {
146
+ const efforts: Array<{
147
+ effort: OpenRouterReasoning['effort'];
148
+ }> = [
149
+ { effort: 'xhigh' },
150
+ { effort: 'none' },
151
+ { effort: 'minimal' },
152
+ { effort: 'high' },
153
+ { effort: 'medium' },
154
+ { effort: 'low' },
155
+ ];
156
+
157
+ it.each(efforts)('supports effort level "$effort"', ({ effort }) => {
158
+ const router = createRouter({ reasoning: { effort } });
159
+ const params = router.invocationParams();
160
+ expect(params.reasoning).toEqual({ effort });
161
+ expect(params.reasoning_effort).toBeUndefined();
162
+ });
163
+ });
164
+
165
+ // ---------------------------------------------------------------
166
+ // 8. max_tokens reasoning
167
+ // ---------------------------------------------------------------
168
+ describe('max_tokens reasoning', () => {
169
+ it('passes max_tokens in reasoning object', () => {
170
+ const router = createRouter({
171
+ reasoning: { max_tokens: 8000 },
172
+ });
173
+ const params = router.invocationParams();
174
+ expect(params.reasoning).toEqual({ max_tokens: 8000 });
175
+ });
176
+
177
+ it('combines max_tokens with effort', () => {
178
+ const router = createRouter({
179
+ reasoning: { effort: 'high', max_tokens: 8000 },
180
+ });
181
+ const params = router.invocationParams();
182
+ expect(params.reasoning).toEqual({ effort: 'high', max_tokens: 8000 });
183
+ expect(params.reasoning_effort).toBeUndefined();
184
+ });
185
+ });
186
+
187
+ // ---------------------------------------------------------------
188
+ // 9. exclude reasoning
189
+ // ---------------------------------------------------------------
190
+ describe('exclude reasoning', () => {
191
+ it('passes exclude flag in reasoning object', () => {
192
+ const router = createRouter({
193
+ reasoning: { effort: 'high', exclude: true },
194
+ });
195
+ const params = router.invocationParams();
196
+ expect(params.reasoning).toEqual({ effort: 'high', exclude: true });
197
+ });
198
+
199
+ it('supports exclude without effort', () => {
200
+ const router = createRouter({
201
+ reasoning: { exclude: true },
202
+ });
203
+ const params = router.invocationParams();
204
+ expect(params.reasoning).toEqual({ exclude: true });
205
+ });
206
+ });
207
+ });
@@ -6,9 +6,11 @@ import type {
6
6
  GoogleAbstractedClient,
7
7
  } from '@langchain/google-common';
8
8
  import type { BaseMessage } from '@langchain/core/messages';
9
- import type { VertexAIClientOptions } from '@/types';
9
+ import type { GoogleThinkingConfig, VertexAIClientOptions } from '@/types';
10
10
 
11
11
  class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
12
+ thinkingConfig?: GoogleThinkingConfig;
13
+
12
14
  async formatData(
13
15
  input: BaseMessage[],
14
16
  parameters: GoogleAIModelRequestParams
@@ -26,6 +28,15 @@ class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
26
28
  }
27
29
  delete formattedData.generationConfig.thinkingConfig.thinkingBudget;
28
30
  }
31
+ if (this.thinkingConfig?.thinkingLevel) {
32
+ formattedData.generationConfig ??= {};
33
+ (
34
+ formattedData.generationConfig as Record<string, unknown>
35
+ ).thinkingConfig = {
36
+ ...formattedData.generationConfig.thinkingConfig,
37
+ thinkingLevel: this.thinkingConfig.thinkingLevel,
38
+ };
39
+ }
29
40
  return formattedData;
30
41
  }
31
42
  }
@@ -315,6 +326,7 @@ class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
315
326
  export class ChatVertexAI extends ChatGoogle {
316
327
  lc_namespace = ['langchain', 'chat_models', 'vertexai'];
317
328
  dynamicThinkingBudget = false;
329
+ thinkingConfig?: GoogleThinkingConfig;
318
330
 
319
331
  static lc_name(): 'LibreChatVertexAI' {
320
332
  return 'LibreChatVertexAI';
@@ -327,6 +339,7 @@ export class ChatVertexAI extends ChatGoogle {
327
339
  platformType: 'gcp',
328
340
  });
329
341
  this.dynamicThinkingBudget = dynamicThinkingBudget;
342
+ this.thinkingConfig = fields?.thinkingConfig;
330
343
  }
331
344
  invocationParams(
332
345
  options?: this['ParsedCallOptions'] | undefined
@@ -342,18 +355,22 @@ export class ChatVertexAI extends ChatGoogle {
342
355
  fields: VertexAIClientOptions,
343
356
  client: GoogleAbstractedClient
344
357
  ): void {
345
- this.connection = new CustomChatConnection(
358
+ const connection = new CustomChatConnection(
346
359
  { ...fields, ...this },
347
360
  this.caller,
348
361
  client,
349
362
  false
350
363
  );
364
+ connection.thinkingConfig = this.thinkingConfig;
365
+ this.connection = connection;
351
366
 
352
- this.streamedConnection = new CustomChatConnection(
367
+ const streamedConnection = new CustomChatConnection(
353
368
  { ...fields, ...this },
354
369
  this.caller,
355
370
  client,
356
371
  true
357
372
  );
373
+ streamedConnection.thinkingConfig = this.thinkingConfig;
374
+ this.streamedConnection = streamedConnection;
358
375
  }
359
376
  }
package/src/run.ts CHANGED
@@ -45,6 +45,7 @@ export class Run<_T extends t.BaseGraphState> {
45
45
  graphRunnable?: t.CompiledStateWorkflow;
46
46
  Graph: StandardGraph | MultiAgentGraph | undefined;
47
47
  returnContent: boolean = false;
48
+ private skipCleanup: boolean = false;
48
49
 
49
50
  private constructor(config: Partial<t.RunConfig>) {
50
51
  const runId = config.runId ?? '';
@@ -89,6 +90,7 @@ export class Run<_T extends t.BaseGraphState> {
89
90
  }
90
91
 
91
92
  this.returnContent = config.returnContent ?? false;
93
+ this.skipCleanup = config.skipCleanup ?? false;
92
94
  }
93
95
 
94
96
  private createLegacyGraph(
@@ -303,9 +305,45 @@ export class Run<_T extends t.BaseGraphState> {
303
305
  }
304
306
  }
305
307
 
306
- if (this.returnContent) {
307
- return this.Graph.getContentParts();
308
+ /**
309
+ * Break the reference chain that keeps heavy data alive via
310
+ * LangGraph's internal `__pregel_scratchpad.currentTaskInput` →
311
+ * `@langchain/core` `RunTree.extra[lc:child_config]` →
312
+ * Node.js `AsyncLocalStorage` context captured by timers/promises.
313
+ *
314
+ * Without this, base64-encoded images/PDFs in message content remain
315
+ * reachable from lingering `Timeout` handles until GC runs.
316
+ */
317
+ if (!this.skipCleanup) {
318
+ if (
319
+ (config.configurable as Record<string, unknown> | undefined) != null
320
+ ) {
321
+ for (const key of Object.getOwnPropertySymbols(config.configurable)) {
322
+ const val = config.configurable[key as unknown as string] as
323
+ | Record<string, unknown>
324
+ | undefined;
325
+ if (
326
+ val != null &&
327
+ typeof val === 'object' &&
328
+ 'currentTaskInput' in val
329
+ ) {
330
+ (val as Record<string, unknown>).currentTaskInput = undefined;
331
+ }
332
+ delete config.configurable[key as unknown as string];
333
+ }
334
+ config.configurable = undefined;
335
+ }
336
+ config.callbacks = undefined;
337
+ }
338
+
339
+ const result = this.returnContent
340
+ ? this.Graph.getContentParts()
341
+ : undefined;
342
+
343
+ if (!this.skipCleanup) {
344
+ this.Graph.clearHeavyState();
308
345
  }
346
+ return result;
309
347
  }
310
348
 
311
349
  private createSystemCallback<K extends keyof t.ClientCallbacks>(
@@ -98,6 +98,7 @@ async function testStandardStreaming(): Promise<void> {
98
98
  // additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
99
99
  },
100
100
  returnContent: true,
101
+ skipCleanup: true,
101
102
  customHandlers,
102
103
  });
103
104
 
@@ -100,6 +100,7 @@ async function testStandardStreaming(): Promise<void> {
100
100
  // additional_instructions: `Always address the user by their name. The user's name is ${userName} and they are located in ${location}.`,
101
101
  },
102
102
  returnContent: true,
103
+ skipCleanup: true,
103
104
  customHandlers,
104
105
  });
105
106
 
@@ -97,6 +97,7 @@ async function testStandardStreaming(): Promise<void> {
97
97
  instructions: 'You are a helpful AI research assistant.',
98
98
  },
99
99
  returnContent: true,
100
+ skipCleanup: true,
100
101
  customHandlers,
101
102
  });
102
103
 
@@ -136,6 +136,7 @@ async function testBedrockContentAggregation(): Promise<void> {
136
136
  llmConfig,
137
137
  },
138
138
  returnContent: true,
139
+ skipCleanup: true,
139
140
  customHandlers: customHandlers as t.RunConfig['customHandlers'],
140
141
  });
141
142
 
@@ -123,6 +123,7 @@ async function testParallelToolCalls(): Promise<void> {
123
123
  llmConfig,
124
124
  },
125
125
  returnContent: true,
126
+ skipCleanup: true,
126
127
  customHandlers: customHandlers as t.RunConfig['customHandlers'],
127
128
  });
128
129
 
@@ -74,6 +74,7 @@ ${CACHED_TEXT}`;
74
74
  llmConfig,
75
75
  },
76
76
  returnContent: true,
77
+ skipCleanup: true,
77
78
  customHandlers,
78
79
  });
79
80
 
@@ -93,6 +93,7 @@ async function testCodeExecution(): Promise<void> {
93
93
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
94
94
  },
95
95
  returnContent: true,
96
+ skipCleanup: true,
96
97
  customHandlers,
97
98
  });
98
99
 
@@ -127,6 +127,7 @@ async function testCodeExecution(): Promise<void> {
127
127
  additional_instructions: `The user's name is ${userName} and they are located in ${location}. The current date is ${currentDate}.`,
128
128
  },
129
129
  returnContent: true,
130
+ skipCleanup: true,
130
131
  customHandlers,
131
132
  });
132
133
 
@@ -73,6 +73,7 @@ When reading files, print their contents.
73
73
  Be concise in responses.`,
74
74
  },
75
75
  returnContent: true,
76
+ skipCleanup: true,
76
77
  customHandlers,
77
78
  });
78
79
 
@@ -217,6 +217,7 @@ async function testProgrammaticToolCalling(): Promise<void> {
217
217
  ],
218
218
  },
219
219
  returnContent: true,
220
+ skipCleanup: true,
220
221
  customHandlers,
221
222
  });
222
223
 
@@ -141,6 +141,7 @@ When writing Python code:
141
141
  additional_instructions: `User: ${userName}, Location: ${location}, Date: ${currentDate}.`,
142
142
  },
143
143
  returnContent: true,
144
+ skipCleanup: true,
144
145
  customHandlers,
145
146
  });
146
147
 
@@ -101,6 +101,7 @@ async function testCodeExecution(): Promise<void> {
101
101
  maxContextTokens: 8000,
102
102
  },
103
103
  returnContent: true,
104
+ skipCleanup: true,
104
105
  customHandlers,
105
106
  indexTokenCountMap,
106
107
  tokenCounter,
@@ -88,6 +88,7 @@ async function testStandardStreaming(): Promise<void> {
88
88
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
89
89
  },
90
90
  returnContent: true,
91
+ skipCleanup: true,
91
92
  customHandlers,
92
93
  });
93
94
 
@@ -94,6 +94,7 @@ async function testCodeExecution(): Promise<void> {
94
94
  additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
95
95
  },
96
96
  returnContent: true,
97
+ skipCleanup: true,
97
98
  customHandlers,
98
99
  });
99
100
 
@@ -17,19 +17,27 @@ const memoryKv: Record<string, string> = {};
17
17
  const setMemory = tool(
18
18
  async ({ key, value }) => {
19
19
  if (!/^[a-z_]+$/.test(key)) {
20
- throw new Error('Key must only contain lowercase letters and underscores');
20
+ throw new Error(
21
+ 'Key must only contain lowercase letters and underscores'
22
+ );
21
23
  }
22
-
24
+
23
25
  memoryKv[key] = value;
24
-
26
+
25
27
  return { ok: true };
26
28
  },
27
29
  {
28
30
  name: 'set_memory',
29
31
  description: 'Saves important data about the user into memory.',
30
32
  schema: z.object({
31
- key: z.string().describe('The key of the memory value. Always use lowercase and underscores, no other characters.'),
32
- value: z.string().describe('Value can be anything represented as a string')
33
+ key: z
34
+ .string()
35
+ .describe(
36
+ 'The key of the memory value. Always use lowercase and underscores, no other characters.'
37
+ ),
38
+ value: z
39
+ .string()
40
+ .describe('Value can be anything represented as a string'),
33
41
  }),
34
42
  }
35
43
  );
@@ -48,10 +56,12 @@ async function testStandardStreaming(): Promise<void> {
48
56
  streaming: false,
49
57
  },
50
58
  tools: [setMemory],
51
- instructions: 'You can use the `set_memory` tool to save important data about the user into memory. If there is nothing to note about the user specifically, respond with `nothing`.',
59
+ instructions:
60
+ 'You can use the `set_memory` tool to save important data about the user into memory. If there is nothing to note about the user specifically, respond with `nothing`.',
52
61
  toolEnd: true,
53
62
  },
54
63
  returnContent: true,
64
+ skipCleanup: true,
55
65
  });
56
66
 
57
67
  const config = {