2025-09-30 17:00:54 -04:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-12-17 22:46:55 -05:00
import { Storage } from '../config/storage.js' ;
import { coreEvents , CoreEvent } from '../utils/events.js' ;
2026-01-13 14:31:34 -08:00
import type { AgentOverride , Config } from '../config/config.js' ;
import type { AgentDefinition , LocalAgentDefinition } from './types.js' ;
2026-01-12 11:31:49 -05:00
import { loadAgentsFromDirectory } from './agentLoader.js' ;
2025-09-30 17:00:54 -04:00
import { CodebaseInvestigatorAgent } from './codebase-investigator.js' ;
2026-01-07 15:01:57 -08:00
import { CliHelpAgent } from './cli-help-agent.js' ;
2026-01-16 09:21:13 -08:00
import { GeneralistAgent } from './generalist-agent.js' ;
2025-12-30 18:11:51 -05:00
import { A2AClientManager } from './a2a-client-manager.js' ;
2026-01-07 17:46:37 -05:00
import { ADCHandler } from './remote-invocation.js' ;
2025-10-03 13:21:08 -04:00
import { type z } from 'zod' ;
2025-10-21 16:35:22 -04:00
import { debugLogger } from '../utils/debugLogger.js' ;
2025-12-09 16:07:50 -05:00
import {
2025-12-17 09:43:21 -08:00
DEFAULT_GEMINI_MODEL ,
GEMINI_MODEL_ALIAS_AUTO ,
PREVIEW_GEMINI_FLASH_MODEL ,
isPreviewModel ,
2026-01-08 12:39:40 -08:00
isAutoModel ,
2025-12-09 16:07:50 -05:00
} from '../config/models.js' ;
2026-01-13 12:16:02 -08:00
import {
type ModelConfig ,
ModelConfigService ,
} from '../services/modelConfigService.js' ;
2026-01-16 16:51:10 +00:00
import { DELEGATE_TO_AGENT_TOOL_NAME } from '../tools/tool-names.js' ;
2025-11-19 20:41:16 -08:00
/**
* Returns the model config alias for a given agent definition.
*/
export function getModelConfigAlias < TOutput extends z.ZodTypeAny > (
definition : AgentDefinition < TOutput > ,
) : string {
return ` ${ definition . name } -config ` ;
}
2025-09-30 17:00:54 -04:00
/**
* Manages the discovery, loading, validation, and registration of
* AgentDefinitions.
*/
export class AgentRegistry {
2025-10-03 13:21:08 -04:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private readonly agents = new Map < string , AgentDefinition < any > > ( ) ;
2025-09-30 17:00:54 -04:00
constructor ( private readonly config : Config ) { }
/**
* Discovers and loads agents.
*/
async initialize ( ) : Promise < void > {
2026-01-12 12:11:24 -05:00
coreEvents . on ( CoreEvent . ModelChanged , this . onModelChanged ) ;
2025-12-17 09:43:21 -08:00
2026-01-09 09:33:59 -08:00
await this . loadAgents ( ) ;
}
2026-01-12 12:11:24 -05:00
private onModelChanged = ( ) = > {
this . refreshAgents ( ) . catch ( ( e ) = > {
debugLogger . error (
'[AgentRegistry] Failed to refresh agents on model change:' ,
e ,
) ;
} ) ;
} ;
2026-01-09 09:33:59 -08:00
/**
* Clears the current registry and re-scans for agents.
*/
async reload ( ) : Promise < void > {
A2AClientManager . getInstance ( ) . clearCache ( ) ;
2026-01-14 19:30:17 -05:00
await this . config . reloadAgents ( ) ;
2026-01-09 09:33:59 -08:00
this . agents . clear ( ) ;
await this . loadAgents ( ) ;
coreEvents . emitAgentsRefreshed ( ) ;
}
2026-01-12 12:11:24 -05:00
/**
* Disposes of resources and removes event listeners.
*/
dispose ( ) : void {
coreEvents . off ( CoreEvent . ModelChanged , this . onModelChanged ) ;
}
2026-01-09 09:33:59 -08:00
private async loadAgents ( ) : Promise < void > {
this . loadBuiltInAgents ( ) ;
2025-12-17 22:46:55 -05:00
if ( ! this . config . isAgentsEnabled ( ) ) {
return ;
}
// Load user-level agents: ~/.gemini/agents/
const userAgentsDir = Storage . getUserAgentsDir ( ) ;
const userAgents = await loadAgentsFromDirectory ( userAgentsDir ) ;
for ( const error of userAgents . errors ) {
debugLogger . warn (
` [AgentRegistry] Error loading user agent: ${ error . message } ` ,
) ;
coreEvents . emitFeedback ( 'error' , ` Agent loading error: ${ error . message } ` ) ;
}
2025-12-30 18:11:51 -05:00
await Promise . allSettled (
userAgents . agents . map ( ( agent ) = > this . registerAgent ( agent ) ) ,
) ;
2025-12-17 22:46:55 -05:00
// Load project-level agents: .gemini/agents/ (relative to Project Root)
const folderTrustEnabled = this . config . getFolderTrust ( ) ;
const isTrustedFolder = this . config . isTrustedFolder ( ) ;
if ( ! folderTrustEnabled || isTrustedFolder ) {
const projectAgentsDir = this . config . storage . getProjectAgentsDir ( ) ;
const projectAgents = await loadAgentsFromDirectory ( projectAgentsDir ) ;
for ( const error of projectAgents . errors ) {
coreEvents . emitFeedback (
'error' ,
` Agent loading error: ${ error . message } ` ,
) ;
}
2025-12-30 18:11:51 -05:00
await Promise . allSettled (
projectAgents . agents . map ( ( agent ) = > this . registerAgent ( agent ) ) ,
) ;
2025-12-17 22:46:55 -05:00
} else {
coreEvents . emitFeedback (
'info' ,
'Skipping project agents due to untrusted folder. To enable, ensure that the project root is trusted.' ,
) ;
}
2026-01-13 19:09:22 +00:00
// Load agents from extensions
for ( const extension of this . config . getExtensions ( ) ) {
if ( extension . isActive && extension . agents ) {
await Promise . allSettled (
extension . agents . map ( ( agent ) = > this . registerAgent ( agent ) ) ,
) ;
}
}
2025-09-30 17:00:54 -04:00
if ( this . config . getDebugMode ( ) ) {
2025-10-21 16:35:22 -04:00
debugLogger . log (
2026-01-09 09:33:59 -08:00
` [AgentRegistry] Loaded with ${ this . agents . size } agents. ` ,
2025-09-30 17:00:54 -04:00
) ;
}
}
private loadBuiltInAgents ( ) : void {
2025-10-13 22:30:32 -04:00
const investigatorSettings = this . config . getCodebaseInvestigatorSettings ( ) ;
2026-01-07 15:01:57 -08:00
const cliHelpSettings = this . config . getCliHelpAgentSettings ( ) ;
2026-01-14 19:30:17 -05:00
const agentsSettings = this . config . getAgentsSettings ( ) ;
const agentsOverrides = agentsSettings . overrides ? ? { } ;
// Only register the agent if it's enabled in the settings and not explicitly disabled via overrides.
if (
investigatorSettings ? . enabled &&
! agentsOverrides [ CodebaseInvestigatorAgent . name ] ? . disabled
) {
2025-12-17 09:43:21 -08:00
let model ;
const settingsModel = investigatorSettings . model ;
// Check if the user explicitly set a model in the settings.
if ( settingsModel && settingsModel !== GEMINI_MODEL_ALIAS_AUTO ) {
model = settingsModel ;
} else {
// Use Preview Flash model if the main model is any of the preview models
// If the main model is not preview model, use default pro model.
model = isPreviewModel ( this . config . getModel ( ) )
? PREVIEW_GEMINI_FLASH_MODEL
: DEFAULT_GEMINI_MODEL ;
2025-12-09 16:07:50 -05:00
}
2025-10-13 22:30:32 -04:00
const agentDef = {
. . . CodebaseInvestigatorAgent ,
modelConfig : {
. . . CodebaseInvestigatorAgent . modelConfig ,
2025-12-09 16:07:50 -05:00
model ,
2026-01-13 14:31:34 -08:00
generateContentConfig : {
. . . CodebaseInvestigatorAgent . modelConfig . generateContentConfig ,
thinkingConfig : {
. . . CodebaseInvestigatorAgent . modelConfig . generateContentConfig
? . thinkingConfig ,
thinkingBudget :
investigatorSettings.thinkingBudget ? ?
CodebaseInvestigatorAgent . modelConfig . generateContentConfig
? . thinkingConfig ? . thinkingBudget ,
} ,
} ,
2025-10-13 22:30:32 -04:00
} ,
runConfig : {
. . . CodebaseInvestigatorAgent . runConfig ,
2026-01-13 14:31:34 -08:00
maxTimeMinutes :
2025-10-13 22:30:32 -04:00
investigatorSettings.maxTimeMinutes ? ?
2026-01-13 14:31:34 -08:00
CodebaseInvestigatorAgent . runConfig . maxTimeMinutes ,
maxTurns :
2025-10-13 22:30:32 -04:00
investigatorSettings.maxNumTurns ? ?
2026-01-13 14:31:34 -08:00
CodebaseInvestigatorAgent . runConfig . maxTurns ,
2025-10-13 22:30:32 -04:00
} ,
} ;
2025-12-30 18:11:51 -05:00
this . registerLocalAgent ( agentDef ) ;
2025-10-13 22:30:32 -04:00
}
2025-12-19 14:11:32 -08:00
2026-01-14 19:30:17 -05:00
// Register the CLI help agent if it's explicitly enabled and not explicitly disabled via overrides.
if (
cliHelpSettings . enabled &&
! agentsOverrides [ CliHelpAgent . name ] ? . disabled
) {
2026-01-07 15:01:57 -08:00
this . registerLocalAgent ( CliHelpAgent ( this . config ) ) ;
2025-12-19 14:11:32 -08:00
}
2026-01-16 09:21:13 -08:00
// Register the generalist agent.
this . registerLocalAgent ( GeneralistAgent ( this . config ) ) ;
2025-09-30 17:00:54 -04:00
}
2025-12-30 18:11:51 -05:00
private async refreshAgents ( ) : Promise < void > {
2025-12-17 22:46:55 -05:00
this . loadBuiltInAgents ( ) ;
2025-12-30 18:11:51 -05:00
await Promise . allSettled (
Array . from ( this . agents . values ( ) ) . map ( ( agent ) = >
this . registerAgent ( agent ) ,
) ,
) ;
2025-12-17 22:46:55 -05:00
}
2025-09-30 17:00:54 -04:00
/**
* Registers an agent definition. If an agent with the same name exists,
* it will be overwritten, respecting the precedence established by the
* initialization order.
*/
2025-12-30 18:11:51 -05:00
protected async registerAgent < TOutput extends z.ZodTypeAny > (
definition : AgentDefinition < TOutput > ,
) : Promise < void > {
if ( definition . kind === 'local' ) {
this . registerLocalAgent ( definition ) ;
} else if ( definition . kind === 'remote' ) {
await this . registerRemoteAgent ( definition ) ;
}
}
/**
* Registers a local agent definition synchronously.
*/
protected registerLocalAgent < TOutput extends z.ZodTypeAny > (
2025-10-03 13:21:08 -04:00
definition : AgentDefinition < TOutput > ,
) : void {
2025-12-30 18:11:51 -05:00
if ( definition . kind !== 'local' ) {
return ;
}
2025-09-30 17:00:54 -04:00
// Basic validation
if ( ! definition . name || ! definition . description ) {
2025-10-21 16:35:22 -04:00
debugLogger . warn (
2025-09-30 17:00:54 -04:00
` [AgentRegistry] Skipping invalid agent definition. Missing name or description. ` ,
) ;
return ;
}
2026-01-13 14:31:34 -08:00
const settingsOverrides =
2026-01-13 12:16:02 -08:00
this . config . getAgentsSettings ( ) . overrides ? . [ definition . name ] ;
2026-01-16 09:21:13 -08:00
if ( ! this . isAgentEnabled ( definition , settingsOverrides ) ) {
2026-01-13 12:16:02 -08:00
if ( this . config . getDebugMode ( ) ) {
debugLogger . log (
` [AgentRegistry] Skipping disabled agent ' ${ definition . name } ' ` ,
) ;
}
return ;
}
2025-09-30 17:00:54 -04:00
if ( this . agents . has ( definition . name ) && this . config . getDebugMode ( ) ) {
2025-10-21 16:35:22 -04:00
debugLogger . log ( ` [AgentRegistry] Overriding agent ' ${ definition . name } ' ` ) ;
2025-09-30 17:00:54 -04:00
}
2026-01-13 14:31:34 -08:00
const mergedDefinition = this . applyOverrides ( definition , settingsOverrides ) ;
2026-01-13 12:16:02 -08:00
this . agents . set ( mergedDefinition . name , mergedDefinition ) ;
2025-11-19 20:41:16 -08:00
2026-01-13 14:31:34 -08:00
this . registerModelConfigs ( mergedDefinition ) ;
2025-12-30 18:11:51 -05:00
}
2025-12-17 12:06:38 -05:00
2026-01-16 09:21:13 -08:00
private isAgentEnabled < TOutput extends z.ZodTypeAny > (
definition : AgentDefinition < TOutput > ,
overrides? : AgentOverride ,
) : boolean {
const isExperimental = definition . experimental === true ;
let isEnabled = ! isExperimental ;
if ( overrides ) {
if ( overrides . disabled !== undefined ) {
isEnabled = ! overrides . disabled ;
} else if ( overrides . enabled !== undefined ) {
isEnabled = overrides . enabled ;
}
}
return isEnabled ;
}
2025-12-30 18:11:51 -05:00
/**
* Registers a remote agent definition asynchronously.
*/
protected async registerRemoteAgent < TOutput extends z.ZodTypeAny > (
definition : AgentDefinition < TOutput > ,
) : Promise < void > {
if ( definition . kind !== 'remote' ) {
return ;
}
// Basic validation
if ( ! definition . name || ! definition . description ) {
debugLogger . warn (
` [AgentRegistry] Skipping invalid agent definition. Missing name or description. ` ,
2025-12-17 12:06:38 -05:00
) ;
2025-12-30 18:11:51 -05:00
return ;
}
2026-01-13 12:16:02 -08:00
const overrides =
this . config . getAgentsSettings ( ) . overrides ? . [ definition . name ] ;
2026-01-16 09:21:13 -08:00
if ( ! this . isAgentEnabled ( definition , overrides ) ) {
2026-01-13 12:16:02 -08:00
if ( this . config . getDebugMode ( ) ) {
debugLogger . log (
` [AgentRegistry] Skipping disabled remote agent ' ${ definition . name } ' ` ,
) ;
}
return ;
}
2025-12-30 18:11:51 -05:00
if ( this . agents . has ( definition . name ) && this . config . getDebugMode ( ) ) {
debugLogger . log ( ` [AgentRegistry] Overriding agent ' ${ definition . name } ' ` ) ;
2025-12-17 12:06:38 -05:00
}
2025-11-19 20:41:16 -08:00
2025-12-29 19:12:16 -05:00
// Log remote A2A agent registration for visibility.
2025-12-30 18:11:51 -05:00
try {
const clientManager = A2AClientManager . getInstance ( ) ;
2026-01-07 17:46:37 -05:00
// Use ADCHandler to ensure we can load agents hosted on secure platforms (e.g. Vertex AI)
const authHandler = new ADCHandler ( ) ;
2025-12-30 18:11:51 -05:00
const agentCard = await clientManager . loadAgent (
definition . name ,
definition . agentCardUrl ,
2026-01-07 17:46:37 -05:00
authHandler ,
2025-12-30 18:11:51 -05:00
) ;
if ( agentCard . skills && agentCard . skills . length > 0 ) {
definition . description = agentCard . skills
. map (
( skill : { name : string ; description : string } ) = >
` ${ skill . name } : ${ skill . description } ` ,
)
. join ( '\n' ) ;
}
if ( this . config . getDebugMode ( ) ) {
debugLogger . log (
` [AgentRegistry] Registered remote agent ' ${ definition . name } ' with card: ${ definition . agentCardUrl } ` ,
) ;
}
this . agents . set ( definition . name , definition ) ;
} catch ( e ) {
debugLogger . warn (
` [AgentRegistry] Error loading A2A agent " ${ definition . name } ": ` ,
e ,
2025-12-29 19:12:16 -05:00
) ;
}
2025-09-30 17:00:54 -04:00
}
2026-01-13 14:31:34 -08:00
private applyOverrides < TOutput extends z.ZodTypeAny > (
definition : LocalAgentDefinition < TOutput > ,
overrides? : AgentOverride ,
) : LocalAgentDefinition < TOutput > {
if ( definition . kind !== 'local' || ! overrides ) {
return definition ;
}
2026-01-16 09:21:13 -08:00
// Use Object.create to preserve lazy getters on the definition object
const merged : LocalAgentDefinition < TOutput > = Object . create ( definition ) ;
if ( overrides . runConfig ) {
merged . runConfig = {
2026-01-13 14:31:34 -08:00
. . . definition . runConfig ,
. . . overrides . runConfig ,
2026-01-16 09:21:13 -08:00
} ;
}
if ( overrides . modelConfig ) {
merged . modelConfig = ModelConfigService . merge (
2026-01-13 14:31:34 -08:00
definition . modelConfig ,
2026-01-16 09:21:13 -08:00
overrides . modelConfig ,
) ;
}
return merged ;
2026-01-13 14:31:34 -08:00
}
private registerModelConfigs < TOutput extends z.ZodTypeAny > (
definition : LocalAgentDefinition < TOutput > ,
) : void {
const modelConfig = definition . modelConfig ;
let model = modelConfig . model ;
if ( model === 'inherit' ) {
model = this . config . getModel ( ) ;
}
const agentModelConfig : ModelConfig = {
. . . modelConfig ,
model ,
} ;
this . config . modelConfigService . registerRuntimeModelConfig (
getModelConfigAlias ( definition ) ,
{
modelConfig : agentModelConfig ,
} ,
) ;
if ( agentModelConfig . model && isAutoModel ( agentModelConfig . model ) ) {
this . config . modelConfigService . registerRuntimeModelOverride ( {
match : {
overrideScope : definition.name ,
} ,
modelConfig : {
generateContentConfig : agentModelConfig.generateContentConfig ,
} ,
} ) ;
}
}
2025-09-30 17:00:54 -04:00
/**
* Retrieves an agent definition by name.
*/
getDefinition ( name : string ) : AgentDefinition | undefined {
return this . agents . get ( name ) ;
}
/**
* Returns all active agent definitions.
*/
getAllDefinitions ( ) : AgentDefinition [ ] {
return Array . from ( this . agents . values ( ) ) ;
}
2025-12-10 16:14:13 -05:00
/**
* Returns a list of all registered agent names.
*/
getAllAgentNames ( ) : string [ ] {
return Array . from ( this . agents . keys ( ) ) ;
}
/**
* Generates a description for the delegate_to_agent tool.
* Unlike getDirectoryContext() which is for system prompts,
* this is formatted for tool descriptions.
*/
getToolDescription ( ) : string {
if ( this . agents . size === 0 ) {
return 'Delegates a task to a specialized sub-agent. No agents are currently available.' ;
}
const agentDescriptions = Array . from ( this . agents . entries ( ) )
. map ( ( [ name , def ] ) = > ` - ** ${ name } **: ${ def . description } ` )
. join ( '\n' ) ;
2025-12-17 22:46:55 -05:00
return ` Delegates a task to a specialized sub-agent. \ n \ nAvailable agents: \ n ${ agentDescriptions } ` ;
2025-12-10 16:14:13 -05:00
}
/**
* Generates a markdown "Phone Book" of available agents and their schemas.
* This MUST be injected into the System Prompt of the parent agent.
*/
getDirectoryContext ( ) : string {
if ( this . agents . size === 0 ) {
return 'No sub-agents are currently available.' ;
}
let context = '## Available Sub-Agents\n' ;
2026-01-16 16:51:10 +00:00
context += ` Sub-agents are specialized expert agents that you can use to assist you in
the completion of all or part of a task.
ALWAYS use \` ${ DELEGATE_TO_AGENT_TOOL_NAME } \` to delegate to a subagent if one
exists that has expertise relevant to your task.
For example:
- Prompt: 'Fix test', Description: 'An agent with expertise in fixing tests.' -> should use the sub-agent.
- Prompt: 'Update the license header', Description: 'An agent with expertise in licensing and copyright.' -> should use the sub-agent.
- Prompt: 'Diagram the architecture of the codebase', Description: 'Agent with architecture experience'. -> should use the sub-agent.
- Prompt: 'Implement a fix for [bug]' -> Should decompose the project into subtasks, which may utilize available agents like 'plan', 'validate', and 'fix-tests'.
The following are the available sub-agents: \ n \ n ` ;
2025-12-10 16:14:13 -05:00
2025-12-17 12:06:38 -05:00
for ( const [ name , def ] of this . agents ) {
2025-12-10 16:14:13 -05:00
context += ` - ** ${ name } **: ${ def . description } \ n ` ;
}
return context ;
}
2025-09-30 17:00:54 -04:00
}