@librechat/agents 3.8.5 → 3.8.7

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 (111) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +1 -1
  2. package/dist/cjs/common/constants.cjs +2 -0
  3. package/dist/cjs/common/constants.cjs.map +1 -1
  4. package/dist/cjs/graphs/Graph.cjs +18 -9
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/hitl/approvalReview.cjs +1 -1
  7. package/dist/cjs/hooks/types.cjs.map +1 -1
  8. package/dist/cjs/main.cjs +8 -6
  9. package/dist/cjs/messages/index.cjs +1 -1
  10. package/dist/cjs/messages/prune.cjs +1 -1
  11. package/dist/cjs/run.cjs +7 -3
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/stream.cjs +13 -3
  14. package/dist/cjs/stream.cjs.map +1 -1
  15. package/dist/cjs/summarization/node.cjs +3 -1
  16. package/dist/cjs/summarization/node.cjs.map +1 -1
  17. package/dist/cjs/tools/BashExecutor.cjs +79 -27
  18. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  19. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +47 -7
  20. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  21. package/dist/cjs/tools/CodeExecutor.cjs +5 -2
  22. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  23. package/dist/cjs/tools/CodeSessionFileSummary.cjs +11 -1
  24. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
  25. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +54 -8
  26. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  27. package/dist/cjs/tools/ToolNode.cjs +176 -43
  28. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  29. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +1 -0
  30. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +0 -1
  32. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +120 -26
  34. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  35. package/dist/cjs/tools/toolBatchReplay.cjs +4 -2
  36. package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -1
  37. package/dist/cjs/utils/index.cjs +1 -1
  38. package/dist/esm/agents/AgentContext.mjs +1 -1
  39. package/dist/esm/common/constants.mjs +2 -1
  40. package/dist/esm/common/constants.mjs.map +1 -1
  41. package/dist/esm/graphs/Graph.mjs +17 -8
  42. package/dist/esm/graphs/Graph.mjs.map +1 -1
  43. package/dist/esm/hitl/approvalReview.mjs +1 -1
  44. package/dist/esm/hooks/types.mjs.map +1 -1
  45. package/dist/esm/main.mjs +7 -7
  46. package/dist/esm/messages/index.mjs +1 -1
  47. package/dist/esm/messages/prune.mjs +1 -1
  48. package/dist/esm/run.mjs +7 -3
  49. package/dist/esm/run.mjs.map +1 -1
  50. package/dist/esm/stream.mjs +13 -3
  51. package/dist/esm/stream.mjs.map +1 -1
  52. package/dist/esm/summarization/node.mjs +3 -1
  53. package/dist/esm/summarization/node.mjs.map +1 -1
  54. package/dist/esm/tools/BashExecutor.mjs +81 -29
  55. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  56. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +48 -9
  57. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  58. package/dist/esm/tools/CodeExecutor.mjs +5 -2
  59. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  60. package/dist/esm/tools/CodeSessionFileSummary.mjs +11 -2
  61. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
  62. package/dist/esm/tools/ProgrammaticToolCalling.mjs +55 -9
  63. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  64. package/dist/esm/tools/ToolNode.mjs +176 -43
  65. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  66. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +1 -0
  67. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  68. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +0 -1
  69. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  70. package/dist/esm/tools/subagent/SubagentExecutor.mjs +120 -26
  71. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  72. package/dist/esm/tools/toolBatchReplay.mjs +4 -2
  73. package/dist/esm/tools/toolBatchReplay.mjs.map +1 -1
  74. package/dist/esm/utils/index.mjs +1 -1
  75. package/dist/types/common/constants.d.ts +2 -0
  76. package/dist/types/graphs/Graph.d.ts +3 -2
  77. package/dist/types/hooks/types.d.ts +3 -0
  78. package/dist/types/run.d.ts +1 -0
  79. package/dist/types/summarization/node.d.ts +1 -0
  80. package/dist/types/tools/BashExecutor.d.ts +2 -0
  81. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +1 -0
  82. package/dist/types/tools/CodeExecutor.d.ts +1 -1
  83. package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
  84. package/dist/types/tools/ProgrammaticToolCalling.d.ts +2 -2
  85. package/dist/types/tools/ToolNode.d.ts +6 -2
  86. package/dist/types/tools/subagent/SubagentExecutor.d.ts +7 -4
  87. package/dist/types/tools/toolBatchReplay.d.ts +2 -0
  88. package/dist/types/types/graph.d.ts +28 -0
  89. package/dist/types/types/run.d.ts +2 -0
  90. package/dist/types/types/tools.d.ts +27 -3
  91. package/package.json +2 -2
  92. package/src/common/constants.ts +3 -0
  93. package/src/graphs/Graph.ts +32 -18
  94. package/src/hooks/types.ts +3 -0
  95. package/src/run.ts +30 -15
  96. package/src/stream.ts +27 -5
  97. package/src/summarization/node.ts +3 -0
  98. package/src/tools/BashExecutor.ts +143 -40
  99. package/src/tools/BashProgrammaticToolCalling.ts +82 -10
  100. package/src/tools/CodeExecutor.ts +12 -2
  101. package/src/tools/CodeSessionFileSummary.ts +25 -1
  102. package/src/tools/ProgrammaticToolCalling.ts +77 -6
  103. package/src/tools/ToolNode.ts +665 -170
  104. package/src/tools/local/LocalProgrammaticToolCalling.ts +1 -0
  105. package/src/tools/subagent/SubagentExecutionRegistry.ts +0 -1
  106. package/src/tools/subagent/SubagentExecutor.ts +214 -55
  107. package/src/tools/toolBatchReplay.ts +23 -1
  108. package/src/types/graph.ts +37 -0
  109. package/src/types/run.ts +2 -0
  110. package/src/types/tools.ts +30 -3
  111. package/src/utils/llmConfig.ts +0 -1
package/src/stream.ts CHANGED
@@ -805,6 +805,21 @@ function startEagerToolExecutions(args: {
805
805
  return;
806
806
  }
807
807
 
808
+ const codeSessionBaselineById = new Map(
809
+ entries.map((entry) => [
810
+ entry.id,
811
+ new Map(
812
+ (entry.request.codeSessionContext?.files ?? []).map((file) => [
813
+ file.name,
814
+ `${file.storage_session_id}\0${file.id}`,
815
+ ])
816
+ ),
817
+ ])
818
+ );
819
+ for (const entry of entries) {
820
+ entry.request.codeSessionBaselineId =
821
+ entry.request.codeSessionContext?.session_id;
822
+ }
808
823
  const records: t.EagerEventToolExecution[] = [];
809
824
  const promise: Promise<t.EagerEventToolExecutionOutcome> = new Promise<
810
825
  t.ToolExecuteResult[]
@@ -818,6 +833,7 @@ function startEagerToolExecutions(args: {
818
833
  }
819
834
  };
820
835
  const batchRequest: t.ToolExecuteBatchRequest = {
836
+ executionContext: graph.subagentExecutionContext,
821
837
  toolCalls: entries.map((entry) => entry.request),
822
838
  userId: graph.config?.configurable?.user_id as string | undefined,
823
839
  agentId: agentContext?.agentId,
@@ -826,10 +842,14 @@ function startEagerToolExecutions(args: {
826
842
  | Partial<Pick<AgentContext, 'getCallerCapabilityProjectionSnapshot'>>
827
843
  | undefined
828
844
  )?.getCallerCapabilityProjectionSnapshot?.(),
829
- configurable: graph.config?.configurable as
830
- | Record<string, unknown>
831
- | undefined,
832
- metadata,
845
+ configurable: {
846
+ ...graph.config?.configurable,
847
+ executionContext: graph.subagentExecutionContext,
848
+ },
849
+ metadata: {
850
+ ...metadata,
851
+ executionContext: graph.subagentExecutionContext,
852
+ },
833
853
  signal: composeAbortSignals(
834
854
  graph.config?.signal,
835
855
  graph.breakerAbort.signal
@@ -873,6 +893,7 @@ function startEagerToolExecutions(args: {
873
893
  toolName: entry.toolName,
874
894
  args: entry.coercedArgs,
875
895
  request: entry.request,
896
+ codeSessionBaselineByName: codeSessionBaselineById.get(entry.id),
876
897
  promise,
877
898
  };
878
899
  records.push(record);
@@ -1653,7 +1674,8 @@ export class ChatModelStreamHandler implements t.EventHandler {
1653
1674
  if (
1654
1675
  eventBreaker != null &&
1655
1676
  eventBreaker.signal.aborted &&
1656
- (eventBreaker.signal.reason instanceof StreamLimitExceededError || eventBreaker.signal.reason instanceof PreparedSubagentError)
1677
+ (eventBreaker.signal.reason instanceof StreamLimitExceededError ||
1678
+ eventBreaker.signal.reason instanceof PreparedSubagentError)
1657
1679
  ) {
1658
1680
  throw eventBreaker.signal.reason;
1659
1681
  }
@@ -924,6 +924,7 @@ async function dispatchCompletionEvents(params: {
924
924
  runId: sessionId,
925
925
  threadId,
926
926
  agentId,
927
+ executionContext: graph.getSubagentExecutionContext?.(),
927
928
  summary: summaryText,
928
929
  messagesAfterCount,
929
930
  },
@@ -948,6 +949,7 @@ interface CreateSummarizeNodeParams {
948
949
  config?: RunnableConfig;
949
950
  runId?: string;
950
951
  isMultiAgent: boolean;
952
+ getSubagentExecutionContext?: () => t.SubagentExecutionContext | undefined;
951
953
  hookRegistry?: HookRegistry;
952
954
  getToolsForBinding?: (
953
955
  provider: t.ProviderName,
@@ -1331,6 +1333,7 @@ export function createSummarizeNode({
1331
1333
  runId: sessionId,
1332
1334
  threadId,
1333
1335
  agentId: request.agentId,
1336
+ executionContext: graph.getSubagentExecutionContext?.(),
1334
1337
  messagesBeforeCount: messagesToRefine.length,
1335
1338
  trigger:
1336
1339
  request.reason === 'manual'
@@ -11,6 +11,7 @@ import {
11
11
  addCodeApiExecutionProfileHeader,
12
12
  emptyOutputMessage,
13
13
  buildCodeApiHttpErrorMessage,
14
+ buildCodeApiExecutionErrorMessage,
14
15
  CodeApiRequestError,
15
16
  buildCodeApiEndpoint,
16
17
  getCodeBaseURL,
@@ -23,9 +24,12 @@ import {
23
24
  normalizeArtifactDeliveryFailure,
24
25
  } from '@/tools/ArtifactDelivery';
25
26
  import { logCodeApiDiagnostic } from '@/tools/diagnostics';
27
+ import { appendExecutionArtifactFileSummary } from '@/tools/CodeSessionFileSummary';
26
28
  import { resolveFetchProxyAgent } from '@/utils/proxy';
27
29
  import { INTENT_PROPERTY } from '@/tools/intentArg';
28
30
  import { Constants } from '@/common';
31
+ import { prepareBashProgrammaticCode } from './BashProgrammaticToolCalling';
32
+ import { makeRequest } from './ProgrammaticToolCalling';
29
33
 
30
34
  config();
31
35
 
@@ -90,6 +94,17 @@ Usage:
90
94
  - NEVER use this tool to execute malicious commands.
91
95
  `.trim();
92
96
 
97
+ const AttachedWorkspaceBashExecutionToolDescription = `
98
+ Runs bash commands in the selected persistent project through an isolated sandbox process.
99
+
100
+ Usage:
101
+ - Project file changes persist between calls; shell variables, background processes, and execution-private temporary files do not.
102
+ - Injected files and generated artifacts use \${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}; write durable files to the project root.
103
+ - Generated artifacts are automatically delivered; **DO NOT** provide download links.
104
+ - ${BASH_SHELL_GUIDANCE}
105
+ - NEVER use this tool to execute malicious commands.
106
+ `.trim();
107
+
93
108
  /**
94
109
  * Supplemental prompt documenting the tool-output reference feature.
95
110
  *
@@ -121,11 +136,14 @@ Referencing previous tool outputs:
121
136
  export function buildBashExecutionToolDescription(options?: {
122
137
  enableToolOutputReferences?: boolean;
123
138
  statefulSessions?: boolean;
139
+ attachedWorkspace?: boolean;
124
140
  }): string {
125
- const base =
126
- options?.statefulSessions === true
127
- ? StatefulBashExecutionToolDescription
128
- : BashExecutionToolDescription;
141
+ let base = BashExecutionToolDescription;
142
+ if (options?.attachedWorkspace === true) {
143
+ base = AttachedWorkspaceBashExecutionToolDescription;
144
+ } else if (options?.statefulSessions === true) {
145
+ base = StatefulBashExecutionToolDescription;
146
+ }
129
147
  if (options?.enableToolOutputReferences === true) {
130
148
  return `${base}\n\n${BashToolOutputReferencesGuide}`;
131
149
  }
@@ -136,19 +154,33 @@ const STATELESS_BASH_PARAM_NOTE =
136
154
  'The environment is stateless; variables and state don\'t persist between executions.';
137
155
  const STATEFUL_BASH_PARAM_NOTE =
138
156
  'Files written to /mnt/data persist between calls on the same warm machine. Each call runs in a fresh sandbox: shell variables, cwd, /tmp, and background processes do NOT survive the call. Only /mnt/data is durable.';
157
+ const ATTACHED_BASH_PARAM_NOTE =
158
+ 'Commands start in the selected persistent project. Project file changes persist, but shell variables, background processes, and execution-private temporary files do not.';
159
+ const ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE =
160
+ 'Injected files and generated artifacts use `${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}` for this execution only. Write anything needed later into the selected project.';
161
+ const ATTACHED_BASH_TMP_REMINDER =
162
+ 'Note: /tmp files are same-call scratch only and were not persisted; write files needed later into the selected project.';
139
163
 
140
164
  export function buildBashExecutionToolSchema(opts?: {
141
165
  statefulSessions?: boolean;
166
+ attachedWorkspace?: boolean;
142
167
  }): typeof BashExecutionToolSchema {
143
- const note =
144
- opts?.statefulSessions === true
145
- ? STATEFUL_BASH_PARAM_NOTE
146
- : STATELESS_BASH_PARAM_NOTE;
147
- const commandDescription =
168
+ let note = STATELESS_BASH_PARAM_NOTE;
169
+ if (opts?.attachedWorkspace === true) {
170
+ note = ATTACHED_BASH_PARAM_NOTE;
171
+ } else if (opts?.statefulSessions === true) {
172
+ note = STATEFUL_BASH_PARAM_NOTE;
173
+ }
174
+ let commandDescription =
148
175
  BashExecutionToolSchema.properties.command.description.replace(
149
176
  STATELESS_BASH_PARAM_NOTE,
150
177
  note
151
178
  );
179
+ if (opts?.attachedWorkspace === true) {
180
+ commandDescription = commandDescription
181
+ .replace('- Prior /mnt/data files are available and can be modified in place.\n', '')
182
+ .replace(CODE_ARTIFACT_PATH_GUIDANCE, ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE);
183
+ }
152
184
  return {
153
185
  ...BashExecutionToolSchema,
154
186
  properties: {
@@ -163,6 +195,15 @@ export function buildBashExecutionToolSchema(opts?: {
163
195
 
164
196
  export const BashExecutionToolName = Constants.BASH_TOOL;
165
197
 
198
+ function quoteBashArgument(value: string): string {
199
+ return `'${value.replace(/'/g, '\'"\'"\'')}'`;
200
+ }
201
+
202
+ function commandWithArguments(command: string, args: string[] | undefined): string {
203
+ if (args == null || args.length === 0) return command;
204
+ return `bash -c ${quoteBashArgument(command)} -- ${args.map(quoteBashArgument).join(' ')}`;
205
+ }
206
+
166
207
  /**
167
208
  * Default bash tool definition using the base description.
168
209
  *
@@ -182,9 +223,17 @@ export const BashExecutionToolDefinition = {
182
223
  function createBashExecutionTool(
183
224
  params: t.BashExecutionToolParams | null = {}
184
225
  ): DynamicStructuredTool {
226
+ const workspaceId = params?.workspaceId?.trim();
227
+ const hasWorkspace = workspaceId != null && workspaceId !== '';
228
+ if (
229
+ hasWorkspace &&
230
+ !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(workspaceId)
231
+ ) {
232
+ throw new Error('Invalid attached workspace identifier');
233
+ }
185
234
  const execEndpoint = buildCodeApiEndpoint(
186
235
  params?.baseUrl ?? getCodeBaseURL(),
187
- 'exec'
236
+ hasWorkspace ? 'exec/programmatic' : 'exec'
188
237
  );
189
238
 
190
239
  return tool(
@@ -197,17 +246,20 @@ function createBashExecutionTool(
197
246
  executionProfile,
198
247
  runtimeSessionHint,
199
248
  statefulSessions,
249
+ workspaceId: _workspaceId,
200
250
  ...executionParams
201
251
  } = params ?? {};
202
252
  void _baseUrl;
253
+ void _workspaceId;
203
254
  /* Drop any model-supplied `runtime_session_hint` from the raw args: the
204
255
  * hint must only come from ToolNode's injected `_runtime_session_hint`
205
256
  * (below), never from the tool call itself. */
206
257
  /* `intent` is a UI display label — never part of the wire body. */
207
258
  const {
208
- command,
259
+ command: rawCommand,
209
260
  intent: _ignoredIntent,
210
261
  runtime_session_hint: _ignoredModelHint,
262
+ args,
211
263
  ...rest
212
264
  } = rawInput as {
213
265
  command: string;
@@ -217,6 +269,9 @@ function createBashExecutionTool(
217
269
  };
218
270
  void _ignoredModelHint;
219
271
  void _ignoredIntent;
272
+ const command = hasWorkspace
273
+ ? commandWithArguments(rawCommand, args)
274
+ : rawCommand;
220
275
  const { session_id, _injected_files, _runtime_session_hint } =
221
276
  (config.toolCall ?? {}) as {
222
277
  session_id?: string;
@@ -226,7 +281,9 @@ function createBashExecutionTool(
226
281
 
227
282
  const postData: Record<string, unknown> = {
228
283
  lang: 'bash',
229
- code: command,
284
+ code: hasWorkspace ? prepareBashProgrammaticCode(command) : command,
285
+ ...(hasWorkspace ? { tools: [] } : {}),
286
+ ...(!hasWorkspace && args != null ? { args } : {}),
230
287
  ...rest,
231
288
  ...executionParams,
232
289
  };
@@ -265,44 +322,70 @@ function createBashExecutionTool(
265
322
  try {
266
323
  const resolvedAuthHeaders =
267
324
  await resolveCodeApiAuthHeaders(authHeaders);
268
- const fetchOptions: RequestInit = {
269
- method: 'POST',
270
- headers: {
271
- 'Content-Type': 'application/json',
272
- 'User-Agent': 'LibreChat/1.0',
273
- ...addCodeApiExecutionProfileHeader(
274
- resolvedAuthHeaders,
275
- executionProfile
276
- ),
277
- },
278
- body: JSON.stringify(postData),
279
- };
325
+ let result: Partial<t.ExecuteResult> & t.ProgrammaticExecutionResponse;
326
+ if (hasWorkspace) {
327
+ result = await makeRequest(
328
+ execEndpoint,
329
+ postData,
330
+ undefined,
331
+ {
332
+ ...resolvedAuthHeaders,
333
+ 'X-LibreChat-Code-Workspace-ID': workspaceId,
334
+ },
335
+ executionProfile,
336
+ config.signal
337
+ );
338
+ } else {
339
+ const fetchOptions: RequestInit = {
340
+ method: 'POST',
341
+ headers: {
342
+ 'Content-Type': 'application/json',
343
+ 'User-Agent': 'LibreChat/1.0',
344
+ ...addCodeApiExecutionProfileHeader(
345
+ resolvedAuthHeaders,
346
+ executionProfile
347
+ ),
348
+ },
349
+ body: JSON.stringify(postData),
350
+ signal: config.signal,
351
+ };
280
352
 
281
- const proxyAgent = resolveFetchProxyAgent(execEndpoint);
282
- if (proxyAgent != null) {
283
- fetchOptions.agent = proxyAgent;
353
+ const proxyAgent = resolveFetchProxyAgent(execEndpoint);
354
+ if (proxyAgent != null) {
355
+ fetchOptions.agent = proxyAgent;
356
+ }
357
+ const response = await fetch(execEndpoint, fetchOptions);
358
+ if (!response.ok) {
359
+ throw new CodeApiRequestError(
360
+ await buildCodeApiHttpErrorMessage('POST', execEndpoint, response, {
361
+ profile: executionProfile,
362
+ })
363
+ );
364
+ }
365
+ result = (await response.json()) as Partial<t.ExecuteResult> &
366
+ t.ProgrammaticExecutionResponse;
284
367
  }
285
- const response = await fetch(execEndpoint, fetchOptions);
286
- if (!response.ok) {
368
+ if (hasWorkspace && result.status !== 'completed') {
287
369
  throw new CodeApiRequestError(
288
- await buildCodeApiHttpErrorMessage('POST', execEndpoint, response, {
289
- profile: executionProfile,
290
- })
370
+ buildCodeApiExecutionErrorMessage(
371
+ result as t.ProgrammaticExecutionResponse
372
+ )
291
373
  );
292
374
  }
293
-
294
- const result: t.ExecuteResult = await response.json();
295
375
  let formattedOutput = '';
296
- if (result.stdout) {
376
+ if (typeof result.stdout === 'string' && result.stdout.length > 0) {
297
377
  formattedOutput += `stdout:\n${result.stdout}\n`;
298
378
  } else {
299
379
  formattedOutput += emptyOutputMessage;
300
380
  }
301
- if (result.stderr) formattedOutput += `stderr:\n${result.stderr}\n`;
381
+ if (typeof result.stderr === 'string' && result.stderr.length > 0) {
382
+ formattedOutput += `stderr:\n${result.stderr}\n`;
383
+ }
302
384
 
303
385
  const outputWithReminder = appendTmpScratchReminder(
304
386
  formattedOutput,
305
- command
387
+ command,
388
+ hasWorkspace ? ATTACHED_BASH_TMP_REMINDER : undefined
306
389
  );
307
390
  const artifactDelivery = normalizeArtifactDeliveryFailure(
308
391
  result.artifact_delivery
@@ -312,6 +395,10 @@ function createBashExecutionTool(
312
395
  artifactDelivery
313
396
  );
314
397
  const hasFiles = result.files != null && result.files.length > 0;
398
+ const deletionEcho =
399
+ result.deleted_files != null
400
+ ? { deleted_files: result.deleted_files }
401
+ : {};
315
402
  const runtimeEcho =
316
403
  result.runtime_session_id != null
317
404
  ? {
@@ -320,7 +407,15 @@ function createBashExecutionTool(
320
407
  }
321
408
  : {};
322
409
  return [
323
- appendCodeSessionFileSummary(outputWithDeliveryWarning, result.files),
410
+ hasWorkspace
411
+ ? appendExecutionArtifactFileSummary(
412
+ outputWithDeliveryWarning,
413
+ result.files
414
+ )
415
+ : appendCodeSessionFileSummary(
416
+ outputWithDeliveryWarning,
417
+ result.files
418
+ ),
324
419
  (hasFiles
325
420
  ? {
326
421
  session_id: result.session_id,
@@ -328,6 +423,7 @@ function createBashExecutionTool(
328
423
  ...(artifactDelivery != null
329
424
  ? { artifact_delivery: artifactDelivery }
330
425
  : {}),
426
+ ...deletionEcho,
331
427
  ...runtimeEcho,
332
428
  }
333
429
  : {
@@ -335,6 +431,7 @@ function createBashExecutionTool(
335
431
  ...(artifactDelivery != null
336
432
  ? { artifact_delivery: artifactDelivery }
337
433
  : {}),
434
+ ...deletionEcho,
338
435
  ...runtimeEcho,
339
436
  }) satisfies t.CodeExecutionArtifact,
340
437
  ];
@@ -343,15 +440,21 @@ function createBashExecutionTool(
343
440
  normalizeCodeApiRequestError(error).message,
344
441
  command
345
442
  );
346
- throw new CodeApiRequestError(`Execution error:\n\n${messageWithReminder}`);
443
+ throw new CodeApiRequestError(
444
+ `Execution error:\n\n${messageWithReminder}`
445
+ );
347
446
  }
348
447
  },
349
448
  {
350
449
  name: BashExecutionToolName,
351
450
  description: buildBashExecutionToolDescription({
352
451
  statefulSessions: params?.statefulSessions,
452
+ attachedWorkspace: hasWorkspace,
453
+ }),
454
+ schema: buildBashExecutionToolSchema({
455
+ statefulSessions: params?.statefulSessions,
456
+ attachedWorkspace: hasWorkspace,
353
457
  }),
354
- schema: buildBashExecutionToolSchema(params ?? undefined),
355
458
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
356
459
  }
357
460
  );
@@ -11,6 +11,7 @@ import {
11
11
  buildCodeApiEndpoint,
12
12
  CodeApiRequestError,
13
13
  getCodeBaseURL,
14
+ resolveCodeApiAuthHeaders,
14
15
  selectRuntimeSessionHint,
15
16
  } from './CodeExecutor';
16
17
  import {
@@ -44,6 +45,11 @@ config();
44
45
  const DEFAULT_MAX_ROUND_TRIPS = 20;
45
46
  const DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();
46
47
  const BASH_LAST_BACKGROUND_PID_GUARD = ': &\nwait "$!"';
48
+ const CODE_API_WORKSPACE_HEADER = 'X-LibreChat-Code-Workspace-ID';
49
+ const ATTACHED_BASH_DATA_DIRECTORY = '"${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}"';
50
+ const ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE =
51
+ `Use ${ATTACHED_BASH_DATA_DIRECTORY} for injected files and generated artifacts. ` +
52
+ 'The directory is execution-scoped; the selected workspace is the persistent project root.';
47
53
 
48
54
  /** Bash reserved words that get `_tool` suffix when used as function names */
49
55
  const BASH_RESERVED = new Set([
@@ -81,6 +87,11 @@ Each call is a fresh bash shell. Variables and state do NOT persist between call
81
87
  You MUST complete your entire workflow in ONE code block.
82
88
  DO NOT split work across multiple calls expecting to reuse variables.`;
83
89
 
90
+ const ATTACHED_WORKSPACE_WARNING = `ATTACHED WORKSPACE EXECUTION:
91
+ - Commands start in the selected persistent workspace; project file changes persist between calls.
92
+ - Each sandbox run is a fresh process, so shell variables, background processes, and temporary execution data do not persist.
93
+ - Injected files and generated artifacts use \${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}; do not copy them into the project unless the task requires it.`;
94
+
84
95
  const CORE_RULES = `Rules:
85
96
  - One call: state does not persist
86
97
  - Tools are pre-defined as bash functions—DO NOT redefine them
@@ -107,6 +118,30 @@ Example (Parallel calls):
107
118
  echo "SF: $(jq -r . /mnt/data/sf.json)"
108
119
  echo "NY: $(jq -r . /mnt/data/ny.json)"`;
109
120
 
121
+ const ATTACHED_CORE_RULES = `Rules:
122
+ - One call: process state does not persist; project files do
123
+ - Tools are pre-defined as bash functions—DO NOT redefine them
124
+ - Each tool function accepts a JSON string argument
125
+ - Resolve tool calls into variables before changing project files; do not redirect a tool call directly into the project
126
+ - Set data_dir=${ATTACHED_BASH_DATA_DIRECTORY}; save generated artifacts there, and write durable project files relative to the working directory
127
+ - Tool stdout is normalized to one compact JSON value when possible; parse saved stdout once, then use fromjson? // . only for JSON-string fields
128
+ - Only echo/printf output returns to the model
129
+ - ${ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE}
130
+ - ${BASH_SHELL_GUIDANCE}
131
+ - timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;
132
+
133
+ const ATTACHED_EXAMPLES = `Example (Complete workflow in one call):
134
+ data=$(query_database '{"sql": "SELECT * FROM users"}')
135
+ echo "$data" | jq '.[] | .name'
136
+
137
+ Example (Parallel calls):
138
+ data_dir=${ATTACHED_BASH_DATA_DIRECTORY}
139
+ { sf=$(web_search '{"query": "SF weather"}'); printf '%s\n' "$sf" > "$data_dir/sf.json"; } &
140
+ { ny=$(web_search '{"query": "NY weather"}'); printf '%s\n' "$ny" > "$data_dir/ny.json"; } &
141
+ wait
142
+ echo "SF: $(jq -r . "$data_dir/sf.json")"
143
+ echo "NY: $(jq -r . "$data_dir/ny.json")"`;
144
+
110
145
  const CODE_PARAM_DESCRIPTION = `Bash code that calls tools programmatically. Tools are available as bash functions.
111
146
 
112
147
  ${STATELESS_WARNING}
@@ -175,7 +210,7 @@ export const BashProgrammaticToolCallingDefinition = {
175
210
  schema: BashProgrammaticToolCallingSchema,
176
211
  } as const;
177
212
 
178
- function prepareBashProgrammaticCode(code: string): string {
213
+ export function prepareBashProgrammaticCode(code: string): string {
179
214
  /* The Code API's generated Bash wrapper reads `$!` after user code. A user
180
215
  * `set -u` makes that expansion fail when no background process has run.
181
216
  * Seed and reap a no-op job before user code so strict mode remains active
@@ -326,7 +361,38 @@ export function createBashProgrammaticToolCallingTool(
326
361
  const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
327
362
  const proxy = initParams.proxy ?? process.env.PROXY;
328
363
  const debug = initParams.debug ?? process.env.BASH_PTC_DEBUG === 'true';
364
+ const workspaceId = initParams.workspaceId?.trim();
365
+ const hasWorkspace = workspaceId != null && workspaceId !== '';
366
+ if (
367
+ hasWorkspace &&
368
+ !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(workspaceId)
369
+ ) {
370
+ throw new Error('Invalid attached workspace identifier');
371
+ }
372
+ const requestAuthHeaders: t.CodeApiAuthHeaders = hasWorkspace
373
+ ? async (): Promise<t.CodeApiAuthHeaderMap> => ({
374
+ ...(await resolveCodeApiAuthHeaders(initParams.authHeaders)),
375
+ [CODE_API_WORKSPACE_HEADER]: workspaceId,
376
+ })
377
+ : (initParams.authHeaders ?? {});
329
378
  const EXEC_ENDPOINT = buildCodeApiEndpoint(baseUrl, 'exec/programmatic');
379
+ const description = hasWorkspace
380
+ ? BashProgrammaticToolCallingDescription.replace(
381
+ STATELESS_WARNING,
382
+ ATTACHED_WORKSPACE_WARNING
383
+ )
384
+ .replace(CORE_RULES, ATTACHED_CORE_RULES)
385
+ .replace(EXAMPLES, ATTACHED_EXAMPLES)
386
+ : BashProgrammaticToolCallingDescription;
387
+ const schema = createBashProgrammaticToolCallingSchema(maxRunTimeoutMs);
388
+ if (hasWorkspace) {
389
+ schema.properties.code.description = CODE_PARAM_DESCRIPTION.replace(
390
+ STATELESS_WARNING,
391
+ ATTACHED_WORKSPACE_WARNING
392
+ )
393
+ .replace(CORE_RULES, ATTACHED_CORE_RULES)
394
+ .replace(EXAMPLES, ATTACHED_EXAMPLES);
395
+ }
330
396
 
331
397
  return tool(
332
398
  async (rawParams, config) => {
@@ -449,7 +515,7 @@ export function createBashProgrammaticToolCallingTool(
449
515
 
450
516
  /* Raw `code`, not `preparedCode`: the `$!` guard exists for the
451
517
  * programmatic replay wrapper, which plain `/exec` never applies. */
452
- if (needsNoTools) {
518
+ if (needsNoTools && !hasWorkspace) {
453
519
  return await runPlainExecution({
454
520
  baseUrl,
455
521
  lang: 'bash',
@@ -459,7 +525,7 @@ export function createBashProgrammaticToolCallingTool(
459
525
  files,
460
526
  runtimeSessionHint,
461
527
  proxy,
462
- authHeaders: initParams.authHeaders,
528
+ authHeaders: requestAuthHeaders,
463
529
  executionProfile: initParams.executionProfile,
464
530
  });
465
531
  }
@@ -478,8 +544,9 @@ export function createBashProgrammaticToolCallingTool(
478
544
  : {}),
479
545
  },
480
546
  proxy,
481
- initParams.authHeaders,
482
- initParams.executionProfile
547
+ requestAuthHeaders,
548
+ initParams.executionProfile,
549
+ config.signal
483
550
  );
484
551
 
485
552
  // ====================================================================
@@ -519,8 +586,9 @@ export function createBashProgrammaticToolCallingTool(
519
586
  tool_results: toolResults,
520
587
  },
521
588
  proxy,
522
- initParams.authHeaders,
523
- initParams.executionProfile
589
+ requestAuthHeaders,
590
+ initParams.executionProfile,
591
+ config.signal
524
592
  );
525
593
  }
526
594
 
@@ -529,7 +597,11 @@ export function createBashProgrammaticToolCallingTool(
529
597
  // ====================================================================
530
598
 
531
599
  if (response.status === 'completed') {
532
- return formatCompletedResponse(response, code);
600
+ return formatCompletedResponse(
601
+ response,
602
+ code,
603
+ hasWorkspace ? 'execution' : 'session'
604
+ );
533
605
  }
534
606
 
535
607
  if (response.status === 'error') {
@@ -551,8 +623,8 @@ export function createBashProgrammaticToolCallingTool(
551
623
  },
552
624
  {
553
625
  name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
554
- description: BashProgrammaticToolCallingDescription,
555
- schema: createBashProgrammaticToolCallingSchema(maxRunTimeoutMs),
626
+ description,
627
+ schema,
556
628
  responseFormat: Constants.CONTENT_AND_ARTIFACT,
557
629
  }
558
630
  );
@@ -58,11 +58,15 @@ export const TMP_SCRATCH_OUTPUT_REMINDER =
58
58
  export const FAILED_EXECUTION_FILE_REMINDER =
59
59
  'Note: any files written during this failed call were not registered for later calls; fix the error and rerun before relying on them.';
60
60
 
61
- export function appendTmpScratchReminder(output: string, code: string): string {
61
+ export function appendTmpScratchReminder(
62
+ output: string,
63
+ code: string,
64
+ reminder = TMP_SCRATCH_OUTPUT_REMINDER
65
+ ): string {
62
66
  if (!TMP_PATH_PATTERN.test(code)) {
63
67
  return output;
64
68
  }
65
- return `${output.trimEnd()}\n${TMP_SCRATCH_OUTPUT_REMINDER}\n`;
69
+ return `${output.trimEnd()}\n${reminder}\n`;
66
70
  }
67
71
 
68
72
  export function appendFailedExecutionFileReminder(
@@ -668,6 +672,10 @@ function createCodeExecutionTool(
668
672
  artifactDelivery
669
673
  );
670
674
  const hasFiles = result.files != null && result.files.length > 0;
675
+ const deletionEcho =
676
+ result.deleted_files != null
677
+ ? { deleted_files: result.deleted_files }
678
+ : {};
671
679
  /* Echo the durable runtime session (stateful backends only) so hosts
672
680
  * can surface a "session active / was reset" signal later. Additive:
673
681
  * absent on stateless servers. */
@@ -687,6 +695,7 @@ function createCodeExecutionTool(
687
695
  ...(artifactDelivery != null
688
696
  ? { artifact_delivery: artifactDelivery }
689
697
  : {}),
698
+ ...deletionEcho,
690
699
  ...runtimeEcho,
691
700
  }
692
701
  : {
@@ -694,6 +703,7 @@ function createCodeExecutionTool(
694
703
  ...(artifactDelivery != null
695
704
  ? { artifact_delivery: artifactDelivery }
696
705
  : {}),
706
+ ...deletionEcho,
697
707
  ...runtimeEcho,
698
708
  }) satisfies t.CodeExecutionArtifact,
699
709
  ];