diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index ca078429e3..0888f23cd5 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -113,6 +113,7 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { loadTools: async (toolNames, agentId) => { const ctx = agentToolContexts.get(agentId) ?? {}; logger.debug(`[ON_TOOL_EXECUTE] ctx found: ${!!ctx.userMCPAuthMap}, agent: ${ctx.agent?.id}`); + logger.debug(`[ON_TOOL_EXECUTE] toolRegistry size: ${ctx.toolRegistry?.size ?? 'undefined'}`); const result = await loadToolsForExecution({ req, @@ -208,6 +209,9 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => { /** Store primary agent's tool context for ON_TOOL_EXECUTE callback */ logger.debug(`[initializeClient] Storing tool context for agentId: ${primaryConfig.id}`); + logger.debug( + `[initializeClient] toolRegistry size: ${primaryConfig.toolRegistry?.size ?? 'undefined'}`, + ); agentToolContexts.set(primaryConfig.id, { agent: primaryAgent, toolRegistry: primaryConfig.toolRegistry, diff --git a/api/server/services/ToolService.js b/api/server/services/ToolService.js index c9d8ff25f4..eb6dd71e23 100644 --- a/api/server/services/ToolService.js +++ b/api/server/services/ToolService.js @@ -4,6 +4,7 @@ const { StepTypes, GraphEvents, createToolSearch, + Constants: AgentConstants, createProgrammaticToolCallingTool, } = require('@librechat/agents'); const { logger } = require('@librechat/data-schemas'); @@ -1055,8 +1056,12 @@ async function loadToolsForExecution({ const allLoadedTools = []; const configurable = { userMCPAuthMap }; - const isToolSearch = toolNames.includes(Constants.TOOL_SEARCH); - const isPTC = toolNames.includes(Constants.PROGRAMMATIC_TOOL_CALLING); + const isToolSearch = toolNames.includes(AgentConstants.TOOL_SEARCH); + const isPTC = toolNames.includes(AgentConstants.PROGRAMMATIC_TOOL_CALLING); + + logger.debug( + `[loadToolsForExecution] isToolSearch: ${isToolSearch}, toolRegistry: ${toolRegistry?.size ?? 'undefined'}`, + ); if (isToolSearch && toolRegistry) { const toolSearchTool = createToolSearch({ @@ -1087,7 +1092,10 @@ async function loadToolsForExecution({ } } - const specialToolNames = new Set([Constants.TOOL_SEARCH, Constants.PROGRAMMATIC_TOOL_CALLING]); + const specialToolNames = new Set([ + AgentConstants.TOOL_SEARCH, + AgentConstants.PROGRAMMATIC_TOOL_CALLING, + ]); let ptcOrchestratedToolNames = []; if (isPTC && toolRegistry) { @@ -1149,7 +1157,7 @@ async function loadToolsForExecution({ if (isPTC && allLoadedTools.length > 0) { const ptcToolMap = new Map(); for (const tool of allLoadedTools) { - if (tool.name && tool.name !== Constants.PROGRAMMATIC_TOOL_CALLING) { + if (tool.name && tool.name !== AgentConstants.PROGRAMMATIC_TOOL_CALLING) { ptcToolMap.set(tool.name, tool); } } diff --git a/packages/api/src/agents/run.ts b/packages/api/src/agents/run.ts index f3f776367b..8544d01300 100644 --- a/packages/api/src/agents/run.ts +++ b/packages/api/src/agents/run.ts @@ -1,4 +1,4 @@ -import { Run, Providers } from '@librechat/agents'; +import { Run, Providers, Constants } from '@librechat/agents'; import { providerEndpointMap, KnownEndpoints } from 'librechat-data-provider'; import type { BaseMessage } from '@langchain/core/messages'; import type { @@ -17,9 +17,6 @@ import type { Agent } from 'librechat-data-provider'; import type * as t from '~/types'; import { resolveHeaders, createSafeUser } from '~/utils/env'; -/** Tool search tool name constant */ -const TOOL_SEARCH_NAME = 'tool_search'; - /** Expected shape of JSON tool search results */ interface ToolSearchJsonResult { found?: number; @@ -91,7 +88,7 @@ export function extractDiscoveredToolsFromHistory(messages: BaseMessage[]): Set< } const name = (message as { name?: string }).name; - if (name !== TOOL_SEARCH_NAME) { + if (name !== Constants.TOOL_SEARCH) { continue; } diff --git a/packages/api/src/tools/classification.spec.ts b/packages/api/src/tools/classification.spec.ts index 4782be5b5d..2d6fe222ec 100644 --- a/packages/api/src/tools/classification.spec.ts +++ b/packages/api/src/tools/classification.spec.ts @@ -1,75 +1,15 @@ -import { - parseToolList, - toolMatchesPatterns, - getServerNameFromTool, - buildToolRegistryFromEnv, - buildToolRegistryFromAgentOptions, - buildToolClassification, - agentHasDeferredTools, - agentHasProgrammaticTools, - isAgentAllowedForClassification, -} from './classification'; -import type { ToolDefinition, LCToolRegistry } from './classification'; -import type { GenericTool } from '@librechat/agents'; import type { AgentToolOptions } from 'librechat-data-provider'; +import type { GenericTool } from '@librechat/agents'; +import type { LCToolRegistry } from './classification'; +import { + buildToolRegistryFromAgentOptions, + agentHasProgrammaticTools, + buildToolClassification, + getServerNameFromTool, + agentHasDeferredTools, +} from './classification'; describe('classification.ts', () => { - const originalEnv = process.env; - - beforeEach(() => { - process.env = { ...originalEnv }; - // Clear classification-related env vars - delete process.env.TOOL_PROGRAMMATIC_ONLY; - delete process.env.TOOL_PROGRAMMATIC_ONLY_EXCLUDE; - delete process.env.TOOL_DUAL_CONTEXT; - delete process.env.TOOL_DUAL_CONTEXT_EXCLUDE; - delete process.env.TOOL_DEFERRED; - delete process.env.TOOL_DEFERRED_EXCLUDE; - delete process.env.TOOL_CLASSIFICATION_AGENT_IDS; - delete process.env.TOOL_CLASSIFICATION_FROM_ENV; - }); - - afterEach(() => { - process.env = originalEnv; - }); - - describe('parseToolList', () => { - it('should return empty set for undefined input', () => { - const result = parseToolList(undefined); - expect(result.size).toBe(0); - }); - - it('should return empty set for empty string', () => { - const result = parseToolList(''); - expect(result.size).toBe(0); - }); - - it('should return empty set for whitespace-only string', () => { - const result = parseToolList(' '); - expect(result.size).toBe(0); - }); - - it('should parse comma-separated tool names', () => { - const result = parseToolList('tool1,tool2,tool3'); - expect(result.size).toBe(3); - expect(result.has('tool1')).toBe(true); - expect(result.has('tool2')).toBe(true); - expect(result.has('tool3')).toBe(true); - }); - - it('should trim whitespace from tool names', () => { - const result = parseToolList(' tool1 , tool2 '); - expect(result.size).toBe(2); - expect(result.has('tool1')).toBe(true); - expect(result.has('tool2')).toBe(true); - }); - - it('should filter out empty entries', () => { - const result = parseToolList('tool1,,tool2,,,tool3'); - expect(result.size).toBe(3); - }); - }); - describe('getServerNameFromTool', () => { it('should extract server name from MCP tool name', () => { const result = getServerNameFromTool('list_files_mcp_Google-Workspace'); @@ -87,100 +27,9 @@ describe('classification.ts', () => { }); }); - describe('toolMatchesPatterns', () => { - it('should return true for exact match', () => { - const patterns = new Set(['tool1', 'tool2']); - const excludes = new Set(); - expect(toolMatchesPatterns('tool1', patterns, excludes)).toBe(true); - }); - - it('should return false for non-matching tool', () => { - const patterns = new Set(['tool1', 'tool2']); - const excludes = new Set(); - expect(toolMatchesPatterns('tool3', patterns, excludes)).toBe(false); - }); - - it('should return false when tool is in excludes', () => { - const patterns = new Set(['tool1', 'tool2']); - const excludes = new Set(['tool1']); - expect(toolMatchesPatterns('tool1', patterns, excludes)).toBe(false); - }); - - it('should match server-wide pattern', () => { - const patterns = new Set(['sys__all__sys_mcp_Google-Workspace']); - const excludes = new Set(); - expect(toolMatchesPatterns('list_files_mcp_Google-Workspace', patterns, excludes)).toBe(true); - }); - - it('should respect excludes for server-wide patterns', () => { - const patterns = new Set(['sys__all__sys_mcp_Google-Workspace']); - const excludes = new Set(['list_files_mcp_Google-Workspace']); - expect(toolMatchesPatterns('list_files_mcp_Google-Workspace', patterns, excludes)).toBe( - false, - ); - }); - }); - - describe('buildToolRegistryFromEnv', () => { - it('should set defer_loading based on TOOL_DEFERRED env var', () => { - process.env.TOOL_DEFERRED = 'tool1,tool2'; - - const tools: ToolDefinition[] = [ - { name: 'tool1', description: 'Tool 1' }, - { name: 'tool2', description: 'Tool 2' }, - { name: 'tool3', description: 'Tool 3' }, - ]; - - const registry = buildToolRegistryFromEnv(tools); - - expect(registry.get('tool1')?.defer_loading).toBe(true); - expect(registry.get('tool2')?.defer_loading).toBe(true); - expect(registry.get('tool3')?.defer_loading).toBe(false); - }); - - it('should respect TOOL_DEFERRED_EXCLUDE', () => { - process.env.TOOL_DEFERRED = 'sys__all__sys_mcp_TestServer'; - process.env.TOOL_DEFERRED_EXCLUDE = 'tool2_mcp_TestServer'; - - const tools: ToolDefinition[] = [ - { name: 'tool1_mcp_TestServer', description: 'Tool 1' }, - { name: 'tool2_mcp_TestServer', description: 'Tool 2' }, - ]; - - const registry = buildToolRegistryFromEnv(tools); - - expect(registry.get('tool1_mcp_TestServer')?.defer_loading).toBe(true); - expect(registry.get('tool2_mcp_TestServer')?.defer_loading).toBe(false); - }); - - it('should set allowed_callers based on TOOL_PROGRAMMATIC_ONLY', () => { - process.env.TOOL_PROGRAMMATIC_ONLY = 'tool1'; - - const tools: ToolDefinition[] = [ - { name: 'tool1', description: 'Tool 1' }, - { name: 'tool2', description: 'Tool 2' }, - ]; - - const registry = buildToolRegistryFromEnv(tools); - - expect(registry.get('tool1')?.allowed_callers).toEqual(['code_execution']); - expect(registry.get('tool2')?.allowed_callers).toEqual(['direct']); - }); - - it('should set dual context callers based on TOOL_DUAL_CONTEXT', () => { - process.env.TOOL_DUAL_CONTEXT = 'tool1'; - - const tools: ToolDefinition[] = [{ name: 'tool1', description: 'Tool 1' }]; - - const registry = buildToolRegistryFromEnv(tools); - - expect(registry.get('tool1')?.allowed_callers).toEqual(['direct', 'code_execution']); - }); - }); - describe('buildToolRegistryFromAgentOptions', () => { it('should use agent tool options for defer_loading', () => { - const tools: ToolDefinition[] = [ + const tools = [ { name: 'tool1', description: 'Tool 1' }, { name: 'tool2', description: 'Tool 2' }, ]; @@ -197,7 +46,7 @@ describe('classification.ts', () => { }); it('should default defer_loading to false when not specified', () => { - const tools: ToolDefinition[] = [{ name: 'tool1', description: 'Tool 1' }]; + const tools = [{ name: 'tool1', description: 'Tool 1' }]; const agentToolOptions: AgentToolOptions = {}; @@ -207,7 +56,7 @@ describe('classification.ts', () => { }); it('should use agent allowed_callers when specified', () => { - const tools: ToolDefinition[] = [{ name: 'tool1', description: 'Tool 1' }]; + const tools = [{ name: 'tool1', description: 'Tool 1' }]; const agentToolOptions: AgentToolOptions = { tool1: { allowed_callers: ['code_execution'] }, @@ -217,6 +66,18 @@ describe('classification.ts', () => { expect(registry.get('tool1')?.allowed_callers).toEqual(['code_execution']); }); + + it('should default allowed_callers to direct when not specified', () => { + const tools = [{ name: 'tool1', description: 'Tool 1' }]; + + const agentToolOptions: AgentToolOptions = { + tool1: { defer_loading: true }, + }; + + const registry = buildToolRegistryFromAgentOptions(tools, agentToolOptions); + + expect(registry.get('tool1')?.allowed_callers).toEqual(['direct']); + }); }); describe('agentHasDeferredTools', () => { @@ -273,27 +134,6 @@ describe('classification.ts', () => { }); }); - describe('isAgentAllowedForClassification', () => { - it('should return true when TOOL_CLASSIFICATION_AGENT_IDS is not set', () => { - expect(isAgentAllowedForClassification('any-agent-id')).toBe(true); - }); - - it('should return true when agent is in allowed list', () => { - process.env.TOOL_CLASSIFICATION_AGENT_IDS = 'agent1,agent2,agent3'; - expect(isAgentAllowedForClassification('agent2')).toBe(true); - }); - - it('should return false when agent is not in allowed list', () => { - process.env.TOOL_CLASSIFICATION_AGENT_IDS = 'agent1,agent2'; - expect(isAgentAllowedForClassification('agent3')).toBe(false); - }); - - it('should return false when agentId is undefined and list is set', () => { - process.env.TOOL_CLASSIFICATION_AGENT_IDS = 'agent1'; - expect(isAgentAllowedForClassification(undefined)).toBe(false); - }); - }); - describe('buildToolClassification with deferredToolsEnabled', () => { const mockLoadAuthValues = jest.fn().mockResolvedValue({}); diff --git a/packages/api/src/tools/classification.ts b/packages/api/src/tools/classification.ts index b3b52cc632..2c65076f6f 100644 --- a/packages/api/src/tools/classification.ts +++ b/packages/api/src/tools/classification.ts @@ -1,25 +1,6 @@ /** - * @fileoverview Utility functions for building tool registries from environment variables. - * This is a temporary solution for tool classification until UI-based configuration is available. - * - * Environment Variables: - * - TOOL_PROGRAMMATIC_ONLY: Comma-separated tool names or server patterns (sys__all__sys_mcp_ServerName) - * - TOOL_PROGRAMMATIC_ONLY_EXCLUDE: Comma-separated tool names to exclude from programmatic only - * - TOOL_DUAL_CONTEXT: Comma-separated tool names or server patterns callable BOTH by LLM and PTC - * - TOOL_DUAL_CONTEXT_EXCLUDE: Comma-separated tool names to exclude from dual context - * - TOOL_DEFERRED: Comma-separated tool names or server patterns for deferred tools - * - TOOL_DEFERRED_EXCLUDE: Comma-separated tool names to exclude from deferred - * - TOOL_CLASSIFICATION_AGENT_IDS: Optional comma-separated agent IDs to restrict classification features - * - * Server patterns: Use `sys__all__sys_mcp_ServerName` to match all tools from an MCP server. - * Example: `sys__all__sys_mcp_Google-Workspace` matches all Google Workspace tools. - * - * Agent restriction: If TOOL_CLASSIFICATION_AGENT_IDS is set, only those agents will get - * PTC and tool search tools. If not set, all agents with matching tools get them. - * - * Smart enablement: PTC/tool search are only created if the agent has tools that actually - * match the classification patterns. An agent with no programmatic/deferred tools won't - * get PTC/tool search even if the env vars are set. + * @fileoverview Utility functions for building tool registries from agent tool_options. + * Tool classification (deferred_tools, allowed_callers) is configured via the agent UI. * * @module packages/api/src/tools/classification */ @@ -44,9 +25,6 @@ import type { export type { LCTool, LCToolRegistry, AllowedCaller, JsonSchemaType }; -/** Pattern prefix for matching all tools from an MCP server */ -const MCP_ALL_PATTERN = `${Constants.mcp_all}${Constants.mcp_delimiter}`; - export interface ToolDefinition { name: string; description?: string; @@ -55,23 +33,6 @@ export interface ToolDefinition { serverName?: string; } -/** - * Parses a comma-separated tool list from an environment variable. - * @param envValue - The environment variable value - * @returns Set of tool names or server patterns - */ -export function parseToolList(envValue: string | undefined): Set { - if (!envValue || envValue.trim() === '') { - return new Set(); - } - return new Set( - envValue - .split(',') - .map((s) => s.trim()) - .filter((s) => s.length > 0), - ); -} - /** * Extracts the MCP server name from a tool name. * Tool names follow the pattern: toolName_mcp_ServerName @@ -86,112 +47,8 @@ export function getServerNameFromTool(toolName: string): string | undefined { return undefined; } -/** - * Checks if a tool matches a set of patterns (tool names or server patterns). - * Supports both exact tool name matches and server-wide patterns like `mcp_all_mcp_ServerName`. - * - * @param toolName - The tool name to check - * @param patterns - Set of patterns (tool names or mcp_all_mcp_ServerName patterns) - * @param excludes - Set of tool names to exclude (takes precedence over patterns) - * @returns Whether the tool matches any pattern and is not excluded - */ -export function toolMatchesPatterns( - toolName: string, - patterns: Set, - excludes: Set, -): boolean { - if (excludes.has(toolName)) { - return false; - } - - if (patterns.has(toolName)) { - return true; - } - - const serverName = getServerNameFromTool(toolName); - if (serverName) { - const serverPattern = `${MCP_ALL_PATTERN}${serverName}`; - if (patterns.has(serverPattern)) { - return true; - } - } - - return false; -} - -/** - * Builds a tool registry from environment variables for the given tools. - * This is a temporary solution while UI-based configuration is being developed. - * - * Supports server-wide patterns using `mcp_all_mcp_ServerName` syntax. - * Exclusion env vars take precedence over inclusion patterns. - * - * Default behavior (if tool not listed in any env var): - * - allowed_callers: ['direct'] - * - defer_loading: false - * - * @param tools - Array of tool definitions - * @returns Map of tool name to tool definition with classification - * - * @example - * // Environment for server-wide configuration: - * // TOOL_PROGRAMMATIC_ONLY=mcp_all_mcp_Google-Workspace - * // TOOL_DEFERRED=mcp_all_mcp_Google-Workspace - * // TOOL_DEFERRED_EXCLUDE=list_spreadsheets_mcp_Google-Workspace,read_sheet_values_mcp_Google-Workspace - * - * @example - * // Environment for individual tools: - * // TOOL_PROGRAMMATIC_ONLY=get_expenses,get_team_members - * // TOOL_DUAL_CONTEXT=get_weather - * // TOOL_DEFERRED=generate_report - */ -export function buildToolRegistryFromEnv(tools: ToolDefinition[]): LCToolRegistry { - const programmaticOnly = parseToolList(process.env.TOOL_PROGRAMMATIC_ONLY); - const programmaticOnlyExclude = parseToolList(process.env.TOOL_PROGRAMMATIC_ONLY_EXCLUDE); - const dualContext = parseToolList(process.env.TOOL_DUAL_CONTEXT); - const dualContextExclude = parseToolList(process.env.TOOL_DUAL_CONTEXT_EXCLUDE); - const deferred = parseToolList(process.env.TOOL_DEFERRED); - const deferredExclude = parseToolList(process.env.TOOL_DEFERRED_EXCLUDE); - - const registry: LCToolRegistry = new Map(); - - for (const tool of tools) { - const { name, description, parameters } = tool; - - let allowed_callers: AllowedCaller[]; - - if (toolMatchesPatterns(name, programmaticOnly, programmaticOnlyExclude)) { - allowed_callers = ['code_execution']; - } else if (toolMatchesPatterns(name, dualContext, dualContextExclude)) { - allowed_callers = ['direct', 'code_execution']; - } else { - // Default: direct only (LLM can call, PTC cannot) - allowed_callers = ['direct']; - } - - const toolDef: LCTool = { - name, - allowed_callers, - defer_loading: toolMatchesPatterns(name, deferred, deferredExclude), - }; - - // Include description and parameters if available (needed for tool search and PTC stub generation) - if (description) { - toolDef.description = description; - } - if (parameters) { - toolDef.parameters = parameters; - } - - registry.set(name, toolDef); - } - - return registry; -} - /** * Builds a tool registry from agent-level tool_options. - * This takes precedence over environment variable configuration when provided. * * @param tools - Array of tool definitions * @param agentToolOptions - Per-tool configuration from the agent @@ -201,37 +58,24 @@ export function buildToolRegistryFromAgentOptions( tools: ToolDefinition[], agentToolOptions: AgentToolOptions, ): LCToolRegistry { - /** Fall back to env vars for tools not configured at agent level */ - const programmaticOnly = parseToolList(process.env.TOOL_PROGRAMMATIC_ONLY); - const programmaticOnlyExclude = parseToolList(process.env.TOOL_PROGRAMMATIC_ONLY_EXCLUDE); - const dualContext = parseToolList(process.env.TOOL_DUAL_CONTEXT); - const dualContextExclude = parseToolList(process.env.TOOL_DUAL_CONTEXT_EXCLUDE); - const registry: LCToolRegistry = new Map(); for (const tool of tools) { const { name, description, parameters } = tool; const agentOptions = agentToolOptions[name]; - /** Determine allowed_callers: agent options take precedence, then env vars, then default */ - let allowed_callers: AllowedCaller[]; - if (agentOptions?.allowed_callers && agentOptions.allowed_callers.length > 0) { - allowed_callers = agentOptions.allowed_callers; - } else if (toolMatchesPatterns(name, programmaticOnly, programmaticOnlyExclude)) { - allowed_callers = ['code_execution']; - } else if (toolMatchesPatterns(name, dualContext, dualContextExclude)) { - allowed_callers = ['direct', 'code_execution']; - } else { - allowed_callers = ['direct']; - } + const allowed_callers: AllowedCaller[] = + agentOptions?.allowed_callers && agentOptions.allowed_callers.length > 0 + ? agentOptions.allowed_callers + : ['direct']; - /** Determine defer_loading: agent options take precedence (explicit true/false) */ const defer_loading = agentOptions?.defer_loading === true; const toolDef: LCTool = { name, allowed_callers, defer_loading, + toolType: 'mcp', }; if (description) { @@ -240,6 +84,9 @@ export function buildToolRegistryFromAgentOptions( if (parameters) { toolDef.parameters = parameters; } + if (tool.serverName) { + toolDef.serverName = tool.serverName; + } registry.set(name, toolDef); } @@ -247,27 +94,6 @@ export function buildToolRegistryFromAgentOptions( return registry; } -/** - * Checks if PTC (Programmatic Tool Calling) should be enabled based on environment configuration. - * PTC is enabled if any tools or server patterns are configured for programmatic calling. - * @returns Whether PTC should be enabled - */ -export function shouldEnablePTC(): boolean { - const programmaticOnly = parseToolList(process.env.TOOL_PROGRAMMATIC_ONLY); - const dualContext = parseToolList(process.env.TOOL_DUAL_CONTEXT); - return programmaticOnly.size > 0 || dualContext.size > 0; -} - -/** - * Checks if tool search should be enabled based on environment configuration. - * Tool search is enabled if any tools or server patterns are configured as deferred. - * @returns Whether tool search should be enabled - */ -export function shouldEnableToolSearch(): boolean { - const deferred = parseToolList(process.env.TOOL_DEFERRED); - return deferred.size > 0; -} - interface MCPToolInstance { name: string; description?: string; @@ -294,7 +120,6 @@ export function extractMCPToolDefinition(tool: MCPToolInstance): ToolDefinition def.parameters = tool.mcpJsonSchema; } - /** Extract server name from tool name (format: toolName_mcp_ServerName) */ const serverName = getServerNameFromTool(tool.name); if (serverName) { def.serverName = serverName; @@ -326,10 +151,7 @@ export function cleanupMCPToolSchemas(tools: MCPToolInstance[]): void { } } -/** - * Builds tool registry from MCP tool definitions using the appropriate strategy. - * Uses early returns to avoid nesting (Torvalds principle). - */ +/** Builds tool registry from MCP tool definitions. */ function buildToolRegistry( mcpToolDefs: ToolDefinition[], agentToolOptions?: AgentToolOptions, @@ -338,11 +160,7 @@ function buildToolRegistry( return buildToolRegistryFromAgentOptions(mcpToolDefs, agentToolOptions); } - if (process.env.TOOL_CLASSIFICATION_FROM_ENV === 'true') { - return buildToolRegistryFromEnv(mcpToolDefs); - } - - /** No classification config - build basic definitions for event-driven mode */ + /** No agent options - build basic definitions for event-driven mode */ const registry: LCToolRegistry = new Map(); for (const toolDef of mcpToolDefs) { registry.set(toolDef.name, { @@ -362,9 +180,9 @@ export interface BuildToolClassificationParams { loadedTools: GenericTool[]; /** User ID for auth lookup */ userId: string; - /** Agent ID (used to check if this agent should have classification features) */ + /** Agent ID (used for logging and context) */ agentId?: string; - /** Per-tool configuration from the agent (takes precedence over env vars) */ + /** Per-tool configuration from the agent */ agentToolOptions?: AgentToolOptions; /** Whether the deferred_tools capability is enabled (from agent config) */ deferredToolsEnabled?: boolean; @@ -389,24 +207,6 @@ export interface BuildToolClassificationResult { hasDeferredTools: boolean; } -/** - * Checks if an agent is allowed to have classification features based on TOOL_CLASSIFICATION_AGENT_IDS. - * If TOOL_CLASSIFICATION_AGENT_IDS is not set, all agents are allowed (including when no agentId). - * If set, requires agentId to be in the list. - * @param agentId - The agent ID to check - * @returns Whether the agent is allowed - */ -export function isAgentAllowedForClassification(agentId?: string): boolean { - const allowedAgentIds = parseToolList(process.env.TOOL_CLASSIFICATION_AGENT_IDS); - if (allowedAgentIds.size === 0) { - return true; - } - if (!agentId) { - return false; - } - return allowedAgentIds.has(agentId); -} - /** * Checks if an agent's tools have any that match PTC patterns (programmatic only or dual context). * @param toolRegistry - The tool registry to check @@ -439,14 +239,11 @@ export function agentHasDeferredTools(toolRegistry: LCToolRegistry): boolean { * Builds the tool registry from MCP tools and conditionally creates PTC and tool search tools. * * This function: - * 1. Checks if the agent is allowed for classification features (via TOOL_CLASSIFICATION_AGENT_IDS) - * 2. Filters loaded tools for MCP tools - * 3. Extracts tool definitions and builds the registry - * - Uses agent's tool_options if provided (UI-based configuration) - * - Falls back to env vars for tools not configured at agent level - * 4. Cleans up temporary mcpJsonSchema properties - * 5. Creates PTC tool only if agent has tools configured for programmatic calling - * 6. Creates tool search tool only if agent has deferred tools + * 1. Filters loaded tools for MCP tools + * 2. Extracts tool definitions and builds the registry from agent's tool_options + * 3. Cleans up temporary mcpJsonSchema properties + * 4. Creates PTC tool only if agent has tools configured for programmatic calling + * 5. Creates tool search tool only if agent has deferred tools * * @param params - Parameters including loaded tools, userId, agentId, agentToolOptions, and dependencies * @returns Tool registry and any additional tools created @@ -475,34 +272,14 @@ export async function buildToolClassification( }; } - /** - * Check if this agent is allowed to have advanced classification features (PTC, deferred tools). - * Even if not allowed, we still build basic tool definitions for event-driven execution. - */ - const isAllowedForClassification = isAgentAllowedForClassification(agentId); - if (!isAllowedForClassification) { - logger.debug( - `[buildToolClassification] Agent ${agentId ?? 'undefined'} not allowed for classification, building basic definitions only`, - ); - } - const mcpToolDefs = mcpTools.map(extractMCPToolDefinition); - - /** - * Build registry from agent's tool_options if provided (UI config). - * Environment variable-based classification is only used as fallback - * when TOOL_CLASSIFICATION_FROM_ENV=true is explicitly set. - * - * Even without classification config, we still build basic tool definitions - * for event-driven execution. - */ const toolRegistry: LCToolRegistry = buildToolRegistry(mcpToolDefs, agentToolOptions); /** Clean up temporary mcpJsonSchema property from tools now that registry is populated */ cleanupMCPToolSchemas(mcpTools); /** - * Check if this agent actually has tools that match the patterns. + * Check if this agent actually has tools configured for these features. * Only enable PTC if the agent has programmatic tools. * Only enable tool search if the agent has deferred tools AND the capability is enabled. */ @@ -522,14 +299,6 @@ export async function buildToolClassification( /** Build toolDefinitions array from registry (single pass, reused) */ const toolDefinitions: LCTool[] = Array.from(toolRegistry.values()); - /** Agent not allowed for classification - return basic definitions */ - if (!isAllowedForClassification) { - logger.debug( - `[buildToolClassification] Agent ${agentId} not allowed for classification, returning basic definitions`, - ); - return { toolRegistry, toolDefinitions, additionalTools, hasDeferredTools: false }; - } - /** No programmatic or deferred tools - skip PTC/ToolSearch */ if (!hasProgrammaticTools && !hasDeferredTools) { logger.debug(