@librechat/agents 2.3.99 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -243,29 +243,31 @@ export interface TMessage {
243
243
  [key: string]: unknown;
244
244
  }
245
245
  export type TPayload = Array<Partial<TMessage>>;
246
+ export type CustomChunkDelta = null | undefined | (Partial<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice.Delta> & {
247
+ reasoning?: string | null;
248
+ reasoning_content?: string | null;
249
+ });
250
+ export type CustomChunkChoice = Partial<Omit<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice, 'delta'> & {
251
+ delta?: CustomChunkDelta;
252
+ }>;
246
253
  export type CustomChunk = Partial<OpenAITypes.ChatCompletionChunk> & {
247
- choices?: Partial<Array<Partial<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice> & {
248
- delta?: Partial<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice.Delta> & {
249
- reasoning?: string | null;
250
- reasoning_content?: string | null;
251
- };
252
- }>>;
254
+ choices?: Partial<Array<CustomChunkChoice>>;
253
255
  };
254
256
  export type SplitStreamHandlers = Partial<{
255
- [GraphEvents.ON_RUN_STEP]: ({ event, data }: {
257
+ [GraphEvents.ON_RUN_STEP]: ({ event, data, }: {
256
258
  event: GraphEvents;
257
259
  data: RunStep;
258
260
  }) => void;
259
- [GraphEvents.ON_MESSAGE_DELTA]: ({ event, data }: {
261
+ [GraphEvents.ON_MESSAGE_DELTA]: ({ event, data, }: {
260
262
  event: GraphEvents;
261
263
  data: MessageDeltaEvent;
262
264
  }) => void;
263
- [GraphEvents.ON_REASONING_DELTA]: ({ event, data }: {
265
+ [GraphEvents.ON_REASONING_DELTA]: ({ event, data, }: {
264
266
  event: GraphEvents;
265
267
  data: ReasoningDeltaEvent;
266
268
  }) => void;
267
269
  }>;
268
- export type ContentAggregator = ({ event, data }: {
270
+ export type ContentAggregator = ({ event, data, }: {
269
271
  event: GraphEvents;
270
272
  data: RunStep | MessageDeltaEvent | RunStepDeltaEvent | {
271
273
  result: ToolEndEvent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "2.3.99",
3
+ "version": "2.4.01",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -2,7 +2,19 @@ import { nanoid } from 'nanoid';
2
2
  import type * as t from '@/types';
3
3
  import { ContentTypes, GraphEvents, StepTypes } from '@/common';
4
4
 
5
- export const SEPARATORS = ['.', '?', '!', '۔', '。', '‥', ';', '¡', '¿', '\n', '```'];
5
+ export const SEPARATORS = [
6
+ '.',
7
+ '?',
8
+ '!',
9
+ '۔',
10
+ '。',
11
+ '‥',
12
+ ';',
13
+ '¡',
14
+ '¿',
15
+ '\n',
16
+ '```',
17
+ ];
6
18
 
7
19
  export class SplitStreamHandler {
8
20
  private inCodeBlock = false;
@@ -28,12 +40,12 @@ export class SplitStreamHandler {
28
40
  reasoningKey,
29
41
  blockThreshold,
30
42
  }: {
31
- runId: string,
32
- accumulate?: boolean,
33
- handlers: t.SplitStreamHandlers
34
- blockThreshold?: number,
35
- reasoningKey?: 'reasoning_content' | 'reasoning',
36
- }) {
43
+ runId: string;
44
+ accumulate?: boolean;
45
+ handlers: t.SplitStreamHandlers;
46
+ blockThreshold?: number;
47
+ reasoningKey?: 'reasoning_content' | 'reasoning';
48
+ }) {
37
49
  this.runId = runId;
38
50
  this.handlers = handlers;
39
51
  if (reasoningKey) {
@@ -51,7 +63,9 @@ export class SplitStreamHandler {
51
63
  }
52
64
  return undefined;
53
65
  };
54
- createMessageStep = (type?: ContentTypes.TEXT | ContentTypes.THINK): [string, string] => {
66
+ createMessageStep = (
67
+ type?: ContentTypes.TEXT | ContentTypes.THINK
68
+ ): [string, string] => {
55
69
  if (type != null && this.currentType !== type) {
56
70
  this.currentType = type;
57
71
  }
@@ -70,23 +84,35 @@ export class SplitStreamHandler {
70
84
  stepDetails,
71
85
  // usage: null,
72
86
  };
73
- this.handlers?.[GraphEvents.ON_RUN_STEP]?.({ event: GraphEvents.ON_RUN_STEP, data: runStep });
87
+ this.handlers?.[GraphEvents.ON_RUN_STEP]?.({
88
+ event: GraphEvents.ON_RUN_STEP,
89
+ data: runStep,
90
+ });
74
91
  };
75
92
  dispatchMessageDelta = (stepId: string, delta: t.MessageDelta): void => {
76
93
  const messageDelta: t.MessageDeltaEvent = {
77
94
  id: stepId,
78
95
  delta,
79
96
  };
80
- this.handlers?.[GraphEvents.ON_MESSAGE_DELTA]?.({ event: GraphEvents.ON_MESSAGE_DELTA, data: messageDelta });
97
+ this.handlers?.[GraphEvents.ON_MESSAGE_DELTA]?.({
98
+ event: GraphEvents.ON_MESSAGE_DELTA,
99
+ data: messageDelta,
100
+ });
81
101
  };
82
102
  dispatchReasoningDelta = (stepId: string, delta: t.ReasoningDelta): void => {
83
103
  const reasoningDelta: t.ReasoningDeltaEvent = {
84
104
  id: stepId,
85
105
  delta,
86
106
  };
87
- this.handlers?.[GraphEvents.ON_REASONING_DELTA]?.({ event: GraphEvents.ON_REASONING_DELTA, data: reasoningDelta });
107
+ this.handlers?.[GraphEvents.ON_REASONING_DELTA]?.({
108
+ event: GraphEvents.ON_REASONING_DELTA,
109
+ data: reasoningDelta,
110
+ });
88
111
  };
89
- handleContent = (content: string, _type: ContentTypes.TEXT | ContentTypes.THINK): void => {
112
+ handleContent = (
113
+ content: string,
114
+ _type: ContentTypes.TEXT | ContentTypes.THINK
115
+ ): void => {
90
116
  let type = _type;
91
117
  if (this.inThinkBlock && type === ContentTypes.TEXT) {
92
118
  type = ContentTypes.THINK;
@@ -112,17 +138,21 @@ export class SplitStreamHandler {
112
138
  const stepId = this.currentStepId ?? '';
113
139
  if (type === ContentTypes.THINK) {
114
140
  this.dispatchReasoningDelta(stepId, {
115
- content: [{
116
- type: ContentTypes.THINK,
117
- think: content,
118
- }],
141
+ content: [
142
+ {
143
+ type: ContentTypes.THINK,
144
+ think: content,
145
+ },
146
+ ],
119
147
  });
120
148
  } else {
121
149
  this.dispatchMessageDelta(stepId, {
122
- content: [{
123
- type: ContentTypes.TEXT,
124
- text: content,
125
- }],
150
+ content: [
151
+ {
152
+ type: ContentTypes.TEXT,
153
+ text: content,
154
+ },
155
+ ],
126
156
  });
127
157
  }
128
158
 
@@ -131,7 +161,10 @@ export class SplitStreamHandler {
131
161
  return;
132
162
  }
133
163
 
134
- if (this.currentLength > this.blockThreshold && SEPARATORS.some(sep => content.includes(sep))) {
164
+ if (
165
+ this.currentLength > this.blockThreshold &&
166
+ SEPARATORS.some((sep) => content.includes(sep))
167
+ ) {
135
168
  const [newStepId, newMessageId] = this.createMessageStep(type);
136
169
  this.dispatchRunStep(newStepId, {
137
170
  type: StepTypes.MESSAGE_CREATION,
@@ -141,11 +174,14 @@ export class SplitStreamHandler {
141
174
  });
142
175
  }
143
176
  };
144
- getDeltaContent(chunk: t.CustomChunk): string {
145
- return chunk.choices?.[0]?.delta.content ?? '';
177
+ getDeltaContent(chunk?: t.CustomChunk): string {
178
+ return (chunk?.choices?.[0]?.delta as t.CustomChunkDelta)?.content ?? '';
146
179
  }
147
- getReasoningDelta(chunk: t.CustomChunk): string {
148
- return chunk.choices?.[0]?.delta[this.reasoningKey] ?? '';
180
+ getReasoningDelta(chunk?: t.CustomChunk): string {
181
+ return (
182
+ (chunk?.choices?.[0]?.delta as t.CustomChunkDelta)?.[this.reasoningKey] ??
183
+ ''
184
+ );
149
185
  }
150
186
  handle(chunk?: t.CustomChunk): void {
151
187
  if (!chunk) {
@@ -173,8 +209,12 @@ export class SplitStreamHandler {
173
209
  const message_id = this.getMessageId() ?? '';
174
210
 
175
211
  if (!message_id) {
176
- const initialContentType = this.inThinkBlock ? ContentTypes.THINK : ContentTypes.TEXT;
177
- const initialType = reasoning_content ? ContentTypes.THINK : initialContentType;
212
+ const initialContentType = this.inThinkBlock
213
+ ? ContentTypes.THINK
214
+ : ContentTypes.TEXT;
215
+ const initialType = reasoning_content
216
+ ? ContentTypes.THINK
217
+ : initialContentType;
178
218
  const [stepId, message_id] = this.createMessageStep(initialType);
179
219
  this.dispatchRunStep(stepId, {
180
220
  type: StepTypes.MESSAGE_CREATION,
@@ -190,4 +230,4 @@ export class SplitStreamHandler {
190
230
  this.handleContent(content, ContentTypes.TEXT);
191
231
  }
192
232
  }
193
- }
233
+ }
package/src/stream.ts CHANGED
@@ -484,17 +484,19 @@ export function createContentAggregator(): t.ContentAggregatorResult {
484
484
  'tool_call' in contentPart
485
485
  ) {
486
486
  const existingContent = contentParts[index] as
487
- | (Omit<t.ToolCallContent, 'tool_call'> & { tool_call?: ToolCall })
487
+ | (Omit<t.ToolCallContent, 'tool_call'> & {
488
+ tool_call?: t.ToolCallPart;
489
+ })
488
490
  | undefined;
489
491
 
490
- const toolCallArgs =
491
- (contentPart.tool_call.args as unknown as string | undefined) ?? '';
492
- let args = finalUpdate
493
- ? contentPart.tool_call.args
494
- : (existingContent?.tool_call?.args ?? '');
495
- if (!finalUpdate && typeof args === 'string' && args !== toolCallArgs) {
496
- args += toolCallArgs;
497
- }
492
+ const toolCallArgs = (contentPart.tool_call as t.ToolCallPart).args;
493
+ /** When args are a valid object, they are likely already invoked */
494
+ const args =
495
+ finalUpdate ||
496
+ typeof existingContent?.tool_call?.args === 'object' ||
497
+ typeof toolCallArgs === 'object'
498
+ ? contentPart.tool_call.args
499
+ : (existingContent?.tool_call?.args ?? '') + (toolCallArgs ?? '');
498
500
 
499
501
  const id =
500
502
  getNonEmptyValue([
@@ -10,26 +10,33 @@ import { EnvVar, Constants } from '@/common';
10
10
  config();
11
11
 
12
12
  export const imageExtRegex = /\.(jpg|jpeg|png|gif|webp)$/i;
13
- export const getCodeBaseURL = (): string => getEnvironmentVariable(EnvVar.CODE_BASEURL) ?? Constants.OFFICIAL_CODE_BASEURL;
13
+ export const getCodeBaseURL = (): string =>
14
+ getEnvironmentVariable(EnvVar.CODE_BASEURL) ??
15
+ Constants.OFFICIAL_CODE_BASEURL;
14
16
 
15
- const imageMessage = ' - the image is already displayed to the user';
16
- const otherMessage = ' - the file is already downloaded by the user';
17
+ const imageMessage = 'Image is already displayed to the user';
18
+ const otherMessage = 'File is already downloaded by the user';
19
+ const accessMessage =
20
+ 'IMPORTANT: Files accessed via session ID are READ-ONLY snapshots. Any modifications MUST be saved as NEW files with different names. The original files cannot be modified in-place. To access these files in future executions, you MUST provide this session_id as a parameter. Without the session_id, previously generated files will not be accessible in subsequent executions.';
21
+ const emptyOutputMessage =
22
+ 'stdout: Empty. Ensure you\'re writing output explicitly.\n';
17
23
 
18
24
  const CodeExecutionToolSchema = z.object({
19
- lang: z.enum([
20
- 'py',
21
- 'js',
22
- 'ts',
23
- 'c',
24
- 'cpp',
25
- 'java',
26
- 'php',
27
- 'rs',
28
- 'go',
29
- 'd',
30
- 'f90',
31
- 'r',
32
- ])
25
+ lang: z
26
+ .enum([
27
+ 'py',
28
+ 'js',
29
+ 'ts',
30
+ 'c',
31
+ 'cpp',
32
+ 'java',
33
+ 'php',
34
+ 'rs',
35
+ 'go',
36
+ 'd',
37
+ 'f90',
38
+ 'r',
39
+ ])
33
40
  .describe('The programming language or runtime to execute the code in.'),
34
41
  code: z.string()
35
42
  .describe(`The complete, self-contained code to execute, without any truncation or minimization.
@@ -42,14 +49,36 @@ const CodeExecutionToolSchema = z.object({
42
49
  - js: use the \`console\` or \`process\` methods for all outputs.
43
50
  - r: IMPORTANT: No X11 display available. ALL graphics MUST use Cairo library (library(Cairo)).
44
51
  - Other languages: use appropriate output functions.`),
45
- args: z.array(z.string()).optional()
46
- .describe('Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.'),
52
+ session_id: z
53
+ .string()
54
+ .optional()
55
+ .describe(
56
+ `Optional: Session ID from a previous execution to access generated files.
57
+ - Files load into /mnt/data/ (current working directory)
58
+ - Use relative paths ONLY
59
+ - Files are READ-ONLY - cannot be modified in-place
60
+ - To modify: read original file, write to NEW filename
61
+ `.trim()
62
+ ),
63
+ args: z
64
+ .array(z.string())
65
+ .optional()
66
+ .describe(
67
+ 'Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.'
68
+ ),
47
69
  });
48
70
 
49
- const EXEC_ENDPOINT = `${getCodeBaseURL()}/exec`;
50
-
51
- function createCodeExecutionTool(params: t.CodeExecutionToolParams = {}): DynamicStructuredTool<typeof CodeExecutionToolSchema> {
52
- const apiKey = params[EnvVar.CODE_API_KEY] ?? params.apiKey ?? getEnvironmentVariable(EnvVar.CODE_API_KEY) ?? '';
71
+ const baseEndpoint = getCodeBaseURL();
72
+ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
73
+
74
+ function createCodeExecutionTool(
75
+ params: t.CodeExecutionToolParams = {}
76
+ ): DynamicStructuredTool<typeof CodeExecutionToolSchema> {
77
+ const apiKey =
78
+ params[EnvVar.CODE_API_KEY] ??
79
+ params.apiKey ??
80
+ getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
81
+ '';
53
82
  if (!apiKey) {
54
83
  throw new Error('No API key provided for code execution tool.');
55
84
  }
@@ -64,7 +93,7 @@ Usage:
64
93
  `.trim();
65
94
 
66
95
  return tool<typeof CodeExecutionToolSchema>(
67
- async ({ lang, code, ...rest }) => {
96
+ async ({ lang, code, session_id, ...rest }) => {
68
97
  const postData = {
69
98
  lang,
70
99
  code,
@@ -72,6 +101,54 @@ Usage:
72
101
  ...params,
73
102
  };
74
103
 
104
+ if (session_id != null && session_id.length > 0) {
105
+ try {
106
+ const filesEndpoint = `${baseEndpoint}/files/${session_id}?detail=full`;
107
+ const fetchOptions: RequestInit = {
108
+ method: 'GET',
109
+ headers: {
110
+ 'User-Agent': 'LibreChat/1.0',
111
+ 'X-API-Key': apiKey,
112
+ },
113
+ };
114
+
115
+ if (process.env.PROXY != null && process.env.PROXY !== '') {
116
+ fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
117
+ }
118
+
119
+ const response = await fetch(filesEndpoint, fetchOptions);
120
+ if (!response.ok) {
121
+ throw new Error(
122
+ `Failed to fetch files for session: ${response.status}`
123
+ );
124
+ }
125
+
126
+ const files = await response.json();
127
+ if (Array.isArray(files) && files.length > 0) {
128
+ const fileReferences: t.CodeEnvFile[] = files.map((file) => {
129
+ // Extract the ID from the file name (part after session ID prefix and before extension)
130
+ const nameParts = file.name.split('/');
131
+ const id = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';
132
+
133
+ return {
134
+ session_id,
135
+ id,
136
+ name: file.metadata['original-filename'],
137
+ };
138
+ });
139
+
140
+ if (!postData.files) {
141
+ postData.files = fileReferences;
142
+ } else if (Array.isArray(postData.files)) {
143
+ postData.files = [...postData.files, ...fileReferences];
144
+ }
145
+ }
146
+ } catch {
147
+ // eslint-disable-next-line no-console
148
+ console.warn(`Failed to fetch files for session: ${session_id}`);
149
+ }
150
+ }
151
+
75
152
  try {
76
153
  const fetchOptions: RequestInit = {
77
154
  method: 'POST',
@@ -96,7 +173,7 @@ Usage:
96
173
  if (result.stdout) {
97
174
  formattedOutput += `stdout:\n${result.stdout}\n`;
98
175
  } else {
99
- formattedOutput += 'stdout: Empty. Ensure you\'re writing output explicitly.\n';
176
+ formattedOutput += emptyOutputMessage;
100
177
  }
101
178
  if (result.stderr) formattedOutput += `stderr:\n${result.stderr}\n`;
102
179
  if (result.files && result.files.length > 0) {
@@ -104,24 +181,30 @@ Usage:
104
181
 
105
182
  const fileCount = result.files.length;
106
183
  for (let i = 0; i < fileCount; i++) {
107
- const filename = result.files[i].name;
108
- const isImage = imageExtRegex.test(filename);
109
- formattedOutput += isImage ? `${filename}${imageMessage}` : `${filename}${otherMessage}`;
184
+ const file = result.files[i];
185
+ const isImage = imageExtRegex.test(file.name);
186
+ formattedOutput += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
110
187
 
111
188
  if (i < fileCount - 1) {
112
189
  formattedOutput += fileCount <= 3 ? ', ' : ',\n';
113
190
  }
114
191
  }
115
192
 
116
- return [formattedOutput.trim(), {
117
- session_id: result.session_id,
118
- files: result.files,
119
- }];
193
+ formattedOutput += `\nsession_id: ${result.session_id}\n\n${accessMessage}`;
194
+ return [
195
+ formattedOutput.trim(),
196
+ {
197
+ session_id: result.session_id,
198
+ files: result.files,
199
+ },
200
+ ];
120
201
  }
121
202
 
122
203
  return [formattedOutput.trim(), { session_id: result.session_id }];
123
204
  } catch (error) {
124
- return [`Execution error:\n\n${(error as Error | undefined)?.message}`, {}];
205
+ throw new Error(
206
+ `Execution error:\n\n${(error as Error | undefined)?.message}`
207
+ );
125
208
  }
126
209
  },
127
210
  {
@@ -133,4 +216,4 @@ Usage:
133
216
  );
134
217
  }
135
218
 
136
- export { createCodeExecutionTool };
219
+ export { createCodeExecutionTool };