mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 10:02:41 +00:00
🛻 feat: Route Bash Through Attached Workspaces (#15546)
* feat(code): Route bash through attached workspaces * fix(code): harden attached bash schema adapter
This commit is contained in:
parent
2656f44c03
commit
fddf7e31f2
9 changed files with 485 additions and 18 deletions
|
|
@ -45,6 +45,7 @@ const {
|
|||
AGENT_EXPECTED_MCP_TOOLS_UNAVAILABLE,
|
||||
isFatalAgentInitializationError,
|
||||
codeExecutionAuthHeaders,
|
||||
createAttachedWorkspaceBashTool,
|
||||
resolveCodeExecutionContext,
|
||||
resolveCallerCapabilityProjectionSnapshot,
|
||||
LIST_WORKSPACE_FILES_TOOL_NAME,
|
||||
|
|
@ -2122,14 +2123,21 @@ async function loadToolsForExecution({
|
|||
}
|
||||
if (isBashTool) {
|
||||
try {
|
||||
const bashTool = createBashExecutionTool({
|
||||
authHeaders: () =>
|
||||
codeExecutionAuthHeaders(
|
||||
(bridgeWorkerId) => getCodeApiAuthHeaders(req, bridgeWorkerId),
|
||||
codeExecutionContext,
|
||||
),
|
||||
...codeExecutionContext,
|
||||
});
|
||||
const authHeaders = () =>
|
||||
codeExecutionAuthHeaders(
|
||||
(bridgeWorkerId) => getCodeApiAuthHeaders(req, bridgeWorkerId),
|
||||
codeExecutionContext,
|
||||
);
|
||||
const bashTool =
|
||||
codeExecutionContext.environmentType === 'attached'
|
||||
? createAttachedWorkspaceBashTool({
|
||||
authHeaders,
|
||||
baseUrl: codeExecutionContext.baseUrl,
|
||||
})
|
||||
: createBashExecutionTool({
|
||||
authHeaders,
|
||||
...codeExecutionContext,
|
||||
});
|
||||
allLoadedTools.push(bashTool);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const mockGetMCPServerTools = jest.fn();
|
|||
const mockGetCachedTools = jest.fn();
|
||||
const mockSendEvent = jest.fn();
|
||||
const mockEmitChunk = jest.fn();
|
||||
const mockCreateAttachedWorkspaceBashTool = jest.fn(() => ({ name: AgentConstants.BASH_TOOL }));
|
||||
const mockResolveCodeExecutionContext = jest.fn(
|
||||
({ statefulSessions, environment, userId, agentId, conversationId }) => {
|
||||
if (!statefulSessions) {
|
||||
|
|
@ -80,6 +81,7 @@ jest.mock('@librechat/api', () => ({
|
|||
emitChunk: (...args) => mockEmitChunk(...args),
|
||||
},
|
||||
resolveCodeExecutionContext: (...args) => mockResolveCodeExecutionContext(...args),
|
||||
createAttachedWorkspaceBashTool: (...args) => mockCreateAttachedWorkspaceBashTool(...args),
|
||||
}));
|
||||
|
||||
const mockLoadToolsUtil = jest.fn();
|
||||
|
|
@ -2631,6 +2633,47 @@ describe('ToolService - Action Capability Gating', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('loads bash execution through the attached worker transport', async () => {
|
||||
const capabilities = [
|
||||
AgentCapabilities.tools,
|
||||
AgentCapabilities.execute_code,
|
||||
AgentCapabilities.stateful_code_sessions,
|
||||
];
|
||||
const req = createMockReq(capabilities);
|
||||
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
||||
mockResolveCodeExecutionContext.mockReturnValueOnce({
|
||||
baseUrl: 'http://attached-code.test/v1',
|
||||
codeSessionKey: 'execute_code:stateful:attached',
|
||||
executionProfile: 'stateful',
|
||||
statefulSessions: true,
|
||||
environmentType: 'attached',
|
||||
bridgeWorkerId: 'worker-abc',
|
||||
});
|
||||
const toolRegistry = new Map([
|
||||
[AgentConstants.BASH_TOOL, { name: AgentConstants.BASH_TOOL }],
|
||||
]);
|
||||
|
||||
const result = await loadToolsForExecution({
|
||||
req,
|
||||
res: {},
|
||||
agent: {
|
||||
id: 'attached-agent',
|
||||
tools: [Tools.execute_code],
|
||||
stateful_code_sessions: true,
|
||||
stateful_code_environment: 'agent-user',
|
||||
},
|
||||
toolNames: [AgentConstants.BASH_TOOL],
|
||||
toolRegistry,
|
||||
actionsEnabled: false,
|
||||
});
|
||||
|
||||
expect(mockCreateAttachedWorkspaceBashTool).toHaveBeenCalledWith({
|
||||
authHeaders: expect.any(Function),
|
||||
baseUrl: 'http://attached-code.test/v1',
|
||||
});
|
||||
expect(result.loadedTools).toContainEqual({ name: AgentConstants.BASH_TOOL });
|
||||
});
|
||||
|
||||
it('resolves stateful routing when handle_skill is the only requested tool', async () => {
|
||||
const capabilities = [
|
||||
AgentCapabilities.tools,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue