import { getAppConfig } from '../config/index.js';
import { getBraveSearchApiKey } from '../config/nanocoder-tools-config.js';
import { buildToolEntry } from '../custom-tools/build-tool.js';
import { CustomToolLoader } from '../custom-tools/loader.js';
import { allToolExports } from '../tools/index.js';
import { getToolsForProfile, resolveToolProfile } from '../tools/tool-profiles.js';
import { ToolRegistry } from '../tools/tool-registry.js';
import { getShutdownManager } from '../utils/shutdown/index.js';
// Tools to exclude per development mode
const MODE_EXCLUDED_TOOLS = {
    normal: [],
    'auto-accept': [],
    yolo: [],
    plan: [
        // No mutation tools — plan mode is read-only exploration
        'write_file',
        'string_replace',
        'file_op',
        'execute_bash',
        // No task tool — plan mode produces the plan itself
        'write_tasks',
        // No git mutation tools — keep read-only git tools
        'git_add',
        'git_commit',
        'git_pr', // can create PRs — excluded like other git mutators
    ],
    headless: ['ask_user', 'agent'],
};
/**
 * Manages built-in tools, MCP tools, and file-based custom tools.
 * Single authority for tool availability, filtering, and approval policy.
 */
export class ToolManager {
    registry;
    mcpClient = null;
    customTools = new Map();
    constructor() {
        this.registry = ToolRegistry.fromToolExports(allToolExports);
        // Remove web_search if no Brave Search API key is configured
        if (!getBraveSearchApiKey()) {
            this.registry.unregister('web_search');
        }
    }
    /**
     * Initialize MCP servers and register their tools
     */
    async initializeMCP(servers, onProgress) {
        if (servers && servers.length > 0) {
            // Dynamic import — only paid for by sessions with configured MCP servers.
            const { MCPClient } = await import('../mcp/mcp-client.js');
            this.mcpClient = new MCPClient();
            getShutdownManager().register({
                name: 'mcp-client',
                priority: 20,
                handler: async () => {
                    await this.disconnectMCP();
                },
            });
            const results = await this.mcpClient.connectToServers(servers, onProgress);
            const mcpToolEntries = this.mcpClient.getToolEntries();
            this.registry.registerMany(mcpToolEntries);
            return results;
        }
        return [];
    }
    /**
     * Load file-based custom tools from `.nanocoder/tools/` (project) and
     * the personal tools directory. Project tools override personal ones.
     * Custom tools whose name collides with a built-in or already-registered
     * MCP tool are skipped with an error. Returns a per-tool load summary so
     * callers can surface counts/errors at startup.
     */
    initializeCustomTools(projectRoot) {
        const loader = new CustomToolLoader(projectRoot ?? process.cwd());
        const { tools, errors } = loader.load();
        const loaded = [];
        const collisions = [];
        for (const t of tools) {
            if (this.registry.hasTool(t.metadata.name)) {
                collisions.push({
                    file: t.filePath,
                    error: `Tool name "${t.metadata.name}" collides with a built-in or MCP tool — skipping.`,
                });
                continue;
            }
            const entry = buildToolEntry(t, loader.getProjectRoot());
            this.registry.register(entry);
            this.customTools.set(t.metadata.name, {
                approval: t.metadata.approval,
                readOnly: t.metadata.readOnly,
                source: t.source,
                filePath: t.filePath,
                subscribe: t.subscribe,
            });
            loaded.push(t.metadata.name);
        }
        return { loaded, errors: [...errors, ...collisions] };
    }
    // =========================================================================
    // Tool availability — single source of truth
    // =========================================================================
    /**
     * Get the list of tool names available given the current mode and tune config.
     * This is the single authority used by both prompt building and runtime.
     */
    getAvailableToolNames(tuneConfig, developmentMode, disabledTools, model) {
        let names = this.getToolNames();
        if (tuneConfig?.enabled) {
            const profile = resolveToolProfile(tuneConfig.toolProfile, model);
            if (profile !== 'full') {
                const profileTools = getToolsForProfile(profile);
                if (profileTools.length > 0) {
                    names = profileTools;
                }
            }
        }
        // Apply mode-based exclusions
        if (developmentMode) {
            const excluded = MODE_EXCLUDED_TOOLS[developmentMode];
            if (excluded.length > 0) {
                const excludeSet = new Set(excluded);
                names = names.filter(n => !excludeSet.has(n));
            }
            // Custom tools follow the same posture as built-ins but with policy
            // applied per-tool from their approval/readOnly metadata.
            if (developmentMode === 'plan' || developmentMode === 'headless') {
                names = names.filter(n => {
                    const meta = this.customTools.get(n);
                    if (!meta)
                        return true;
                    if (developmentMode === 'headless') {
                        return meta.approval === 'never';
                    }
                    // plan mode: only read-only tools with no approval are safe
                    return meta.approval === 'never' && meta.readOnly;
                });
            }
        }
        // Apply user-configured disable list (intersects with profile + mode).
        // Defaults to global app config; callers may pass an override (mainly
        // for tests). This is global policy every caller should observe.
        const disabled = disabledTools ?? getAppConfig().disabledTools;
        if (disabled && disabled.length > 0) {
            const disabledSet = new Set(disabled);
            names = names.filter(n => !disabledSet.has(n));
        }
        return names;
    }
    // =========================================================================
    // Tool access — delegates to ToolRegistry
    //
    // All accessors return execute-stripped AI SDK tool definitions: the SDK
    // never auto-executes, so the model only ever needs schemas/descriptions.
    // Execution runs through the registry handler (see getToolHandler), which
    // validates. Approval is decided separately by `resolveToolApproval`.
    // =========================================================================
    getAllTools(opts) {
        return this.applyVisibility(this.registry.getNativeTools(), opts);
    }
    getFilteredTools(allowedToolNames, opts) {
        return this.filterByNames(this.getAllTools(opts), allowedToolNames);
    }
    /**
     * Drop scoped tools unless the caller opts in by passing the owning
     * skill's name. Tools without `scoped` set (built-ins, MCP, custom
     * tools, single-file skill tools) pass through untouched.
     */
    applyVisibility(tools, opts) {
        const result = {};
        for (const [name, tool] of Object.entries(tools)) {
            const entry = this.registry.getEntry(name);
            if (entry?.scoped) {
                if (entry.ownerSkill && entry.ownerSkill === opts?.forSkill) {
                    result[name] = tool;
                }
                continue;
            }
            result[name] = tool;
        }
        return result;
    }
    filterByNames(tools, allowedNames) {
        const nameSet = new Set(allowedNames);
        const filtered = {};
        for (const [name, tool] of Object.entries(tools)) {
            if (nameSet.has(name)) {
                filtered[name] = tool;
            }
        }
        return filtered;
    }
    getToolRegistry() {
        return this.registry.getHandlers();
    }
    getToolHandler(toolName) {
        return this.registry.getHandler(toolName);
    }
    getToolFormatter(toolName) {
        return this.registry.getFormatter(toolName);
    }
    getToolValidator(toolName) {
        return this.registry.getValidator(toolName);
    }
    getStreamingFormatter(toolName) {
        return this.registry.getStreamingFormatter(toolName);
    }
    isReadOnly(toolName) {
        return this.registry.getEntry(toolName)?.readOnly === true;
    }
    hasTool(toolName) {
        return this.registry.hasTool(toolName);
    }
    /**
     * Register a skill-provided tool. Wraps the registry call so the
     * registrar doesn't have to reach through `ToolManager` into
     * `ToolRegistry`. The entry's `ownerSkill` tag is preserved.
     */
    registerSkillTool(entry) {
        this.registry.register(entry);
    }
    /**
     * Unregister a previously-registered skill-provided tool. Returns true
     * if the tool was found and removed.
     */
    unregisterSkillTool(toolName) {
        if (!this.registry.hasTool(toolName))
            return false;
        this.registry.unregister(toolName);
        return true;
    }
    /**
     * Return the tool entry's `ownerSkill` tag, if any. Used by callers
     * (e.g. `/tools`, scoped-visibility filters) to attribute a tool back
     * to the skill that registered it.
     */
    getOwnerSkill(toolName) {
        return this.registry.getEntry(toolName)?.ownerSkill;
    }
    isCustomTool(toolName) {
        return this.customTools.has(toolName);
    }
    getCustomToolInfo(toolName) {
        return this.customTools.get(toolName);
    }
    getCustomToolNames() {
        return Array.from(this.customTools.keys());
    }
    getMCPToolInfo(toolName) {
        if (!this.mcpClient) {
            return { isMCPTool: false };
        }
        const toolMapping = this.mcpClient.getToolMapping();
        const mapping = toolMapping.get(toolName);
        if (mapping) {
            return {
                isMCPTool: true,
                serverName: mapping.serverName,
            };
        }
        return { isMCPTool: false };
    }
    async disconnectMCP() {
        if (this.mcpClient) {
            const mcpTools = this.mcpClient.getNativeToolsRegistry();
            const mcpToolNames = Object.keys(mcpTools);
            this.registry.unregisterMany(mcpToolNames);
            await this.mcpClient.disconnect();
            // Reset registry to only static tools
            this.registry = ToolRegistry.fromToolExports(allToolExports);
            this.customTools.clear();
            this.mcpClient = null;
        }
        getShutdownManager().unregister('mcp-client');
    }
    getToolEntry(toolName) {
        return this.registry.getEntry(toolName);
    }
    getToolNames() {
        return this.registry.getToolNames();
    }
    getToolCount() {
        return this.registry.getToolCount();
    }
    getConnectedServers() {
        return this.mcpClient?.getConnectedServers() || [];
    }
    getServerTools(serverName) {
        return this.mcpClient?.getServerTools(serverName) || [];
    }
    getServerInfo(serverName) {
        return this.mcpClient?.getServerInfo(serverName);
    }
    getMCPClient() {
        return this.mcpClient;
    }
}
//# sourceMappingURL=tool-manager.js.map