2025-04-18 17:44:24 -07:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-08-26 00:04:53 +02:00
import type { FunctionDeclaration } from '@google/genai' ;
import type {
2025-08-14 13:28:33 -07:00
AnyDeclarativeTool ,
ToolResult ,
ToolInvocation ,
} from './tools.js' ;
2025-08-26 00:04:53 +02:00
import { Kind , BaseDeclarativeTool , BaseToolInvocation } from './tools.js' ;
import type { Config } from '../config/config.js' ;
2026-02-20 13:33:04 -06:00
import { ApprovalMode } from '../policy/types.js' ;
2025-07-06 05:58:51 +08:00
import { spawn } from 'node:child_process' ;
import { StringDecoder } from 'node:string_decoder' ;
2025-05-28 00:43:23 -07:00
import { DiscoveredMCPTool } from './mcp-tool.js' ;
2025-07-06 05:58:51 +08:00
import { parse } from 'shell-quote' ;
2025-08-21 14:40:18 -07:00
import { ToolErrorType } from './tool-error.js' ;
2025-08-20 13:10:02 -07:00
import { safeJsonStringify } from '../utils/safeJsonStringify.js' ;
2025-10-24 13:04:40 -07:00
import type { MessageBus } from '../confirmation-bus/message-bus.js' ;
2025-10-21 16:35:22 -04:00
import { debugLogger } from '../utils/debugLogger.js' ;
2025-10-27 16:46:35 -07:00
import { coreEvents } from '../utils/events.js' ;
2026-02-06 16:49:25 -08:00
import {
DISCOVERED_TOOL_PREFIX ,
TOOL_LEGACY_ALIASES ,
getToolAliases ,
2026-02-20 13:33:04 -06:00
PLAN_MODE_TOOLS ,
WRITE_FILE_TOOL_NAME ,
EDIT_TOOL_NAME ,
2026-02-06 16:49:25 -08:00
} from './tool-names.js' ;
2025-11-06 15:51:16 -08:00
2025-05-28 00:43:23 -07:00
type ToolParams = Record < string , unknown > ;
2025-05-17 16:53:22 -07:00
2025-08-14 13:28:33 -07:00
class DiscoveredToolInvocation extends BaseToolInvocation <
ToolParams ,
ToolResult
> {
2025-05-03 19:57:28 -07:00
constructor (
private readonly config : Config ,
2025-11-06 15:51:16 -08:00
private readonly originalToolName : string ,
prefixedToolName : string ,
2025-08-14 13:28:33 -07:00
params : ToolParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-05-03 19:57:28 -07:00
) {
2025-11-06 15:51:16 -08:00
super ( params , messageBus , prefixedToolName ) ;
2025-08-14 13:28:33 -07:00
}
2025-05-03 19:57:28 -07:00
2025-08-14 13:28:33 -07:00
getDescription ( ) : string {
2025-08-20 13:10:02 -07:00
return safeJsonStringify ( this . params ) ;
2025-05-03 19:57:28 -07:00
}
2025-08-14 13:28:33 -07:00
async execute (
_signal : AbortSignal ,
_updateOutput ? : ( output : string ) = > void ,
) : Promise < ToolResult > {
2025-05-03 19:57:28 -07:00
const callCommand = this . config . getToolCallCommand ( ) ! ;
2025-11-06 15:51:16 -08:00
const child = spawn ( callCommand , [ this . originalToolName ] ) ;
2025-08-14 13:28:33 -07:00
child . stdin . write ( JSON . stringify ( this . params ) ) ;
2025-05-03 19:57:28 -07:00
child . stdin . end ( ) ;
2025-06-05 06:40:33 -07:00
2025-05-03 19:57:28 -07:00
let stdout = '' ;
let stderr = '' ;
let error : Error | null = null ;
let code : number | null = null ;
let signal : NodeJS.Signals | null = null ;
2025-06-05 06:40:33 -07:00
await new Promise < void > ( ( resolve ) = > {
const onStdout = ( data : Buffer ) = > {
stdout += data ? . toString ( ) ;
} ;
const onStderr = ( data : Buffer ) = > {
stderr += data ? . toString ( ) ;
} ;
const onError = ( err : Error ) = > {
error = err ;
} ;
const onClose = (
_code : number | null ,
_signal : NodeJS.Signals | null ,
) = > {
2025-05-03 19:57:28 -07:00
code = _code ;
signal = _signal ;
2025-06-05 06:40:33 -07:00
cleanup ( ) ;
resolve ( ) ;
} ;
const cleanup = ( ) = > {
child . stdout . removeListener ( 'data' , onStdout ) ;
child . stderr . removeListener ( 'data' , onStderr ) ;
child . removeListener ( 'error' , onError ) ;
child . removeListener ( 'close' , onClose ) ;
if ( child . connected ) {
child . disconnect ( ) ;
}
} ;
child . stdout . on ( 'data' , onStdout ) ;
child . stderr . on ( 'data' , onStderr ) ;
child . on ( 'error' , onError ) ;
child . on ( 'close' , onClose ) ;
} ) ;
2025-05-03 19:57:28 -07:00
// if there is any error, non-zero exit code, signal, or stderr, return error details instead of stdout
if ( error || code !== 0 || signal || stderr ) {
const llmContent = [
` Stdout: ${ stdout || '(empty)' } ` ,
` Stderr: ${ stderr || '(empty)' } ` ,
` Error: ${ error ? ? '(none)' } ` ,
` Exit Code: ${ code ? ? '(none)' } ` ,
` Signal: ${ signal ? ? '(none)' } ` ,
] . join ( '\n' ) ;
return {
llmContent ,
returnDisplay : llmContent ,
2025-08-21 14:40:18 -07:00
error : {
message : llmContent ,
type : ToolErrorType . DISCOVERED_TOOL_EXECUTION_ERROR ,
} ,
2025-05-03 19:57:28 -07:00
} ;
}
return {
llmContent : stdout ,
returnDisplay : stdout ,
} ;
}
}
2025-04-15 21:41:08 -07:00
2025-08-14 13:28:33 -07:00
export class DiscoveredTool extends BaseDeclarativeTool <
ToolParams ,
ToolResult
> {
2025-11-06 15:51:16 -08:00
private readonly originalName : string ;
2025-08-14 13:28:33 -07:00
constructor (
private readonly config : Config ,
2025-11-06 15:51:16 -08:00
originalName : string ,
prefixedName : string ,
description : string ,
2025-08-14 13:28:33 -07:00
override readonly parameterSchema : Record < string , unknown > ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-08-14 13:28:33 -07:00
) {
const discoveryCmd = config . getToolDiscoveryCommand ( ) ! ;
const callCommand = config . getToolCallCommand ( ) ! ;
2025-11-06 15:51:16 -08:00
const fullDescription =
description +
`
2025-08-14 13:28:33 -07:00
This tool was discovered from the project by executing the command \` ${ discoveryCmd } \` on project root.
2025-11-06 15:51:16 -08:00
When called, this tool will execute the command \` ${ callCommand } ${ originalName } \` on project root.
2025-08-14 13:28:33 -07:00
Tool discovery and call commands can be configured in project or user settings.
When called, the tool call command is executed as a subprocess.
On success, tool output is returned as a json string.
Otherwise, the following information is returned:
Stdout: Output on stdout stream. Can be \` (empty) \` or partial.
Stderr: Output on stderr stream. Can be \` (empty) \` or partial.
Error: Error or \` (none) \` if no error was reported for the subprocess.
Exit Code: Exit code or \` (none) \` if terminated by signal.
Signal: Signal number or \` (none) \` if no signal was received.
` ;
super (
2025-11-06 15:51:16 -08:00
prefixedName ,
prefixedName ,
fullDescription ,
2025-08-14 13:28:33 -07:00
Kind . Other ,
parameterSchema ,
2026-01-04 17:11:43 -05:00
messageBus ,
2025-08-14 13:28:33 -07:00
false , // isOutputMarkdown
false , // canUpdateOutput
) ;
2025-11-06 15:51:16 -08:00
this . originalName = originalName ;
2025-08-14 13:28:33 -07:00
}
protected createInvocation (
params : ToolParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-10-24 13:04:40 -07:00
_toolName? : string ,
_displayName? : string ,
2025-08-14 13:28:33 -07:00
) : ToolInvocation < ToolParams , ToolResult > {
2025-11-06 15:51:16 -08:00
return new DiscoveredToolInvocation (
this . config ,
this . originalName ,
2026-01-04 15:51:23 -05:00
_toolName ? ? this . name ,
2025-11-06 15:51:16 -08:00
params ,
2026-01-04 17:11:43 -05:00
messageBus ,
2025-11-06 15:51:16 -08:00
) ;
2025-08-14 13:28:33 -07:00
}
}
2025-04-21 12:59:31 -07:00
export class ToolRegistry {
2025-09-17 11:45:04 -07:00
// The tools keyed by tool name as seen by the LLM.
2025-11-07 12:18:35 -08:00
// This includes tools which are currently not active, use `getActiveTools`
// and `isActive` to get only the active tools.
private allKnownTools : Map < string , AnyDeclarativeTool > = new Map ( ) ;
2025-05-20 11:36:21 -07:00
private config : Config ;
2026-01-04 17:11:43 -05:00
private messageBus : MessageBus ;
2025-04-15 21:41:08 -07:00
2026-01-04 17:11:43 -05:00
constructor ( config : Config , messageBus : MessageBus ) {
2025-05-20 11:36:21 -07:00
this . config = config ;
2025-10-28 09:20:57 -07:00
this . messageBus = messageBus ;
}
2026-01-04 17:11:43 -05:00
getMessageBus ( ) : MessageBus {
2025-10-28 09:20:57 -07:00
return this . messageBus ;
}
2025-04-17 18:06:21 -04:00
/**
* Registers a tool definition.
2025-11-07 12:18:35 -08:00
*
* Note that excluded tools are still registered to allow for enabling them
* later in the session.
*
2025-04-17 18:06:21 -04:00
* @param tool - The tool object containing schema and execution logic.
*/
2025-08-06 10:50:02 -07:00
registerTool ( tool : AnyDeclarativeTool ) : void {
2025-11-07 12:18:35 -08:00
if ( this . allKnownTools . has ( tool . name ) ) {
2025-07-18 14:29:09 -07:00
if ( tool instanceof DiscoveredMCPTool ) {
tool = tool . asFullyQualifiedTool ( ) ;
} else {
// Decide on behavior: throw error, log warning, or allow overwrite
2025-10-21 16:35:22 -04:00
debugLogger . warn (
2025-07-18 14:29:09 -07:00
` Tool with name " ${ tool . name } " is already registered. Overwriting. ` ,
) ;
}
2025-04-15 21:41:08 -07:00
}
2025-11-07 12:18:35 -08:00
this . allKnownTools . set ( tool . name , tool ) ;
2025-04-17 18:06:21 -04:00
}
2025-04-15 21:41:08 -07:00
2026-01-05 15:12:51 -08:00
/**
* Unregisters a tool definition by name.
*
* @param name - The name of the tool to unregister.
*/
unregisterTool ( name : string ) : void {
this . allKnownTools . delete ( name ) ;
}
2025-11-05 15:38:44 -08:00
/**
* Sorts tools as:
* 1. Built in tools.
* 2. Discovered tools.
* 3. MCP tools ordered by server name.
*
2025-11-07 12:18:35 -08:00
* This is a stable sort in that tries preserve existing order.
2025-11-05 15:38:44 -08:00
*/
sortTools ( ) : void {
const getPriority = ( tool : AnyDeclarativeTool ) : number = > {
if ( tool instanceof DiscoveredMCPTool ) return 2 ;
if ( tool instanceof DiscoveredTool ) return 1 ;
return 0 ; // Built-in
} ;
2025-11-07 12:18:35 -08:00
this . allKnownTools = new Map (
Array . from ( this . allKnownTools . entries ( ) ) . sort ( ( a , b ) = > {
2025-11-05 15:38:44 -08:00
const toolA = a [ 1 ] ;
const toolB = b [ 1 ] ;
const priorityA = getPriority ( toolA ) ;
const priorityB = getPriority ( toolB ) ;
if ( priorityA !== priorityB ) {
return priorityA - priorityB ;
}
if ( priorityA === 2 ) {
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-11-05 15:38:44 -08:00
const serverA = ( toolA as DiscoveredMCPTool ) . serverName ;
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-11-05 15:38:44 -08:00
const serverB = ( toolB as DiscoveredMCPTool ) . serverName ;
return serverA . localeCompare ( serverB ) ;
}
return 0 ;
} ) ,
) ;
}
2025-08-05 23:59:31 +02:00
private removeDiscoveredTools ( ) : void {
2025-11-07 12:18:35 -08:00
for ( const tool of this . allKnownTools . values ( ) ) {
2025-08-05 23:59:31 +02:00
if ( tool instanceof DiscoveredTool || tool instanceof DiscoveredMCPTool ) {
2025-11-07 12:18:35 -08:00
this . allKnownTools . delete ( tool . name ) ;
2025-08-05 23:59:31 +02:00
}
}
}
2025-08-13 19:31:24 +00:00
/**
* Removes all tools from a specific MCP server.
* @param serverName The name of the server to remove tools from.
*/
removeMcpToolsByServer ( serverName : string ) : void {
2025-11-07 12:18:35 -08:00
for ( const [ name , tool ] of this . allKnownTools . entries ( ) ) {
2025-08-13 19:31:24 +00:00
if ( tool instanceof DiscoveredMCPTool && tool . serverName === serverName ) {
2025-11-07 12:18:35 -08:00
this . allKnownTools . delete ( name ) ;
2025-08-13 19:31:24 +00:00
}
}
}
2025-05-03 19:57:28 -07:00
/**
2025-06-02 09:56:32 -07:00
* Discovers tools from project (if available and configured).
2025-05-03 19:57:28 -07:00
* Can be called multiple times to update discovered tools.
2025-07-25 03:14:45 +02:00
* This will discover tools from the command line and from MCP servers.
2025-05-03 19:57:28 -07:00
*/
2025-07-25 03:14:45 +02:00
async discoverAllTools ( ) : Promise < void > {
2025-05-03 19:57:28 -07:00
// remove any previously discovered tools
2025-08-05 23:59:31 +02:00
this . removeDiscoveredTools ( ) ;
2025-07-06 05:58:51 +08:00
await this . discoverAndRegisterToolsFromCommand ( ) ;
2025-07-22 09:34:56 -04:00
}
2025-07-06 05:58:51 +08:00
private async discoverAndRegisterToolsFromCommand ( ) : Promise < void > {
2025-05-04 12:11:19 -07:00
const discoveryCmd = this . config . getToolDiscoveryCommand ( ) ;
2025-07-06 05:58:51 +08:00
if ( ! discoveryCmd ) {
return ;
}
try {
const cmdParts = parse ( discoveryCmd ) ;
if ( cmdParts . length === 0 ) {
throw new Error (
'Tool discovery command is empty or contains only whitespace.' ,
) ;
}
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-07-06 05:58:51 +08:00
const proc = spawn ( cmdParts [ 0 ] as string , cmdParts . slice ( 1 ) as string [ ] ) ;
let stdout = '' ;
const stdoutDecoder = new StringDecoder ( 'utf8' ) ;
let stderr = '' ;
const stderrDecoder = new StringDecoder ( 'utf8' ) ;
let sizeLimitExceeded = false ;
const MAX_STDOUT_SIZE = 10 * 1024 * 1024 ; // 10MB limit
const MAX_STDERR_SIZE = 10 * 1024 * 1024 ; // 10MB limit
let stdoutByteLength = 0 ;
let stderrByteLength = 0 ;
proc . stdout . on ( 'data' , ( data ) = > {
if ( sizeLimitExceeded ) return ;
if ( stdoutByteLength + data . length > MAX_STDOUT_SIZE ) {
sizeLimitExceeded = true ;
proc . kill ( ) ;
return ;
}
stdoutByteLength += data . length ;
stdout += stdoutDecoder . write ( data ) ;
} ) ;
proc . stderr . on ( 'data' , ( data ) = > {
if ( sizeLimitExceeded ) return ;
if ( stderrByteLength + data . length > MAX_STDERR_SIZE ) {
sizeLimitExceeded = true ;
proc . kill ( ) ;
return ;
}
stderrByteLength += data . length ;
stderr += stderrDecoder . write ( data ) ;
} ) ;
await new Promise < void > ( ( resolve , reject ) = > {
proc . on ( 'error' , reject ) ;
proc . on ( 'close' , ( code ) = > {
stdout += stdoutDecoder . end ( ) ;
stderr += stderrDecoder . end ( ) ;
if ( sizeLimitExceeded ) {
return reject (
new Error (
` Tool discovery command output exceeded size limit of ${ MAX_STDOUT_SIZE } bytes. ` ,
) ,
) ;
}
if ( code !== 0 ) {
2025-10-27 16:46:35 -07:00
coreEvents . emitFeedback (
'error' ,
` Tool discovery command failed with code ${ code } . ` ,
stderr ,
) ;
2025-07-06 05:58:51 +08:00
return reject (
new Error ( ` Tool discovery command failed with exit code ${ code } ` ) ,
) ;
}
resolve ( ) ;
} ) ;
} ) ;
2025-06-02 13:41:49 -07:00
// execute discovery command and extract function declarations (w/ or w/o "tool" wrappers)
2025-05-04 12:11:19 -07:00
const functions : FunctionDeclaration [ ] = [ ] ;
2026-02-20 22:28:55 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
2025-07-06 05:58:51 +08:00
const discoveredItems = JSON . parse ( stdout . trim ( ) ) ;
if ( ! discoveredItems || ! Array . isArray ( discoveredItems ) ) {
throw new Error (
'Tool discovery command did not return a JSON array of tools.' ,
) ;
}
for ( const tool of discoveredItems ) {
if ( tool && typeof tool === 'object' ) {
if ( Array . isArray ( tool [ 'function_declarations' ] ) ) {
functions . push ( . . . tool [ 'function_declarations' ] ) ;
} else if ( Array . isArray ( tool [ 'functionDeclarations' ] ) ) {
functions . push ( . . . tool [ 'functionDeclarations' ] ) ;
} else if ( tool [ 'name' ] ) {
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-07-06 05:58:51 +08:00
functions . push ( tool as FunctionDeclaration ) ;
}
2025-06-02 13:41:49 -07:00
}
2025-05-04 12:11:19 -07:00
}
// register each function as a tool
for ( const func of functions ) {
2025-07-06 05:58:51 +08:00
if ( ! func . name ) {
2025-10-21 16:35:22 -04:00
debugLogger . warn ( 'Discovered a tool with no name. Skipping.' ) ;
2025-07-06 05:58:51 +08:00
continue ;
}
const parameters =
2025-08-11 16:12:41 -07:00
func . parametersJsonSchema &&
typeof func . parametersJsonSchema === 'object' &&
! Array . isArray ( func . parametersJsonSchema )
? func . parametersJsonSchema
2025-07-06 05:58:51 +08:00
: { } ;
2025-05-04 12:11:19 -07:00
this . registerTool (
new DiscoveredTool (
this . config ,
2025-07-06 05:58:51 +08:00
func . name ,
2025-11-06 15:51:16 -08:00
DISCOVERED_TOOL_PREFIX + func . name ,
2025-07-06 05:58:51 +08:00
func . description ? ? '' ,
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-07-06 05:58:51 +08:00
parameters as Record < string , unknown > ,
2025-11-06 15:51:16 -08:00
this . messageBus ,
2025-05-04 12:11:19 -07:00
) ,
) ;
}
2025-07-06 05:58:51 +08:00
} catch ( e ) {
2025-12-29 15:46:10 -05:00
debugLogger . error ( ` Tool discovery command " ${ discoveryCmd } " failed: ` , e ) ;
2025-07-06 05:58:51 +08:00
throw e ;
2025-05-03 19:57:28 -07:00
}
}
2026-02-24 09:20:11 -05:00
private buildToolMetadata ( ) : Map < string , Record < string , unknown > > {
const toolMetadata = new Map < string , Record < string , unknown > > ( ) ;
for ( const [ name , tool ] of this . allKnownTools ) {
if ( tool . toolAnnotations ) {
toolMetadata . set ( name , tool . toolAnnotations ) ;
}
}
return toolMetadata ;
}
2025-11-07 12:18:35 -08:00
/**
* @returns All the tools that are not excluded.
*/
private getActiveTools ( ) : AnyDeclarativeTool [ ] {
2026-02-24 09:20:11 -05:00
const toolMetadata = this . buildToolMetadata ( ) ;
2026-02-06 16:49:25 -08:00
const excludedTools =
2026-02-24 09:20:11 -05:00
this . expandExcludeToolsWithAliases (
this . config . getExcludeTools ( toolMetadata ) ,
) ? ? new Set ( [ ] ) ;
2025-11-07 12:18:35 -08:00
const activeTools : AnyDeclarativeTool [ ] = [ ] ;
for ( const tool of this . allKnownTools . values ( ) ) {
if ( this . isActiveTool ( tool , excludedTools ) ) {
activeTools . push ( tool ) ;
}
}
return activeTools ;
}
2026-02-06 16:49:25 -08:00
/**
* Expands an excludeTools set to include all legacy aliases.
* For example, if 'search_file_content' is excluded and it's an alias for
* 'grep_search', both names will be in the returned set.
*/
private expandExcludeToolsWithAliases (
excludeTools : Set < string > | undefined ,
) : Set < string > | undefined {
if ( ! excludeTools || excludeTools . size === 0 ) {
return excludeTools ;
}
const expanded = new Set < string > ( ) ;
for ( const name of excludeTools ) {
for ( const alias of getToolAliases ( name ) ) {
expanded . add ( alias ) ;
}
}
return expanded ;
}
2025-11-07 12:18:35 -08:00
/**
* @param tool
* @param excludeTools (optional, helps performance for repeated calls)
* @returns Whether or not the `tool` is not excluded.
*/
private isActiveTool (
tool : AnyDeclarativeTool ,
excludeTools? : Set < string > ,
) : boolean {
2026-02-06 16:49:25 -08:00
excludeTools ? ? =
2026-02-24 09:20:11 -05:00
this . expandExcludeToolsWithAliases (
this . config . getExcludeTools ( this . buildToolMetadata ( ) ) ,
) ? ? new Set ( [ ] ) ;
2026-02-20 13:33:04 -06:00
// Filter tools in Plan Mode to only allow approved read-only tools.
const isPlanMode =
typeof this . config . getApprovalMode === 'function' &&
this . config . getApprovalMode ( ) === ApprovalMode . PLAN ;
if ( isPlanMode ) {
const allowedToolNames = new Set < string > ( PLAN_MODE_TOOLS ) ;
// We allow write_file and replace for writing plans specifically.
allowedToolNames . add ( WRITE_FILE_TOOL_NAME ) ;
allowedToolNames . add ( EDIT_TOOL_NAME ) ;
// Discovered MCP tools are allowed if they are read-only.
if (
tool instanceof DiscoveredMCPTool &&
tool . isReadOnly &&
! allowedToolNames . has ( tool . name )
) {
allowedToolNames . add ( tool . name ) ;
}
if ( ! allowedToolNames . has ( tool . name ) ) {
return false ;
}
}
2025-11-07 12:18:35 -08:00
const normalizedClassName = tool . constructor . name . replace ( /^_+/ , '' ) ;
const possibleNames = [ tool . name , normalizedClassName ] ;
if ( tool instanceof DiscoveredMCPTool ) {
// Check both the unqualified and qualified name for MCP tools.
if ( tool . name . startsWith ( tool . getFullyQualifiedPrefix ( ) ) ) {
possibleNames . push (
tool . name . substring ( tool . getFullyQualifiedPrefix ( ) . length ) ,
) ;
} else {
possibleNames . push ( ` ${ tool . getFullyQualifiedPrefix ( ) } ${ tool . name } ` ) ;
}
}
return ! possibleNames . some ( ( name ) = > excludeTools . has ( name ) ) ;
}
2025-04-17 18:06:21 -04:00
/**
2025-04-19 19:45:42 +01:00
* Retrieves the list of tool schemas (FunctionDeclaration array).
* Extracts the declarations from the ToolListUnion structure.
2025-05-03 19:57:28 -07:00
* Includes discovered (vs registered) tools if configured.
2026-02-09 15:46:23 -05:00
* @param modelId Optional model identifier to get model-specific schemas.
2025-04-19 19:45:42 +01:00
* @returns An array of FunctionDeclarations.
2025-04-17 18:06:21 -04:00
*/
2026-02-09 15:46:23 -05:00
getFunctionDeclarations ( modelId? : string ) : FunctionDeclaration [ ] {
2026-02-20 13:33:04 -06:00
const isPlanMode = this . config . getApprovalMode ( ) === ApprovalMode . PLAN ;
const plansDir = this . config . storage . getPlansDir ( ) ;
2025-04-17 18:06:21 -04:00
const declarations : FunctionDeclaration [ ] = [ ] ;
2025-11-07 12:18:35 -08:00
this . getActiveTools ( ) . forEach ( ( tool ) = > {
2026-02-20 13:33:04 -06:00
let schema = tool . getSchema ( modelId ) ;
if (
isPlanMode &&
( tool . name === WRITE_FILE_TOOL_NAME || tool . name === EDIT_TOOL_NAME )
) {
schema = {
. . . schema ,
description : ` ONLY FOR PLANS: ${ schema . description } . You are currently in Plan Mode and may ONLY use this tool to write or update plans (.md files) in the plans directory: ${ plansDir } /. You cannot use this tool to modify source code directly. ` ,
} ;
}
declarations . push ( schema ) ;
2025-04-17 18:06:21 -04:00
} ) ;
2025-04-19 19:45:42 +01:00
return declarations ;
}
2025-04-15 21:41:08 -07:00
2025-08-06 20:34:38 -04:00
/**
* Retrieves a filtered list of tool schemas based on a list of tool names.
* @param toolNames - An array of tool names to include.
2026-02-09 15:46:23 -05:00
* @param modelId Optional model identifier to get model-specific schemas.
2025-08-06 20:34:38 -04:00
* @returns An array of FunctionDeclarations for the specified tools.
*/
2026-02-09 15:46:23 -05:00
getFunctionDeclarationsFiltered (
toolNames : string [ ] ,
modelId? : string ,
) : FunctionDeclaration [ ] {
2025-08-06 20:34:38 -04:00
const declarations : FunctionDeclaration [ ] = [ ] ;
for ( const name of toolNames ) {
2026-01-26 23:53:05 -05:00
const tool = this . getTool ( name ) ;
if ( tool ) {
2026-02-09 15:46:23 -05:00
declarations . push ( tool . getSchema ( modelId ) ) ;
2025-08-06 20:34:38 -04:00
}
}
return declarations ;
}
2025-08-26 19:22:05 -04:00
/**
2025-11-07 12:18:35 -08:00
* Returns an array of all registered and discovered tool names which are not
* excluded via configuration.
2025-08-26 19:22:05 -04:00
*/
getAllToolNames ( ) : string [ ] {
2025-11-07 12:18:35 -08:00
return this . getActiveTools ( ) . map ( ( tool ) = > tool . name ) ;
2025-08-26 19:22:05 -04:00
}
2025-04-19 19:45:42 +01:00
/**
2025-05-03 19:57:28 -07:00
* Returns an array of all registered and discovered tool instances.
2025-04-19 19:45:42 +01:00
*/
2025-08-06 10:50:02 -07:00
getAllTools ( ) : AnyDeclarativeTool [ ] {
2025-11-07 12:18:35 -08:00
return this . getActiveTools ( ) . sort ( ( a , b ) = >
2025-07-15 14:35:35 -04:00
a . displayName . localeCompare ( b . displayName ) ,
) ;
2025-04-19 19:45:42 +01:00
}
2025-06-02 13:39:25 -07:00
/**
* Returns an array of tools registered from a specific MCP server.
*/
2025-08-06 10:50:02 -07:00
getToolsByServer ( serverName : string ) : AnyDeclarativeTool [ ] {
const serverTools : AnyDeclarativeTool [ ] = [ ] ;
2025-11-07 12:18:35 -08:00
for ( const tool of this . getActiveTools ( ) ) {
2026-02-10 00:10:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2025-06-02 13:39:25 -07:00
if ( ( tool as DiscoveredMCPTool ) ? . serverName === serverName ) {
serverTools . push ( tool ) ;
}
}
2025-07-15 14:35:35 -04:00
return serverTools . sort ( ( a , b ) = > a . name . localeCompare ( b . name ) ) ;
2025-06-02 13:39:25 -07:00
}
2025-04-17 18:06:21 -04:00
/**
* Get the definition of a specific tool.
*/
2025-08-06 10:50:02 -07:00
getTool ( name : string ) : AnyDeclarativeTool | undefined {
2026-01-21 15:18:33 +11:00
let tool = this . allKnownTools . get ( name ) ;
2026-02-02 16:34:14 -08:00
// If not found, check legacy aliases
if ( ! tool && TOOL_LEGACY_ALIASES [ name ] ) {
const currentName = TOOL_LEGACY_ALIASES [ name ] ;
tool = this . allKnownTools . get ( currentName ) ;
if ( tool ) {
debugLogger . debug (
` Resolved legacy tool name " ${ name } " to current name " ${ currentName } " ` ,
) ;
}
}
2026-01-21 15:18:33 +11:00
if ( ! tool && name . includes ( '__' ) ) {
for ( const t of this . allKnownTools . values ( ) ) {
if ( t instanceof DiscoveredMCPTool ) {
if ( t . getFullyQualifiedName ( ) === name ) {
tool = t ;
break ;
}
}
}
}
2025-11-07 12:18:35 -08:00
if ( tool && this . isActiveTool ( tool ) ) {
return tool ;
}
return ;
2025-04-17 18:06:21 -04:00
}
2025-04-15 21:41:08 -07:00
}