@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.
- package/dist/cjs/agents/AgentContext.cjs +1 -1
- package/dist/cjs/common/constants.cjs +2 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +18 -9
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hitl/approvalReview.cjs +1 -1
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/main.cjs +8 -6
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +1 -1
- package/dist/cjs/run.cjs +7 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +13 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +3 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +79 -27
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +47 -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 +176 -43
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +1 -0
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +0 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +120 -26
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolBatchReplay.cjs +4 -2
- package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +1 -1
- package/dist/esm/common/constants.mjs +2 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +17 -8
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hitl/approvalReview.mjs +1 -1
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -7
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/run.mjs +7 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +13 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +3 -1
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +81 -29
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +48 -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 +176 -43
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +1 -0
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +0 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +120 -26
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolBatchReplay.mjs +4 -2
- package/dist/esm/tools/toolBatchReplay.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/types/common/constants.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +3 -2
- package/dist/types/hooks/types.d.ts +3 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/summarization/node.d.ts +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 +6 -2
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +7 -4
- package/dist/types/tools/toolBatchReplay.d.ts +2 -0
- package/dist/types/types/graph.d.ts +28 -0
- package/dist/types/types/run.d.ts +2 -0
- package/dist/types/types/tools.d.ts +27 -3
- package/package.json +2 -2
- package/src/common/constants.ts +3 -0
- package/src/graphs/Graph.ts +32 -18
- package/src/hooks/types.ts +3 -0
- package/src/run.ts +30 -15
- package/src/stream.ts +27 -5
- package/src/summarization/node.ts +3 -0
- package/src/tools/BashExecutor.ts +143 -40
- package/src/tools/BashProgrammaticToolCalling.ts +82 -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 +665 -170
- package/src/tools/local/LocalProgrammaticToolCalling.ts +1 -0
- package/src/tools/subagent/SubagentExecutionRegistry.ts +0 -1
- package/src/tools/subagent/SubagentExecutor.ts +214 -55
- package/src/tools/toolBatchReplay.ts +23 -1
- package/src/types/graph.ts +37 -0
- package/src/types/run.ts +2 -0
- package/src/types/tools.ts +30 -3
- 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:
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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 ||
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
if (!response.ok) {
|
|
368
|
+
if (hasWorkspace && result.status !== 'completed') {
|
|
287
369
|
throw new CodeApiRequestError(
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
555
|
-
schema
|
|
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(
|
|
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
|
];
|