@librechat/agents 3.8.6 → 3.8.8
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.
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/tools/BashExecutor.cjs +83 -28
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +50 -7
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +5 -2
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +11 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +54 -8
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +52 -20
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolBatchReplay.cjs +3 -1
- package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -1
- package/dist/cjs/tools/workspaceIdentity.cjs +12 -0
- package/dist/cjs/tools/workspaceIdentity.cjs.map +1 -0
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/tools/BashExecutor.mjs +85 -30
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +51 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +5 -2
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +11 -2
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +55 -9
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +52 -20
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolBatchReplay.mjs +3 -1
- package/dist/esm/tools/toolBatchReplay.mjs.map +1 -1
- package/dist/esm/tools/workspaceIdentity.mjs +12 -0
- package/dist/esm/tools/workspaceIdentity.mjs.map +1 -0
- package/dist/types/tools/BashExecutor.d.ts +2 -0
- package/dist/types/tools/BashProgrammaticToolCalling.d.ts +1 -0
- package/dist/types/tools/CodeExecutor.d.ts +1 -1
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +2 -2
- package/dist/types/tools/ToolNode.d.ts +2 -0
- package/dist/types/tools/toolBatchReplay.d.ts +2 -0
- package/dist/types/tools/workspaceIdentity.d.ts +1 -0
- package/dist/types/types/tools.d.ts +20 -2
- package/package.json +2 -2
- package/src/tools/BashExecutor.ts +156 -40
- package/src/tools/BashProgrammaticToolCalling.ts +90 -10
- package/src/tools/CodeExecutor.ts +12 -2
- package/src/tools/CodeSessionFileSummary.ts +25 -1
- package/src/tools/ProgrammaticToolCalling.ts +77 -6
- package/src/tools/ToolNode.ts +152 -26
- package/src/tools/toolBatchReplay.ts +23 -1
- package/src/tools/workspaceIdentity.ts +15 -0
- package/src/types/tools.ts +23 -2
|
@@ -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,13 @@ import {
|
|
|
23
24
|
normalizeArtifactDeliveryFailure,
|
|
24
25
|
} from '@/tools/ArtifactDelivery';
|
|
25
26
|
import { logCodeApiDiagnostic } from '@/tools/diagnostics';
|
|
27
|
+
import { appendExecutionArtifactFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
28
|
+
import { resolveAttachedWorkspaceInstanceId } from '@/tools/workspaceIdentity';
|
|
26
29
|
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
27
30
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
28
31
|
import { Constants } from '@/common';
|
|
32
|
+
import { prepareBashProgrammaticCode } from './BashProgrammaticToolCalling';
|
|
33
|
+
import { makeRequest } from './ProgrammaticToolCalling';
|
|
29
34
|
|
|
30
35
|
config();
|
|
31
36
|
|
|
@@ -90,6 +95,17 @@ Usage:
|
|
|
90
95
|
- NEVER use this tool to execute malicious commands.
|
|
91
96
|
`.trim();
|
|
92
97
|
|
|
98
|
+
const AttachedWorkspaceBashExecutionToolDescription = `
|
|
99
|
+
Runs bash commands in the selected persistent project through an isolated sandbox process.
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
- Project file changes persist between calls; shell variables, background processes, and execution-private temporary files do not.
|
|
103
|
+
- Injected files and generated artifacts use \${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}; write durable files to the project root.
|
|
104
|
+
- Generated artifacts are automatically delivered; **DO NOT** provide download links.
|
|
105
|
+
- ${BASH_SHELL_GUIDANCE}
|
|
106
|
+
- NEVER use this tool to execute malicious commands.
|
|
107
|
+
`.trim();
|
|
108
|
+
|
|
93
109
|
/**
|
|
94
110
|
* Supplemental prompt documenting the tool-output reference feature.
|
|
95
111
|
*
|
|
@@ -121,11 +137,14 @@ Referencing previous tool outputs:
|
|
|
121
137
|
export function buildBashExecutionToolDescription(options?: {
|
|
122
138
|
enableToolOutputReferences?: boolean;
|
|
123
139
|
statefulSessions?: boolean;
|
|
140
|
+
attachedWorkspace?: boolean;
|
|
124
141
|
}): string {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
142
|
+
let base = BashExecutionToolDescription;
|
|
143
|
+
if (options?.attachedWorkspace === true) {
|
|
144
|
+
base = AttachedWorkspaceBashExecutionToolDescription;
|
|
145
|
+
} else if (options?.statefulSessions === true) {
|
|
146
|
+
base = StatefulBashExecutionToolDescription;
|
|
147
|
+
}
|
|
129
148
|
if (options?.enableToolOutputReferences === true) {
|
|
130
149
|
return `${base}\n\n${BashToolOutputReferencesGuide}`;
|
|
131
150
|
}
|
|
@@ -136,19 +155,33 @@ const STATELESS_BASH_PARAM_NOTE =
|
|
|
136
155
|
'The environment is stateless; variables and state don\'t persist between executions.';
|
|
137
156
|
const STATEFUL_BASH_PARAM_NOTE =
|
|
138
157
|
'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.';
|
|
158
|
+
const ATTACHED_BASH_PARAM_NOTE =
|
|
159
|
+
'Commands start in the selected persistent project. Project file changes persist, but shell variables, background processes, and execution-private temporary files do not.';
|
|
160
|
+
const ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE =
|
|
161
|
+
'Injected files and generated artifacts use `${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}` for this execution only. Write anything needed later into the selected project.';
|
|
162
|
+
const ATTACHED_BASH_TMP_REMINDER =
|
|
163
|
+
'Note: /tmp files are same-call scratch only and were not persisted; write files needed later into the selected project.';
|
|
139
164
|
|
|
140
165
|
export function buildBashExecutionToolSchema(opts?: {
|
|
141
166
|
statefulSessions?: boolean;
|
|
167
|
+
attachedWorkspace?: boolean;
|
|
142
168
|
}): typeof BashExecutionToolSchema {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
169
|
+
let note = STATELESS_BASH_PARAM_NOTE;
|
|
170
|
+
if (opts?.attachedWorkspace === true) {
|
|
171
|
+
note = ATTACHED_BASH_PARAM_NOTE;
|
|
172
|
+
} else if (opts?.statefulSessions === true) {
|
|
173
|
+
note = STATEFUL_BASH_PARAM_NOTE;
|
|
174
|
+
}
|
|
175
|
+
let commandDescription =
|
|
148
176
|
BashExecutionToolSchema.properties.command.description.replace(
|
|
149
177
|
STATELESS_BASH_PARAM_NOTE,
|
|
150
178
|
note
|
|
151
179
|
);
|
|
180
|
+
if (opts?.attachedWorkspace === true) {
|
|
181
|
+
commandDescription = commandDescription
|
|
182
|
+
.replace('- Prior /mnt/data files are available and can be modified in place.\n', '')
|
|
183
|
+
.replace(CODE_ARTIFACT_PATH_GUIDANCE, ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE);
|
|
184
|
+
}
|
|
152
185
|
return {
|
|
153
186
|
...BashExecutionToolSchema,
|
|
154
187
|
properties: {
|
|
@@ -163,6 +196,15 @@ export function buildBashExecutionToolSchema(opts?: {
|
|
|
163
196
|
|
|
164
197
|
export const BashExecutionToolName = Constants.BASH_TOOL;
|
|
165
198
|
|
|
199
|
+
function quoteBashArgument(value: string): string {
|
|
200
|
+
return `'${value.replace(/'/g, '\'"\'"\'')}'`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function commandWithArguments(command: string, args: string[] | undefined): string {
|
|
204
|
+
if (args == null || args.length === 0) return command;
|
|
205
|
+
return `bash -c ${quoteBashArgument(command)} -- ${args.map(quoteBashArgument).join(' ')}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
166
208
|
/**
|
|
167
209
|
* Default bash tool definition using the base description.
|
|
168
210
|
*
|
|
@@ -182,9 +224,21 @@ export const BashExecutionToolDefinition = {
|
|
|
182
224
|
function createBashExecutionTool(
|
|
183
225
|
params: t.BashExecutionToolParams | null = {}
|
|
184
226
|
): DynamicStructuredTool {
|
|
227
|
+
const workspaceId = params?.workspaceId?.trim();
|
|
228
|
+
const hasWorkspace = workspaceId != null && workspaceId !== '';
|
|
229
|
+
const workspaceInstanceId = resolveAttachedWorkspaceInstanceId(
|
|
230
|
+
params?.workspaceInstanceId,
|
|
231
|
+
hasWorkspace
|
|
232
|
+
);
|
|
233
|
+
if (
|
|
234
|
+
hasWorkspace &&
|
|
235
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(workspaceId)
|
|
236
|
+
) {
|
|
237
|
+
throw new Error('Invalid attached workspace identifier');
|
|
238
|
+
}
|
|
185
239
|
const execEndpoint = buildCodeApiEndpoint(
|
|
186
240
|
params?.baseUrl ?? getCodeBaseURL(),
|
|
187
|
-
'exec'
|
|
241
|
+
hasWorkspace ? 'exec/programmatic' : 'exec'
|
|
188
242
|
);
|
|
189
243
|
|
|
190
244
|
return tool(
|
|
@@ -197,26 +251,37 @@ function createBashExecutionTool(
|
|
|
197
251
|
executionProfile,
|
|
198
252
|
runtimeSessionHint,
|
|
199
253
|
statefulSessions,
|
|
254
|
+
workspaceId: _workspaceId,
|
|
255
|
+
workspaceInstanceId: _workspaceInstanceId,
|
|
200
256
|
...executionParams
|
|
201
257
|
} = params ?? {};
|
|
202
258
|
void _baseUrl;
|
|
259
|
+
void _workspaceId;
|
|
260
|
+
void _workspaceInstanceId;
|
|
203
261
|
/* Drop any model-supplied `runtime_session_hint` from the raw args: the
|
|
204
262
|
* hint must only come from ToolNode's injected `_runtime_session_hint`
|
|
205
263
|
* (below), never from the tool call itself. */
|
|
206
264
|
/* `intent` is a UI display label — never part of the wire body. */
|
|
207
265
|
const {
|
|
208
|
-
command,
|
|
266
|
+
command: rawCommand,
|
|
209
267
|
intent: _ignoredIntent,
|
|
210
268
|
runtime_session_hint: _ignoredModelHint,
|
|
269
|
+
workspace_instance_id: _ignoredModelWorkspaceInstanceId,
|
|
270
|
+
args,
|
|
211
271
|
...rest
|
|
212
272
|
} = rawInput as {
|
|
213
273
|
command: string;
|
|
214
274
|
intent?: unknown;
|
|
215
275
|
runtime_session_hint?: unknown;
|
|
276
|
+
workspace_instance_id?: unknown;
|
|
216
277
|
args?: string[];
|
|
217
278
|
};
|
|
218
279
|
void _ignoredModelHint;
|
|
280
|
+
void _ignoredModelWorkspaceInstanceId;
|
|
219
281
|
void _ignoredIntent;
|
|
282
|
+
const command = hasWorkspace
|
|
283
|
+
? commandWithArguments(rawCommand, args)
|
|
284
|
+
: rawCommand;
|
|
220
285
|
const { session_id, _injected_files, _runtime_session_hint } =
|
|
221
286
|
(config.toolCall ?? {}) as {
|
|
222
287
|
session_id?: string;
|
|
@@ -226,9 +291,14 @@ function createBashExecutionTool(
|
|
|
226
291
|
|
|
227
292
|
const postData: Record<string, unknown> = {
|
|
228
293
|
lang: 'bash',
|
|
229
|
-
code: command,
|
|
294
|
+
code: hasWorkspace ? prepareBashProgrammaticCode(command) : command,
|
|
295
|
+
...(hasWorkspace ? { tools: [] } : {}),
|
|
296
|
+
...(!hasWorkspace && args != null ? { args } : {}),
|
|
230
297
|
...rest,
|
|
231
298
|
...executionParams,
|
|
299
|
+
...(workspaceInstanceId != null && workspaceInstanceId !== ''
|
|
300
|
+
? { workspace_instance_id: workspaceInstanceId }
|
|
301
|
+
: {}),
|
|
232
302
|
};
|
|
233
303
|
|
|
234
304
|
const effectiveRuntimeSessionHint = selectRuntimeSessionHint(
|
|
@@ -265,44 +335,70 @@ function createBashExecutionTool(
|
|
|
265
335
|
try {
|
|
266
336
|
const resolvedAuthHeaders =
|
|
267
337
|
await resolveCodeApiAuthHeaders(authHeaders);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
338
|
+
let result: Partial<t.ExecuteResult> & t.ProgrammaticExecutionResponse;
|
|
339
|
+
if (hasWorkspace) {
|
|
340
|
+
result = await makeRequest(
|
|
341
|
+
execEndpoint,
|
|
342
|
+
postData,
|
|
343
|
+
undefined,
|
|
344
|
+
{
|
|
345
|
+
...resolvedAuthHeaders,
|
|
346
|
+
'X-LibreChat-Code-Workspace-ID': workspaceId,
|
|
347
|
+
},
|
|
348
|
+
executionProfile,
|
|
349
|
+
config.signal
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
const fetchOptions: RequestInit = {
|
|
353
|
+
method: 'POST',
|
|
354
|
+
headers: {
|
|
355
|
+
'Content-Type': 'application/json',
|
|
356
|
+
'User-Agent': 'LibreChat/1.0',
|
|
357
|
+
...addCodeApiExecutionProfileHeader(
|
|
358
|
+
resolvedAuthHeaders,
|
|
359
|
+
executionProfile
|
|
360
|
+
),
|
|
361
|
+
},
|
|
362
|
+
body: JSON.stringify(postData),
|
|
363
|
+
signal: config.signal,
|
|
364
|
+
};
|
|
280
365
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
366
|
+
const proxyAgent = resolveFetchProxyAgent(execEndpoint);
|
|
367
|
+
if (proxyAgent != null) {
|
|
368
|
+
fetchOptions.agent = proxyAgent;
|
|
369
|
+
}
|
|
370
|
+
const response = await fetch(execEndpoint, fetchOptions);
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
throw new CodeApiRequestError(
|
|
373
|
+
await buildCodeApiHttpErrorMessage('POST', execEndpoint, response, {
|
|
374
|
+
profile: executionProfile,
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
result = (await response.json()) as Partial<t.ExecuteResult> &
|
|
379
|
+
t.ProgrammaticExecutionResponse;
|
|
284
380
|
}
|
|
285
|
-
|
|
286
|
-
if (!response.ok) {
|
|
381
|
+
if (hasWorkspace && result.status !== 'completed') {
|
|
287
382
|
throw new CodeApiRequestError(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
383
|
+
buildCodeApiExecutionErrorMessage(
|
|
384
|
+
result as t.ProgrammaticExecutionResponse
|
|
385
|
+
)
|
|
291
386
|
);
|
|
292
387
|
}
|
|
293
|
-
|
|
294
|
-
const result: t.ExecuteResult = await response.json();
|
|
295
388
|
let formattedOutput = '';
|
|
296
|
-
if (result.stdout) {
|
|
389
|
+
if (typeof result.stdout === 'string' && result.stdout.length > 0) {
|
|
297
390
|
formattedOutput += `stdout:\n${result.stdout}\n`;
|
|
298
391
|
} else {
|
|
299
392
|
formattedOutput += emptyOutputMessage;
|
|
300
393
|
}
|
|
301
|
-
if (result.stderr
|
|
394
|
+
if (typeof result.stderr === 'string' && result.stderr.length > 0) {
|
|
395
|
+
formattedOutput += `stderr:\n${result.stderr}\n`;
|
|
396
|
+
}
|
|
302
397
|
|
|
303
398
|
const outputWithReminder = appendTmpScratchReminder(
|
|
304
399
|
formattedOutput,
|
|
305
|
-
command
|
|
400
|
+
command,
|
|
401
|
+
hasWorkspace ? ATTACHED_BASH_TMP_REMINDER : undefined
|
|
306
402
|
);
|
|
307
403
|
const artifactDelivery = normalizeArtifactDeliveryFailure(
|
|
308
404
|
result.artifact_delivery
|
|
@@ -312,6 +408,10 @@ function createBashExecutionTool(
|
|
|
312
408
|
artifactDelivery
|
|
313
409
|
);
|
|
314
410
|
const hasFiles = result.files != null && result.files.length > 0;
|
|
411
|
+
const deletionEcho =
|
|
412
|
+
result.deleted_files != null
|
|
413
|
+
? { deleted_files: result.deleted_files }
|
|
414
|
+
: {};
|
|
315
415
|
const runtimeEcho =
|
|
316
416
|
result.runtime_session_id != null
|
|
317
417
|
? {
|
|
@@ -320,7 +420,15 @@ function createBashExecutionTool(
|
|
|
320
420
|
}
|
|
321
421
|
: {};
|
|
322
422
|
return [
|
|
323
|
-
|
|
423
|
+
hasWorkspace
|
|
424
|
+
? appendExecutionArtifactFileSummary(
|
|
425
|
+
outputWithDeliveryWarning,
|
|
426
|
+
result.files
|
|
427
|
+
)
|
|
428
|
+
: appendCodeSessionFileSummary(
|
|
429
|
+
outputWithDeliveryWarning,
|
|
430
|
+
result.files
|
|
431
|
+
),
|
|
324
432
|
(hasFiles
|
|
325
433
|
? {
|
|
326
434
|
session_id: result.session_id,
|
|
@@ -328,6 +436,7 @@ function createBashExecutionTool(
|
|
|
328
436
|
...(artifactDelivery != null
|
|
329
437
|
? { artifact_delivery: artifactDelivery }
|
|
330
438
|
: {}),
|
|
439
|
+
...deletionEcho,
|
|
331
440
|
...runtimeEcho,
|
|
332
441
|
}
|
|
333
442
|
: {
|
|
@@ -335,6 +444,7 @@ function createBashExecutionTool(
|
|
|
335
444
|
...(artifactDelivery != null
|
|
336
445
|
? { artifact_delivery: artifactDelivery }
|
|
337
446
|
: {}),
|
|
447
|
+
...deletionEcho,
|
|
338
448
|
...runtimeEcho,
|
|
339
449
|
}) satisfies t.CodeExecutionArtifact,
|
|
340
450
|
];
|
|
@@ -343,15 +453,21 @@ function createBashExecutionTool(
|
|
|
343
453
|
normalizeCodeApiRequestError(error).message,
|
|
344
454
|
command
|
|
345
455
|
);
|
|
346
|
-
throw new CodeApiRequestError(
|
|
456
|
+
throw new CodeApiRequestError(
|
|
457
|
+
`Execution error:\n\n${messageWithReminder}`
|
|
458
|
+
);
|
|
347
459
|
}
|
|
348
460
|
},
|
|
349
461
|
{
|
|
350
462
|
name: BashExecutionToolName,
|
|
351
463
|
description: buildBashExecutionToolDescription({
|
|
352
464
|
statefulSessions: params?.statefulSessions,
|
|
465
|
+
attachedWorkspace: hasWorkspace,
|
|
466
|
+
}),
|
|
467
|
+
schema: buildBashExecutionToolSchema({
|
|
468
|
+
statefulSessions: params?.statefulSessions,
|
|
469
|
+
attachedWorkspace: hasWorkspace,
|
|
353
470
|
}),
|
|
354
|
-
schema: buildBashExecutionToolSchema(params ?? undefined),
|
|
355
471
|
responseFormat: Constants.CONTENT_AND_ARTIFACT,
|
|
356
472
|
}
|
|
357
473
|
);
|
|
@@ -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 {
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
formatCompletedResponse,
|
|
33
34
|
} from './ProgrammaticToolCalling';
|
|
34
35
|
import { logCodeApiDiagnostic } from '@/tools/diagnostics';
|
|
36
|
+
import { resolveAttachedWorkspaceInstanceId } from '@/tools/workspaceIdentity';
|
|
35
37
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
36
38
|
import { Constants } from '@/common';
|
|
37
39
|
|
|
@@ -44,6 +46,11 @@ config();
|
|
|
44
46
|
const DEFAULT_MAX_ROUND_TRIPS = 20;
|
|
45
47
|
const DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();
|
|
46
48
|
const BASH_LAST_BACKGROUND_PID_GUARD = ': &\nwait "$!"';
|
|
49
|
+
const CODE_API_WORKSPACE_HEADER = 'X-LibreChat-Code-Workspace-ID';
|
|
50
|
+
const ATTACHED_BASH_DATA_DIRECTORY = '"${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}"';
|
|
51
|
+
const ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE =
|
|
52
|
+
`Use ${ATTACHED_BASH_DATA_DIRECTORY} for injected files and generated artifacts. ` +
|
|
53
|
+
'The directory is execution-scoped; the selected workspace is the persistent project root.';
|
|
47
54
|
|
|
48
55
|
/** Bash reserved words that get `_tool` suffix when used as function names */
|
|
49
56
|
const BASH_RESERVED = new Set([
|
|
@@ -81,6 +88,11 @@ Each call is a fresh bash shell. Variables and state do NOT persist between call
|
|
|
81
88
|
You MUST complete your entire workflow in ONE code block.
|
|
82
89
|
DO NOT split work across multiple calls expecting to reuse variables.`;
|
|
83
90
|
|
|
91
|
+
const ATTACHED_WORKSPACE_WARNING = `ATTACHED WORKSPACE EXECUTION:
|
|
92
|
+
- Commands start in the selected persistent workspace; project file changes persist between calls.
|
|
93
|
+
- Each sandbox run is a fresh process, so shell variables, background processes, and temporary execution data do not persist.
|
|
94
|
+
- Injected files and generated artifacts use \${LIBRECHAT_CODE_DATA_DIR:-/mnt/data}; do not copy them into the project unless the task requires it.`;
|
|
95
|
+
|
|
84
96
|
const CORE_RULES = `Rules:
|
|
85
97
|
- One call: state does not persist
|
|
86
98
|
- Tools are pre-defined as bash functions—DO NOT redefine them
|
|
@@ -107,6 +119,30 @@ Example (Parallel calls):
|
|
|
107
119
|
echo "SF: $(jq -r . /mnt/data/sf.json)"
|
|
108
120
|
echo "NY: $(jq -r . /mnt/data/ny.json)"`;
|
|
109
121
|
|
|
122
|
+
const ATTACHED_CORE_RULES = `Rules:
|
|
123
|
+
- One call: process state does not persist; project files do
|
|
124
|
+
- Tools are pre-defined as bash functions—DO NOT redefine them
|
|
125
|
+
- Each tool function accepts a JSON string argument
|
|
126
|
+
- Resolve tool calls into variables before changing project files; do not redirect a tool call directly into the project
|
|
127
|
+
- Set data_dir=${ATTACHED_BASH_DATA_DIRECTORY}; save generated artifacts there, and write durable project files relative to the working directory
|
|
128
|
+
- Tool stdout is normalized to one compact JSON value when possible; parse saved stdout once, then use fromjson? // . only for JSON-string fields
|
|
129
|
+
- Only echo/printf output returns to the model
|
|
130
|
+
- ${ATTACHED_BASH_ARTIFACT_PATH_GUIDANCE}
|
|
131
|
+
- ${BASH_SHELL_GUIDANCE}
|
|
132
|
+
- timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;
|
|
133
|
+
|
|
134
|
+
const ATTACHED_EXAMPLES = `Example (Complete workflow in one call):
|
|
135
|
+
data=$(query_database '{"sql": "SELECT * FROM users"}')
|
|
136
|
+
echo "$data" | jq '.[] | .name'
|
|
137
|
+
|
|
138
|
+
Example (Parallel calls):
|
|
139
|
+
data_dir=${ATTACHED_BASH_DATA_DIRECTORY}
|
|
140
|
+
{ sf=$(web_search '{"query": "SF weather"}'); printf '%s\n' "$sf" > "$data_dir/sf.json"; } &
|
|
141
|
+
{ ny=$(web_search '{"query": "NY weather"}'); printf '%s\n' "$ny" > "$data_dir/ny.json"; } &
|
|
142
|
+
wait
|
|
143
|
+
echo "SF: $(jq -r . "$data_dir/sf.json")"
|
|
144
|
+
echo "NY: $(jq -r . "$data_dir/ny.json")"`;
|
|
145
|
+
|
|
110
146
|
const CODE_PARAM_DESCRIPTION = `Bash code that calls tools programmatically. Tools are available as bash functions.
|
|
111
147
|
|
|
112
148
|
${STATELESS_WARNING}
|
|
@@ -175,7 +211,7 @@ export const BashProgrammaticToolCallingDefinition = {
|
|
|
175
211
|
schema: BashProgrammaticToolCallingSchema,
|
|
176
212
|
} as const;
|
|
177
213
|
|
|
178
|
-
function prepareBashProgrammaticCode(code: string): string {
|
|
214
|
+
export function prepareBashProgrammaticCode(code: string): string {
|
|
179
215
|
/* The Code API's generated Bash wrapper reads `$!` after user code. A user
|
|
180
216
|
* `set -u` makes that expansion fail when no background process has run.
|
|
181
217
|
* Seed and reap a no-op job before user code so strict mode remains active
|
|
@@ -326,7 +362,42 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
326
362
|
const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
|
|
327
363
|
const proxy = initParams.proxy ?? process.env.PROXY;
|
|
328
364
|
const debug = initParams.debug ?? process.env.BASH_PTC_DEBUG === 'true';
|
|
365
|
+
const workspaceId = initParams.workspaceId?.trim();
|
|
366
|
+
const hasWorkspace = workspaceId != null && workspaceId !== '';
|
|
367
|
+
if (
|
|
368
|
+
hasWorkspace &&
|
|
369
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(workspaceId)
|
|
370
|
+
) {
|
|
371
|
+
throw new Error('Invalid attached workspace identifier');
|
|
372
|
+
}
|
|
373
|
+
const workspaceInstanceId = resolveAttachedWorkspaceInstanceId(
|
|
374
|
+
initParams.workspaceInstanceId,
|
|
375
|
+
hasWorkspace
|
|
376
|
+
);
|
|
377
|
+
const requestAuthHeaders: t.CodeApiAuthHeaders = hasWorkspace
|
|
378
|
+
? async (): Promise<t.CodeApiAuthHeaderMap> => ({
|
|
379
|
+
...(await resolveCodeApiAuthHeaders(initParams.authHeaders)),
|
|
380
|
+
[CODE_API_WORKSPACE_HEADER]: workspaceId,
|
|
381
|
+
})
|
|
382
|
+
: (initParams.authHeaders ?? {});
|
|
329
383
|
const EXEC_ENDPOINT = buildCodeApiEndpoint(baseUrl, 'exec/programmatic');
|
|
384
|
+
const description = hasWorkspace
|
|
385
|
+
? BashProgrammaticToolCallingDescription.replace(
|
|
386
|
+
STATELESS_WARNING,
|
|
387
|
+
ATTACHED_WORKSPACE_WARNING
|
|
388
|
+
)
|
|
389
|
+
.replace(CORE_RULES, ATTACHED_CORE_RULES)
|
|
390
|
+
.replace(EXAMPLES, ATTACHED_EXAMPLES)
|
|
391
|
+
: BashProgrammaticToolCallingDescription;
|
|
392
|
+
const schema = createBashProgrammaticToolCallingSchema(maxRunTimeoutMs);
|
|
393
|
+
if (hasWorkspace) {
|
|
394
|
+
schema.properties.code.description = CODE_PARAM_DESCRIPTION.replace(
|
|
395
|
+
STATELESS_WARNING,
|
|
396
|
+
ATTACHED_WORKSPACE_WARNING
|
|
397
|
+
)
|
|
398
|
+
.replace(CORE_RULES, ATTACHED_CORE_RULES)
|
|
399
|
+
.replace(EXAMPLES, ATTACHED_EXAMPLES);
|
|
400
|
+
}
|
|
330
401
|
|
|
331
402
|
return tool(
|
|
332
403
|
async (rawParams, config) => {
|
|
@@ -449,7 +520,7 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
449
520
|
|
|
450
521
|
/* Raw `code`, not `preparedCode`: the `$!` guard exists for the
|
|
451
522
|
* programmatic replay wrapper, which plain `/exec` never applies. */
|
|
452
|
-
if (needsNoTools) {
|
|
523
|
+
if (needsNoTools && !hasWorkspace) {
|
|
453
524
|
return await runPlainExecution({
|
|
454
525
|
baseUrl,
|
|
455
526
|
lang: 'bash',
|
|
@@ -459,7 +530,7 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
459
530
|
files,
|
|
460
531
|
runtimeSessionHint,
|
|
461
532
|
proxy,
|
|
462
|
-
authHeaders:
|
|
533
|
+
authHeaders: requestAuthHeaders,
|
|
463
534
|
executionProfile: initParams.executionProfile,
|
|
464
535
|
});
|
|
465
536
|
}
|
|
@@ -472,14 +543,18 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
472
543
|
tools: effectiveTools,
|
|
473
544
|
session_id,
|
|
474
545
|
timeout,
|
|
546
|
+
...(workspaceInstanceId != null && workspaceInstanceId !== ''
|
|
547
|
+
? { workspace_instance_id: workspaceInstanceId }
|
|
548
|
+
: {}),
|
|
475
549
|
...(files && files.length > 0 ? { files } : {}),
|
|
476
550
|
...(runtimeSessionHint != null
|
|
477
551
|
? { runtime_session_hint: runtimeSessionHint }
|
|
478
552
|
: {}),
|
|
479
553
|
},
|
|
480
554
|
proxy,
|
|
481
|
-
|
|
482
|
-
initParams.executionProfile
|
|
555
|
+
requestAuthHeaders,
|
|
556
|
+
initParams.executionProfile,
|
|
557
|
+
config.signal
|
|
483
558
|
);
|
|
484
559
|
|
|
485
560
|
// ====================================================================
|
|
@@ -519,8 +594,9 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
519
594
|
tool_results: toolResults,
|
|
520
595
|
},
|
|
521
596
|
proxy,
|
|
522
|
-
|
|
523
|
-
initParams.executionProfile
|
|
597
|
+
requestAuthHeaders,
|
|
598
|
+
initParams.executionProfile,
|
|
599
|
+
config.signal
|
|
524
600
|
);
|
|
525
601
|
}
|
|
526
602
|
|
|
@@ -529,7 +605,11 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
529
605
|
// ====================================================================
|
|
530
606
|
|
|
531
607
|
if (response.status === 'completed') {
|
|
532
|
-
return formatCompletedResponse(
|
|
608
|
+
return formatCompletedResponse(
|
|
609
|
+
response,
|
|
610
|
+
code,
|
|
611
|
+
hasWorkspace ? 'execution' : 'session'
|
|
612
|
+
);
|
|
533
613
|
}
|
|
534
614
|
|
|
535
615
|
if (response.status === 'error') {
|
|
@@ -551,8 +631,8 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
551
631
|
},
|
|
552
632
|
{
|
|
553
633
|
name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
|
|
554
|
-
description
|
|
555
|
-
schema
|
|
634
|
+
description,
|
|
635
|
+
schema,
|
|
556
636
|
responseFormat: Constants.CONTENT_AND_ARTIFACT,
|
|
557
637
|
}
|
|
558
638
|
);
|
|
@@ -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(
|
|
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${
|
|
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
|
];
|
|
@@ -15,6 +15,8 @@ const IMAGE_FILE_EXTENSIONS = new Set([
|
|
|
15
15
|
|
|
16
16
|
const CODE_SESSION_FILE_SUMMARY_PATTERN =
|
|
17
17
|
/^Generated files:\nSession files: \d+ persisted file\(s\) are available in \/mnt\/data, including \d+ image\(s\)\. Use known \/mnt\/data paths directly in later code-tool calls\. The app displays files\/images automatically; do not invent download links or wrap generated images in Markdown\.$/;
|
|
18
|
+
const EXECUTION_ARTIFACT_FILE_SUMMARY_PATTERN =
|
|
19
|
+
/^Generated files:\nExecution artifacts: \d+ file\(s\), including \d+ image\(s\), were delivered to the app\. They are not retained in the attached project; write durable files to the project root when later commands must reuse them\. The app displays files\/images automatically; do not invent download links or wrap generated images in Markdown\.$/;
|
|
18
20
|
|
|
19
21
|
function getFileExtension(name: string): string {
|
|
20
22
|
const lastSlash = name.lastIndexOf('/');
|
|
@@ -53,7 +55,12 @@ export function stripCodeSessionFileSummary(output: string): string {
|
|
|
53
55
|
const beforeSummary = output.slice(0, summaryStart);
|
|
54
56
|
if (beforeSummary !== '' && !beforeSummary.endsWith('\n\n')) return output;
|
|
55
57
|
const maybeSummary = output.slice(summaryStart);
|
|
56
|
-
if (
|
|
58
|
+
if (
|
|
59
|
+
!CODE_SESSION_FILE_SUMMARY_PATTERN.test(maybeSummary) &&
|
|
60
|
+
!EXECUTION_ARTIFACT_FILE_SUMMARY_PATTERN.test(maybeSummary)
|
|
61
|
+
) {
|
|
62
|
+
return output;
|
|
63
|
+
}
|
|
57
64
|
return beforeSummary.trimEnd();
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -78,3 +85,20 @@ export function appendCodeSessionFileSummary(
|
|
|
78
85
|
|
|
79
86
|
return `${output.trimEnd()}\n\n${summary}`.trim();
|
|
80
87
|
}
|
|
88
|
+
|
|
89
|
+
/** Attached-worker artifacts are delivered out of execution-private scratch,
|
|
90
|
+
* not retained in the selected project or a reusable `/mnt/data` session. */
|
|
91
|
+
export function appendExecutionArtifactFileSummary(
|
|
92
|
+
output: string,
|
|
93
|
+
files: t.FileRefs | undefined
|
|
94
|
+
): string {
|
|
95
|
+
const generatedFiles = files?.filter(isGeneratedFile) ?? [];
|
|
96
|
+
if (generatedFiles.length === 0) return output.trim();
|
|
97
|
+
const imageCount = generatedFiles.filter(isImageFile).length;
|
|
98
|
+
const summary =
|
|
99
|
+
'Generated files:\n' +
|
|
100
|
+
`Execution artifacts: ${generatedFiles.length} file(s), including ${imageCount} image(s), were delivered to the app. ` +
|
|
101
|
+
'They are not retained in the attached project; write durable files to the project root when later commands must reuse them. ' +
|
|
102
|
+
'The app displays files/images automatically; do not invent download links or wrap generated images in Markdown.';
|
|
103
|
+
return `${output.trimEnd()}\n\n${summary}`.trim();
|
|
104
|
+
}
|