/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { Storage } from '../config/storage.js';
import { coreEvents, CoreEvent } from '../utils/events.js';
import { loadAgentsFromDirectory } from './toml-loader.js';
import { CodebaseInvestigatorAgent } from './codebase-investigator.js';
import { IntrospectionAgent } from './introspection-agent.js';
import { A2AClientManager } from './a2a-client-manager.js';
import {} from 'zod';
import { debugLogger } from '../utils/debugLogger.js';
import { DEFAULT_GEMINI_MODEL, GEMINI_MODEL_ALIAS_AUTO, PREVIEW_GEMINI_FLASH_MODEL, isPreviewModel, } from '../config/models.js';
/**
 * Returns the model config alias for a given agent definition.
 */
export function getModelConfigAlias(definition) {
    return `${definition.name}-config`;
}
/**
 * Manages the discovery, loading, validation, and registration of
 * AgentDefinitions.
 */
export class AgentRegistry {
    config;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    agents = new Map();
    constructor(config) {
        this.config = config;
    }
    /**
     * Discovers and loads agents.
     */
    async initialize() {
        this.loadBuiltInAgents();
        coreEvents.on(CoreEvent.ModelChanged, () => {
            this.refreshAgents().catch((e) => {
                debugLogger.error('[AgentRegistry] Failed to refresh agents on model change:', e);
            });
        });
        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}`);
        }
        await Promise.allSettled(userAgents.agents.map((agent) => this.registerAgent(agent)));
        // 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}`);
            }
            await Promise.allSettled(projectAgents.agents.map((agent) => this.registerAgent(agent)));
        }
        else {
            coreEvents.emitFeedback('info', 'Skipping project agents due to untrusted folder. To enable, ensure that the project root is trusted.');
        }
        if (this.config.getDebugMode()) {
            debugLogger.log(`[AgentRegistry] Initialized with ${this.agents.size} agents.`);
        }
    }
    loadBuiltInAgents() {
        const investigatorSettings = this.config.getCodebaseInvestigatorSettings();
        const introspectionSettings = this.config.getIntrospectionAgentSettings();
        // Only register the agent if it's enabled in the settings.
        if (investigatorSettings?.enabled) {
            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;
            }
            const agentDef = {
                ...CodebaseInvestigatorAgent,
                modelConfig: {
                    ...CodebaseInvestigatorAgent.modelConfig,
                    model,
                    thinkingBudget: investigatorSettings.thinkingBudget ??
                        CodebaseInvestigatorAgent.modelConfig.thinkingBudget,
                },
                runConfig: {
                    ...CodebaseInvestigatorAgent.runConfig,
                    max_time_minutes: investigatorSettings.maxTimeMinutes ??
                        CodebaseInvestigatorAgent.runConfig.max_time_minutes,
                    max_turns: investigatorSettings.maxNumTurns ??
                        CodebaseInvestigatorAgent.runConfig.max_turns,
                },
            };
            this.registerLocalAgent(agentDef);
        }
        // Register the introspection agent if it's explicitly enabled.
        if (introspectionSettings.enabled) {
            this.registerLocalAgent(IntrospectionAgent);
        }
    }
    async refreshAgents() {
        this.loadBuiltInAgents();
        await Promise.allSettled(Array.from(this.agents.values()).map((agent) => this.registerAgent(agent)));
    }
    /**
     * Registers an agent definition. If an agent with the same name exists,
     * it will be overwritten, respecting the precedence established by the
     * initialization order.
     */
    async registerAgent(definition) {
        if (definition.kind === 'local') {
            this.registerLocalAgent(definition);
        }
        else if (definition.kind === 'remote') {
            await this.registerRemoteAgent(definition);
        }
    }
    /**
     * Registers a local agent definition synchronously.
     */
    registerLocalAgent(definition) {
        if (definition.kind !== 'local') {
            return;
        }
        // Basic validation
        if (!definition.name || !definition.description) {
            debugLogger.warn(`[AgentRegistry] Skipping invalid agent definition. Missing name or description.`);
            return;
        }
        if (this.agents.has(definition.name) && this.config.getDebugMode()) {
            debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
        }
        this.agents.set(definition.name, definition);
        // Register model config.
        // TODO(12916): Migrate sub-agents where possible to static configs.
        const modelConfig = definition.modelConfig;
        let model = modelConfig.model;
        if (model === 'inherit') {
            model = this.config.getModel();
        }
        const runtimeAlias = {
            modelConfig: {
                model,
                generateContentConfig: {
                    temperature: modelConfig.temp,
                    topP: modelConfig.top_p,
                    thinkingConfig: {
                        includeThoughts: true,
                        thinkingBudget: modelConfig.thinkingBudget ?? -1,
                    },
                },
            },
        };
        this.config.modelConfigService.registerRuntimeModelConfig(getModelConfigAlias(definition), runtimeAlias);
    }
    /**
     * Registers a remote agent definition asynchronously.
     */
    async registerRemoteAgent(definition) {
        if (definition.kind !== 'remote') {
            return;
        }
        // Basic validation
        if (!definition.name || !definition.description) {
            debugLogger.warn(`[AgentRegistry] Skipping invalid agent definition. Missing name or description.`);
            return;
        }
        if (this.agents.has(definition.name) && this.config.getDebugMode()) {
            debugLogger.log(`[AgentRegistry] Overriding agent '${definition.name}'`);
        }
        // Log remote A2A agent registration for visibility.
        try {
            const clientManager = A2AClientManager.getInstance();
            const agentCard = await clientManager.loadAgent(definition.name, definition.agentCardUrl);
            if (agentCard.skills && agentCard.skills.length > 0) {
                definition.description = agentCard.skills
                    .map((skill) => `${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);
        }
    }
    /**
     * Retrieves an agent definition by name.
     */
    getDefinition(name) {
        return this.agents.get(name);
    }
    /**
     * Returns all active agent definitions.
     */
    getAllDefinitions() {
        return Array.from(this.agents.values());
    }
    /**
     * Returns a list of all registered agent names.
     */
    getAllAgentNames() {
        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() {
        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');
        return `Delegates a task to a specialized sub-agent.\n\nAvailable agents:\n${agentDescriptions}`;
    }
    /**
     * Generates a markdown "Phone Book" of available agents and their schemas.
     * This MUST be injected into the System Prompt of the parent agent.
     */
    getDirectoryContext() {
        if (this.agents.size === 0) {
            return 'No sub-agents are currently available.';
        }
        let context = '## Available Sub-Agents\n';
        context +=
            'Use `delegate_to_agent` for complex tasks requiring specialized analysis.\n\n';
        for (const [name, def] of this.agents) {
            context += `- **${name}**: ${def.description}\n`;
        }
        return context;
    }
}
//# sourceMappingURL=registry.js.map