/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { ToolErrorType } from './tool-error.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { PolicyDecision } from '../policy/types.js';
export { ToolConfirmationOutcome } from './tool-confirmation-types.js';
/**
 * A convenience base class for ToolInvocation.
 */
export class BaseToolInvocation {
    params;
    messageBus;
    constructor(params, messageBus) {
        this.params = params;
        this.messageBus = messageBus;
    }
    toolLocations() {
        return [];
    }
    /**
     * Returns confirmation details for this tool invocation.
     * Tools that require confirmation should override this method.
     * @returns Confirmation details or null if no confirmation needed.
     */
    getConfirmationDetails() {
        return null;
    }
    /**
     * Requests confirmation through the message bus and policy engine.
     * This integrates with the new policy-based confirmation flow.
     * @param abortSignal Signal to abort the confirmation request
     * @returns Decision from policy engine with requiresUserConfirmation flag
     */
    async getMessageBusDecision(abortSignal) {
        // Default implementation defers to legacy confirmation flow.
        // Concrete tools can override for custom behavior.
        if (abortSignal.aborted) {
            throw new Error('Operation aborted');
        }
        return {
            decision: PolicyDecision.ASK_USER,
            requiresUserConfirmation: true,
        };
    }
    /**
     * Returns the tool name for this invocation.
     * Subclasses can override to provide a specific tool name.
     */
    getToolName() {
        return 'unknown';
    }
    /**
     * Returns the server name for MCP tools.
     * Regular tools should return undefined.
     */
    getServerName() {
        return undefined;
    }
    /**
     * Returns metadata used by the policy engine/message bus.
     */
    getPolicyContext() {
        return {
            toolName: this.getToolName(),
            args: this.params,
            serverName: this.getServerName(),
        };
    }
    shouldConfirmExecute(_abortSignal) {
        return Promise.resolve(false);
    }
}
/**
 * New base class for tools that separates validation from execution.
 * New tools should extend this class.
 */
export class DeclarativeTool {
    name;
    displayName;
    description;
    kind;
    parameterSchema;
    isOutputMarkdown;
    canUpdateOutput;
    messageBus;
    constructor(name, displayName, description, kind, parameterSchema, isOutputMarkdown = true, canUpdateOutput = false, messageBus) {
        this.name = name;
        this.displayName = displayName;
        this.description = description;
        this.kind = kind;
        this.parameterSchema = parameterSchema;
        this.isOutputMarkdown = isOutputMarkdown;
        this.canUpdateOutput = canUpdateOutput;
        this.messageBus = messageBus;
    }
    /**
     * Sets the message bus for this tool.
     * Called by ToolRegistry after tool construction.
     */
    setMessageBus(messageBus) {
        this.messageBus = messageBus;
    }
    get schema() {
        // Strip requireOne from the schema before sending to the model
        // The requireOne property is used internally for validation but not sent to the model
        if (this.parameterSchema && typeof this.parameterSchema === 'object') {
            const schemaClone = {
                ...this.parameterSchema,
            };
            delete schemaClone.requireOne;
            return {
                name: this.name,
                description: this.description,
                parametersJsonSchema: schemaClone,
            };
        }
        return {
            name: this.name,
            description: this.description,
            parametersJsonSchema: this.parameterSchema,
        };
    }
    /**
     * Validates the raw tool parameters.
     * Subclasses should override this to add custom validation logic
     * beyond the JSON schema check.
     * @param params The raw parameters from the model.
     * @returns An error message string if invalid, null otherwise.
     */
    validateToolParams(_params) {
        // Base implementation can be extended by subclasses.
        return null;
    }
    /**
     * A convenience method that builds and executes the tool in one step.
     * Throws an error if validation fails.
     * @param params The raw, untrusted parameters from the model.
     * @param signal AbortSignal for tool cancellation.
     * @param updateOutput Optional callback to stream output.
     * @returns The result of the tool execution.
     */
    async buildAndExecute(params, signal, updateOutput) {
        const invocation = this.build(params);
        return invocation.execute(signal, updateOutput);
    }
    /**
     * Similar to `build` but never throws.
     * @param params The raw, untrusted parameters from the model.
     * @returns A `ToolInvocation` instance.
     */
    silentBuild(params) {
        try {
            return this.build(params);
        }
        catch (e) {
            if (e instanceof Error) {
                return e;
            }
            return new Error(String(e));
        }
    }
    /**
     * A convenience method that builds and executes the tool in one step.
     * Never throws.
     * @param params The raw, untrusted parameters from the model.
     * @params abortSignal a signal to abort.
     * @returns The result of the tool execution.
     */
    async validateBuildAndExecute(params, abortSignal) {
        const invocationOrError = this.silentBuild(params);
        if (invocationOrError instanceof Error) {
            const errorMessage = invocationOrError.message;
            return {
                llmContent: `Error: Invalid parameters provided. Reason: ${errorMessage}`,
                returnDisplay: errorMessage,
                error: {
                    message: errorMessage,
                    type: ToolErrorType.INVALID_TOOL_PARAMS,
                },
            };
        }
        try {
            return await invocationOrError.execute(abortSignal);
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                llmContent: `Error: Tool call execution failed. Reason: ${errorMessage}`,
                returnDisplay: errorMessage,
                error: {
                    message: errorMessage,
                    type: ToolErrorType.EXECUTION_FAILED,
                },
            };
        }
    }
}
/**
 * New base class for declarative tools that separates validation from execution.
 * New tools should extend this class, which provides a `build` method that
 * validates parameters before deferring to a `createInvocation` method for
 * the final `ToolInvocation` object instantiation.
 */
export class BaseDeclarativeTool extends DeclarativeTool {
    build(params) {
        const validationError = this.validateToolParams(params);
        if (validationError) {
            throw new Error(validationError);
        }
        return this.createInvocation(params, this.messageBus);
    }
    validateToolParams(params) {
        const errors = SchemaValidator.validate(this.schema.parametersJsonSchema, params);
        if (errors) {
            return errors;
        }
        return this.validateToolParamValues(params);
    }
    validateToolParamValues(_params) {
        // Base implementation can be extended by subclasses.
        return null;
    }
}
/**
 * Type guard to check if an object is a Tool.
 * @param obj The object to check.
 * @returns True if the object is a Tool, false otherwise.
 */
export function isTool(obj) {
    return (typeof obj === 'object' &&
        obj !== null &&
        'name' in obj &&
        'build' in obj &&
        typeof obj.build === 'function');
}
/**
 * Detects cycles in a JSON schemas due to `$ref`s.
 * @param schema The root of the JSON schema.
 * @returns `true` if a cycle is detected, `false` otherwise.
 */
export function hasCycleInSchema(schema) {
    function resolveRef(ref) {
        if (!ref.startsWith('#/')) {
            return null;
        }
        const path = ref.substring(2).split('/');
        let current = schema;
        for (const segment of path) {
            if (typeof current !== 'object' ||
                current === null ||
                !Object.prototype.hasOwnProperty.call(current, segment)) {
                return null;
            }
            current = current[segment];
        }
        return current;
    }
    function traverse(node, visitedRefs, pathRefs) {
        if (typeof node !== 'object' || node === null) {
            return false;
        }
        if (Array.isArray(node)) {
            for (const item of node) {
                if (traverse(item, visitedRefs, pathRefs)) {
                    return true;
                }
            }
            return false;
        }
        if ('$ref' in node && typeof node.$ref === 'string') {
            const ref = node.$ref;
            if (ref === '#/' || pathRefs.has(ref)) {
                // A ref to just '#/' is always a cycle.
                return true; // Cycle detected!
            }
            if (visitedRefs.has(ref)) {
                return false; // Bail early, we have checked this ref before.
            }
            const resolvedNode = resolveRef(ref);
            if (resolvedNode) {
                // Add it to both visited and the current path
                visitedRefs.add(ref);
                pathRefs.add(ref);
                const hasCycle = traverse(resolvedNode, visitedRefs, pathRefs);
                pathRefs.delete(ref); // Backtrack, leaving it in visited
                return hasCycle;
            }
        }
        // Crawl all the properties of node
        for (const key in node) {
            if (Object.prototype.hasOwnProperty.call(node, key)) {
                if (traverse(node[key], visitedRefs, pathRefs)) {
                    return true;
                }
            }
        }
        return false;
    }
    return traverse(schema, new Set(), new Set());
}
export var Kind;
(function (Kind) {
    Kind["Read"] = "read";
    Kind["Edit"] = "edit";
    Kind["Delete"] = "delete";
    Kind["Move"] = "move";
    Kind["Search"] = "search";
    Kind["Execute"] = "execute";
    Kind["Think"] = "think";
    Kind["Fetch"] = "fetch";
    Kind["Other"] = "other";
})(Kind || (Kind = {}));
/**
 * Legacy BaseTool class for backward compatibility with existing LLxprt tools.
 * New tools should use BaseDeclarativeTool instead.
 * @deprecated Use BaseDeclarativeTool for new tools
 */
export class BaseTool extends DeclarativeTool {
    context;
    constructor(name, displayName, description, kind, parameterSchema, isOutputMarkdown = true, canUpdateOutput = false) {
        super(name, displayName, description, kind, parameterSchema, isOutputMarkdown, canUpdateOutput);
    }
    /**
     * Get the schema for this tool. Maintains backward compatibility.
     */
    get schema() {
        return super.schema;
    }
    /**
     * Legacy method signature for getDescription.
     * Subclasses can override this method.
     */
    getDescription(_params) {
        return `Execute ${this.displayName}`;
    }
    /**
     * Legacy method signature for validateToolParams.
     * Subclasses can override this method.
     * Note: The base class expects TParams, but legacy tools pass unknown.
     */
    validateToolParams(params) {
        return super.validateToolParams(params);
    }
    /**
     * Implementation of the new declarative pattern.
     * Creates a tool invocation that bridges to the legacy execute method.
     */
    build(params) {
        const validationError = this.validateToolParams(params);
        if (validationError) {
            throw new Error(validationError);
        }
        return new BaseToolLegacyInvocation(this, params, this.messageBus);
    }
}
/**
 * Tool invocation wrapper for BaseTool legacy API.
 */
class BaseToolLegacyInvocation extends BaseToolInvocation {
    tool;
    constructor(tool, params, messageBus) {
        super(params, messageBus);
        this.tool = tool;
    }
    getDescription() {
        return this.tool.getDescription(this.params);
    }
    getToolName() {
        return this.tool.name;
    }
    async execute(signal, updateOutput) {
        return this.tool.execute(this.params, signal, updateOutput);
    }
}
//# sourceMappingURL=tools.js.map