@librechat/agents 3.0.75 → 3.0.76

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.
@@ -1,4 +1,11 @@
1
- // src/scripts/cli.ts
1
+ // src/scripts/code_exec_files.ts
2
+ /**
3
+ * Tests automatic session tracking for code execution file persistence.
4
+ * Files created in one execution are automatically available in subsequent executions
5
+ * without the LLM needing to track or pass session_id.
6
+ *
7
+ * Run with: npm run code_exec_files
8
+ */
2
9
  import { config } from 'dotenv';
3
10
  config();
4
11
  import { HumanMessage, BaseMessage } from '@langchain/core/messages';
@@ -12,12 +19,39 @@ import {
12
19
  } from '@/events';
13
20
  import { getLLMConfig } from '@/utils/llmConfig';
14
21
  import { getArgs } from '@/scripts/args';
15
- import { GraphEvents } from '@/common';
22
+ import { Constants, GraphEvents } from '@/common';
16
23
  import { Run } from '@/run';
17
24
  import { createCodeExecutionTool } from '@/tools/CodeExecutor';
18
25
 
19
26
  const conversationHistory: BaseMessage[] = [];
20
27
 
28
+ /**
29
+ * Prints session context from the graph for debugging
30
+ */
31
+ function printSessionContext(run: Run<t.IState>): void {
32
+ const graph = run.Graph;
33
+ if (!graph) {
34
+ console.log('[Session] No graph available');
35
+ return;
36
+ }
37
+
38
+ const session = graph.sessions.get(Constants.EXECUTE_CODE) as
39
+ | t.CodeSessionContext
40
+ | undefined;
41
+
42
+ if (!session) {
43
+ console.log('[Session] No session context stored yet');
44
+ return;
45
+ }
46
+
47
+ console.log('[Session] Current session context:');
48
+ console.log(` - session_id: ${session.session_id}`);
49
+ console.log(` - files: ${JSON.stringify(session.files, null, 2)}`);
50
+ console.log(
51
+ ` - lastUpdated: ${new Date(session.lastUpdated).toISOString()}`
52
+ );
53
+ }
54
+
21
55
  async function testCodeExecution(): Promise<void> {
22
56
  const { userName, location, provider, currentDate } = await getArgs();
23
57
  const { contentParts, aggregateContent } = createContentAggregator();
@@ -72,7 +106,7 @@ async function testCodeExecution(): Promise<void> {
72
106
  handle: (
73
107
  _event: string,
74
108
  data: t.StreamEventData,
75
- metadata?: Record<string, unknown>
109
+ _metadata?: Record<string, unknown>
76
110
  ): void => {
77
111
  console.log('====== TOOL_START ======');
78
112
  console.dir(data, { depth: null });
@@ -96,7 +130,7 @@ async function testCodeExecution(): Promise<void> {
96
130
  customHandlers,
97
131
  });
98
132
 
99
- const config: Partial<RunnableConfig> & {
133
+ const streamConfig: Partial<RunnableConfig> & {
100
134
  version: 'v1' | 'v2';
101
135
  run_id?: string;
102
136
  streamMode: string;
@@ -107,10 +141,12 @@ async function testCodeExecution(): Promise<void> {
107
141
  },
108
142
  streamMode: 'values',
109
143
  version: 'v2' as const,
110
- // recursionLimit: 3,
111
144
  };
112
145
 
113
- console.log('Test 1: Create Project Plan');
146
+ console.log('\n========== Test 1: Create Project Plan ==========\n');
147
+ console.log(
148
+ 'Creating initial file - this establishes the session context.\n'
149
+ );
114
150
 
115
151
  const userMessage1 = `
116
152
  Hi ${userName} here. We are testing your file capabilities.
@@ -125,36 +161,43 @@ async function testCodeExecution(): Promise<void> {
125
161
  let inputs = {
126
162
  messages: conversationHistory,
127
163
  };
128
- const finalContentParts1 = await run.processStream(inputs, config);
164
+ await run.processStream(inputs, streamConfig);
129
165
  const finalMessages1 = run.getRunMessages();
130
166
  if (finalMessages1) {
131
167
  conversationHistory.push(...finalMessages1);
132
168
  }
133
- console.log('\n\n====================\n\n');
169
+
170
+ console.log('\n\n========== Session Context After Test 1 ==========\n');
171
+ printSessionContext(run);
134
172
  console.dir(contentParts, { depth: null });
135
173
 
136
- console.log('Test 2: Edit Project Plan');
174
+ console.log('\n========== Test 2: Edit Project Plan ==========\n');
175
+ console.log(
176
+ 'Editing the file from Test 1 - session_id is automatically injected.\n'
177
+ );
137
178
 
138
179
  const userMessage2 = `
139
180
  Thanks for creating the project plan. Now I'd like you to edit the same plan to:
140
181
 
141
- 1. Add a new section called "Technology Stack" that contains: "The technology stack for this project includes the following technologies" and nothing more.
142
-
182
+ 1. Read the existing project_plan.txt file
183
+ 2. Add a new section called "Technology Stack" that contains: "The technology stack for this project includes the following technologies" and nothing more.
184
+ 3. Save this as a new file called "project_plan_v2.txt" (remember files are read-only)
185
+ 4. Print the contents of both files to verify
143
186
  `;
144
187
 
145
- // Make sure to pass the file ID of the previous file you created and explicitly duplicate or rename the file in your code so we can then access it. Also print the contents of the new file to ensure we did what we wanted.`;
146
-
147
188
  conversationHistory.push(new HumanMessage(userMessage2));
148
189
 
149
190
  inputs = {
150
191
  messages: conversationHistory,
151
192
  };
152
- const finalContentParts2 = await run.processStream(inputs, config);
193
+ await run.processStream(inputs, streamConfig);
153
194
  const finalMessages2 = run.getRunMessages();
154
195
  if (finalMessages2) {
155
196
  conversationHistory.push(...finalMessages2);
156
197
  }
157
- console.log('\n\n====================\n\n');
198
+
199
+ console.log('\n\n========== Session Context After Test 2 ==========\n');
200
+ printSessionContext(run);
158
201
  console.dir(contentParts, { depth: null });
159
202
 
160
203
  const { handleLLMEnd, collected } = createMetadataAggregator();
@@ -0,0 +1,282 @@
1
+ // src/scripts/code_exec_session.ts
2
+ /**
3
+ * Test script for automatic session tracking in code execution tools.
4
+ *
5
+ * This tests the automatic session_id injection feature where:
6
+ * 1. First code execution generates files and returns a session_id
7
+ * 2. Session context is stored in Graph.sessions
8
+ * 3. Subsequent code executions automatically have access to previous files
9
+ * without the LLM needing to explicitly pass session_id
10
+ *
11
+ * Run with: npm run code_exec_session
12
+ */
13
+ import { config } from 'dotenv';
14
+ config();
15
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
16
+ import type { RunnableConfig } from '@langchain/core/runnables';
17
+ import type * as t from '@/types';
18
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
19
+ import {
20
+ ToolEndHandler,
21
+ ModelEndHandler,
22
+ createMetadataAggregator,
23
+ } from '@/events';
24
+ import { getLLMConfig } from '@/utils/llmConfig';
25
+ import { getArgs } from '@/scripts/args';
26
+ import { Constants, GraphEvents } from '@/common';
27
+ import { Run } from '@/run';
28
+ import { createCodeExecutionTool } from '@/tools/CodeExecutor';
29
+
30
+ const conversationHistory: BaseMessage[] = [];
31
+
32
+ /**
33
+ * Prints a formatted section header for test output
34
+ */
35
+ function printSection(title: string): void {
36
+ console.log('\n' + '='.repeat(60));
37
+ console.log(` ${title}`);
38
+ console.log('='.repeat(60) + '\n');
39
+ }
40
+
41
+ /**
42
+ * Prints session context from the graph for debugging
43
+ */
44
+ function printSessionContext(run: Run<t.IState>): void {
45
+ const graph = run.Graph;
46
+ if (!graph) {
47
+ console.log('[Session] No graph available');
48
+ return;
49
+ }
50
+
51
+ const session = graph.sessions.get(Constants.EXECUTE_CODE) as
52
+ | t.CodeSessionContext
53
+ | undefined;
54
+
55
+ if (!session) {
56
+ console.log('[Session] No session context stored yet');
57
+ return;
58
+ }
59
+
60
+ console.log('[Session] Current session context:');
61
+ console.log(` - session_id: ${session.session_id}`);
62
+ console.log(` - files: ${JSON.stringify(session.files, null, 2)}`);
63
+ console.log(
64
+ ` - lastUpdated: ${new Date(session.lastUpdated).toISOString()}`
65
+ );
66
+ }
67
+
68
+ async function testAutomaticSessionTracking(): Promise<void> {
69
+ const { userName, location, provider, currentDate } = await getArgs();
70
+ const { contentParts, aggregateContent } = createContentAggregator();
71
+
72
+ const customHandlers = {
73
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
74
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
75
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
76
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
77
+ handle: (
78
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
79
+ data: t.StreamEventData
80
+ ): void => {
81
+ console.log('====== ON_RUN_STEP_COMPLETED ======');
82
+ console.dir(data, { depth: null });
83
+ aggregateContent({
84
+ event,
85
+ data: data as unknown as { result: t.ToolEndEvent },
86
+ });
87
+ },
88
+ },
89
+ [GraphEvents.ON_RUN_STEP]: {
90
+ handle: (
91
+ event: GraphEvents.ON_RUN_STEP,
92
+ data: t.StreamEventData
93
+ ): void => {
94
+ console.log('====== ON_RUN_STEP ======');
95
+ console.dir(data, { depth: null });
96
+ aggregateContent({ event, data: data as t.RunStep });
97
+ },
98
+ },
99
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
100
+ handle: (
101
+ event: GraphEvents.ON_RUN_STEP_DELTA,
102
+ data: t.StreamEventData
103
+ ): void => {
104
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
105
+ },
106
+ },
107
+ [GraphEvents.ON_MESSAGE_DELTA]: {
108
+ handle: (
109
+ event: GraphEvents.ON_MESSAGE_DELTA,
110
+ data: t.StreamEventData
111
+ ): void => {
112
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
113
+ },
114
+ },
115
+ [GraphEvents.TOOL_START]: {
116
+ handle: (
117
+ _event: string,
118
+ data: t.StreamEventData,
119
+ _metadata?: Record<string, unknown>
120
+ ): void => {
121
+ console.log('====== TOOL_START ======');
122
+ console.dir(data, { depth: null });
123
+ },
124
+ },
125
+ };
126
+
127
+ const llmConfig = getLLMConfig(provider);
128
+
129
+ const run = await Run.create<t.IState>({
130
+ runId: 'session-tracking-test-1',
131
+ graphConfig: {
132
+ type: 'standard',
133
+ llmConfig,
134
+ tools: [createCodeExecutionTool()],
135
+ instructions: `You are an AI assistant testing automatic file persistence.
136
+ When writing Python code:
137
+ - Use print() for all outputs
138
+ - Files from previous executions are automatically available in /mnt/data/
139
+ - Files are READ-ONLY; write modifications to NEW filenames
140
+ - IMPORTANT: Do NOT include session_id in your tool calls - it's handled automatically.`,
141
+ additional_instructions: `User: ${userName}, Location: ${location}, Date: ${currentDate}.`,
142
+ },
143
+ returnContent: true,
144
+ customHandlers,
145
+ });
146
+
147
+ const streamConfig: Partial<RunnableConfig> & {
148
+ version: 'v1' | 'v2';
149
+ run_id?: string;
150
+ streamMode: string;
151
+ } = {
152
+ configurable: {
153
+ provider,
154
+ thread_id: 'session-tracking-test',
155
+ },
156
+ streamMode: 'values',
157
+ version: 'v2' as const,
158
+ };
159
+
160
+ // =========================================================================
161
+ // Test 1: Create initial file (establishes session)
162
+ // =========================================================================
163
+ printSection('Test 1: Create Initial File');
164
+ console.log(
165
+ 'This test creates a file, which should establish a session context.\n'
166
+ );
167
+
168
+ const userMessage1 = `
169
+ Create a Python file that writes a simple JSON config file named "app_config.json" with the following content:
170
+ {
171
+ "app_name": "TestApp",
172
+ "version": "1.0.0",
173
+ "debug": true
174
+ }
175
+
176
+ After writing, print the contents to confirm it was created correctly.
177
+ `;
178
+
179
+ conversationHistory.push(new HumanMessage(userMessage1));
180
+ await run.processStream({ messages: conversationHistory }, streamConfig);
181
+
182
+ const finalMessages1 = run.getRunMessages();
183
+ if (finalMessages1) {
184
+ conversationHistory.push(...finalMessages1);
185
+ }
186
+
187
+ printSection('Session Context After Test 1');
188
+ printSessionContext(run);
189
+
190
+ // =========================================================================
191
+ // Test 2: Access previously created file (uses automatic session injection)
192
+ // =========================================================================
193
+ printSection('Test 2: Access Previous File (Automatic Session)');
194
+ console.log('This test reads the file created in Test 1.');
195
+ console.log(
196
+ 'The LLM does NOT need to provide session_id - it should be injected automatically.\n'
197
+ );
198
+
199
+ const userMessage2 = `
200
+ Now read the app_config.json file that was just created and:
201
+ 1. Print its contents
202
+ 2. Confirm the version is "1.0.0"
203
+
204
+ Note: You should be able to access this file from the previous execution automatically.
205
+ `;
206
+
207
+ conversationHistory.push(new HumanMessage(userMessage2));
208
+ await run.processStream({ messages: conversationHistory }, streamConfig);
209
+
210
+ const finalMessages2 = run.getRunMessages();
211
+ if (finalMessages2) {
212
+ conversationHistory.push(...finalMessages2);
213
+ }
214
+
215
+ printSection('Session Context After Test 2');
216
+ printSessionContext(run);
217
+
218
+ // =========================================================================
219
+ // Test 3: Modify file (write to new filename)
220
+ // =========================================================================
221
+ printSection('Test 3: Modify File (Write to New Filename)');
222
+ console.log(
223
+ 'This test modifies the config by reading the old file and writing a new one.\n'
224
+ );
225
+
226
+ const userMessage3 = `
227
+ Read app_config.json, update the version to "2.0.0" and debug to false,
228
+ then save it as "app_config_v2.json". Print both the old and new contents.
229
+ `;
230
+
231
+ conversationHistory.push(new HumanMessage(userMessage3));
232
+ await run.processStream({ messages: conversationHistory }, streamConfig);
233
+
234
+ const finalMessages3 = run.getRunMessages();
235
+ if (finalMessages3) {
236
+ conversationHistory.push(...finalMessages3);
237
+ }
238
+
239
+ printSection('Session Context After Test 3');
240
+ printSessionContext(run);
241
+
242
+ // =========================================================================
243
+ // Summary
244
+ // =========================================================================
245
+ printSection('Test Summary');
246
+ console.log('The automatic session tracking feature should have:');
247
+ console.log('1. Stored the session_id after the first code execution');
248
+ console.log('2. Automatically injected it into subsequent executions');
249
+ console.log('3. Accumulated file references across all executions');
250
+ console.log('\nCheck the session context output above to verify.\n');
251
+
252
+ // Generate title
253
+ const { handleLLMEnd, collected } = createMetadataAggregator();
254
+ const titleResult = await run.generateTitle({
255
+ provider,
256
+ inputText: 'Testing automatic session tracking for code execution',
257
+ contentParts,
258
+ chainOptions: {
259
+ callbacks: [{ handleLLMEnd }],
260
+ },
261
+ });
262
+ console.log('Generated Title:', titleResult);
263
+ console.log('Collected metadata:', collected);
264
+ }
265
+
266
+ process.on('unhandledRejection', (reason, promise) => {
267
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
268
+ console.log('Conversation history:');
269
+ console.dir(conversationHistory, { depth: null });
270
+ process.exit(1);
271
+ });
272
+
273
+ process.on('uncaughtException', (err) => {
274
+ console.error('Uncaught Exception:', err);
275
+ });
276
+
277
+ testAutomaticSessionTracking().catch((err) => {
278
+ console.error(err);
279
+ console.log('Conversation history:');
280
+ console.dir(conversationHistory, { depth: null });
281
+ process.exit(1);
282
+ });