mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
🛡️ fix: Gate Bash PTC Capabilities (#13053)
This commit is contained in:
parent
030dc98a1d
commit
5bab22d236
6 changed files with 213 additions and 15 deletions
|
|
@ -546,6 +546,10 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
const areToolsEnabled = checkCapability(AgentCapabilities.tools);
|
||||
const actionsEnabled = checkCapability(AgentCapabilities.actions);
|
||||
const deferredToolsEnabled = checkCapability(AgentCapabilities.deferred_tools);
|
||||
const programmaticToolsEnabled = enabledCapabilities.has(AgentCapabilities.programmatic_tools);
|
||||
const codeExecutionEnabled =
|
||||
agent.tools?.includes(Tools.execute_code) === true &&
|
||||
enabledCapabilities.has(AgentCapabilities.execute_code);
|
||||
|
||||
const filteredTools = agent.tools?.filter((tool) => {
|
||||
if (tool === Tools.file_search) {
|
||||
|
|
@ -721,6 +725,8 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
tools: filteredTools,
|
||||
toolOptions: agent.tool_options,
|
||||
deferredToolsEnabled,
|
||||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
},
|
||||
{
|
||||
isBuiltInTool,
|
||||
|
|
@ -775,6 +781,8 @@ async function loadToolDefinitionsWrapper({ req, res, agent, streamId = null, to
|
|||
tools: filteredTools,
|
||||
toolOptions: agent.tool_options,
|
||||
deferredToolsEnabled,
|
||||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
},
|
||||
{
|
||||
isBuiltInTool,
|
||||
|
|
@ -1003,6 +1011,10 @@ async function loadAgentTools({
|
|||
|
||||
/** Build tool registry from MCP tools and create PTC/tool search tools if configured */
|
||||
const deferredToolsEnabled = checkCapability(AgentCapabilities.deferred_tools);
|
||||
const programmaticToolsEnabled = enabledCapabilities.has(AgentCapabilities.programmatic_tools);
|
||||
const codeExecutionEnabled =
|
||||
agent.tools?.includes(Tools.execute_code) === true &&
|
||||
enabledCapabilities.has(AgentCapabilities.execute_code);
|
||||
const { toolRegistry, toolDefinitions, additionalTools, hasDeferredTools } =
|
||||
await buildToolClassification({
|
||||
loadedTools,
|
||||
|
|
@ -1010,6 +1022,8 @@ async function loadAgentTools({
|
|||
agentId: agent.id,
|
||||
agentToolOptions: agent.tool_options,
|
||||
deferredToolsEnabled,
|
||||
programmaticToolsEnabled,
|
||||
codeExecutionEnabled,
|
||||
authHeaders: () => getCodeApiAuthHeaders(req),
|
||||
});
|
||||
|
||||
|
|
@ -1256,17 +1270,26 @@ async function loadToolsForExecution({
|
|||
const allLoadedTools = [];
|
||||
const configurable = { userMCPAuthMap };
|
||||
|
||||
if (actionsEnabled === undefined) {
|
||||
const enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent?.id);
|
||||
actionsEnabled = enabledCapabilities.has(AgentCapabilities.actions);
|
||||
}
|
||||
|
||||
const isToolSearch = toolNames.includes(AgentConstants.TOOL_SEARCH);
|
||||
const ptcToolNames = [
|
||||
AgentConstants.BASH_PROGRAMMATIC_TOOL_CALLING,
|
||||
AgentConstants.PROGRAMMATIC_TOOL_CALLING,
|
||||
].filter((name) => toolNames.includes(name));
|
||||
const isPTC = ptcToolNames.length > 0;
|
||||
const isPTCRequested = ptcToolNames.length > 0;
|
||||
|
||||
let enabledCapabilities;
|
||||
if (actionsEnabled === undefined || isPTCRequested) {
|
||||
enabledCapabilities = await resolveAgentCapabilities(req, appConfig, agent?.id);
|
||||
}
|
||||
if (actionsEnabled === undefined) {
|
||||
actionsEnabled = enabledCapabilities.has(AgentCapabilities.actions);
|
||||
}
|
||||
|
||||
const isPTC =
|
||||
isPTCRequested &&
|
||||
enabledCapabilities.has(AgentCapabilities.programmatic_tools) &&
|
||||
enabledCapabilities.has(AgentCapabilities.execute_code) &&
|
||||
agent?.tools?.includes(Tools.execute_code) === true;
|
||||
|
||||
logger.debug(
|
||||
`[loadToolsForExecution] isToolSearch: ${isToolSearch}, toolRegistry: ${toolRegistry?.size ?? 'undefined'}`,
|
||||
|
|
|
|||
|
|
@ -300,14 +300,20 @@ describe('ToolService - Action Capability Gating', () => {
|
|||
const actionToolName = `get_weather${actionDelimiter}api_example_com`;
|
||||
const regularTool = Tools.web_search;
|
||||
|
||||
it('loads bash PTC under the legacy programmatic tool name for event-driven compatibility', async () => {
|
||||
const req = createMockReq([]);
|
||||
it('loads bash PTC under the legacy programmatic tool name when code capabilities are enabled', async () => {
|
||||
const capabilities = [
|
||||
AgentCapabilities.tools,
|
||||
AgentCapabilities.programmatic_tools,
|
||||
AgentCapabilities.execute_code,
|
||||
];
|
||||
const req = createMockReq(capabilities);
|
||||
const toolRegistry = new Map([['custom_tool', { name: 'custom_tool' }]]);
|
||||
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
||||
|
||||
const result = await loadToolsForExecution({
|
||||
req,
|
||||
res: {},
|
||||
agent: { id: 'agent_ptc' },
|
||||
agent: { id: 'agent_ptc', tools: [Tools.execute_code] },
|
||||
toolNames: [Constants.PROGRAMMATIC_TOOL_CALLING],
|
||||
toolRegistry,
|
||||
actionsEnabled: false,
|
||||
|
|
@ -320,6 +326,50 @@ describe('ToolService - Action Capability Gating', () => {
|
|||
expect(result.configurable.ptcToolMap.size).toBe(0);
|
||||
});
|
||||
|
||||
it('does not load PTC when programmatic tools capability is disabled', async () => {
|
||||
const capabilities = [AgentCapabilities.tools, AgentCapabilities.execute_code];
|
||||
const req = createMockReq(capabilities);
|
||||
const toolRegistry = new Map([['custom_tool', { name: 'custom_tool' }]]);
|
||||
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
||||
|
||||
const result = await loadToolsForExecution({
|
||||
req,
|
||||
res: {},
|
||||
agent: { id: 'agent_ptc', tools: [Tools.execute_code] },
|
||||
toolNames: [Constants.BASH_PROGRAMMATIC_TOOL_CALLING],
|
||||
toolRegistry,
|
||||
actionsEnabled: false,
|
||||
});
|
||||
|
||||
expect(result.loadedTools.map((tool) => tool.name)).toEqual([]);
|
||||
expect(result.configurable.toolRegistry).toBeUndefined();
|
||||
expect(result.configurable.ptcToolMap).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not load PTC when agent did not request execute_code', async () => {
|
||||
const capabilities = [
|
||||
AgentCapabilities.tools,
|
||||
AgentCapabilities.programmatic_tools,
|
||||
AgentCapabilities.execute_code,
|
||||
];
|
||||
const req = createMockReq(capabilities);
|
||||
const toolRegistry = new Map([['custom_tool', { name: 'custom_tool' }]]);
|
||||
mockGetEndpointsConfig.mockResolvedValue(createEndpointsConfig(capabilities));
|
||||
|
||||
const result = await loadToolsForExecution({
|
||||
req,
|
||||
res: {},
|
||||
agent: { id: 'agent_ptc', tools: [] },
|
||||
toolNames: [Constants.BASH_PROGRAMMATIC_TOOL_CALLING],
|
||||
toolRegistry,
|
||||
actionsEnabled: false,
|
||||
});
|
||||
|
||||
expect(result.loadedTools.map((tool) => tool.name)).toEqual([]);
|
||||
expect(result.configurable.toolRegistry).toBeUndefined();
|
||||
expect(result.configurable.ptcToolMap).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should skip action tool loading when actionsEnabled=false', async () => {
|
||||
const req = createMockReq([]);
|
||||
req.config = {};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue