( tool: string, modeSlug: string, customModes: ModeConfig[], toolRequirements?: Record<string, boolean>, toolParams?: Record<string, any>, // All tool parameters experiments?: Record<string, boolean>, includedTools?: string[], // Opt-in tools explicitly included (e.g., from modelInfo) )
| 118 | } |
| 119 | |
| 120 | export function isToolAllowedForMode( |
| 121 | tool: string, |
| 122 | modeSlug: string, |
| 123 | customModes: ModeConfig[], |
| 124 | toolRequirements?: Record<string, boolean>, |
| 125 | toolParams?: Record<string, any>, // All tool parameters |
| 126 | experiments?: Record<string, boolean>, |
| 127 | includedTools?: string[], // Opt-in tools explicitly included (e.g., from modelInfo) |
| 128 | ): boolean { |
| 129 | // Resolve alias to canonical name (e.g., "search_and_replace" → "edit") |
| 130 | const resolvedTool = TOOL_ALIASES[tool] ?? tool |
| 131 | const resolvedIncludedTools = includedTools?.map((t) => TOOL_ALIASES[t] ?? t) |
| 132 | |
| 133 | // Check tool requirements first — explicit disabling takes priority over everything, |
| 134 | // including ALWAYS_AVAILABLE_TOOLS. This ensures disabledTools works consistently |
| 135 | // at both the filtering layer and the execution-time validation layer. |
| 136 | if (toolRequirements && typeof toolRequirements === "object") { |
| 137 | if ( |
| 138 | (tool in toolRequirements && !toolRequirements[tool]) || |
| 139 | (resolvedTool in toolRequirements && !toolRequirements[resolvedTool]) |
| 140 | ) { |
| 141 | return false |
| 142 | } |
| 143 | } else if (toolRequirements === false) { |
| 144 | // If toolRequirements is a boolean false, all tools are disabled |
| 145 | return false |
| 146 | } |
| 147 | |
| 148 | // Always allow these tools (unless explicitly disabled above) |
| 149 | if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) { |
| 150 | return true |
| 151 | } |
| 152 | |
| 153 | // For now, allow all custom tools in any mode. |
| 154 | // As a follow-up we should expand the custom tool definition to include mode restrictions. |
| 155 | if (experiments?.customTools && customToolRegistry.has(tool)) { |
| 156 | return true |
| 157 | } |
| 158 | |
| 159 | // Check if this is a dynamic MCP tool (mcp_serverName_toolName) |
| 160 | // These should be allowed if the mcp group is allowed for the mode |
| 161 | const isDynamicMcpTool = tool.startsWith("mcp_") |
| 162 | |
| 163 | if (experiments && Object.values(EXPERIMENT_IDS).includes(tool as ExperimentId)) { |
| 164 | if (!experiments[tool]) { |
| 165 | return false |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | const mode = getModeBySlug(modeSlug, customModes) |
| 170 | |
| 171 | if (!mode) { |
| 172 | return false |
| 173 | } |
| 174 | |
| 175 | // Check if tool is in any of the mode's groups and respects any group options |
| 176 | for (const group of mode.groups) { |
| 177 | const groupName = getGroupName(group) |
no test coverage detected