// tools/safe-handler.ts — Generic error-boundary wrapper for MCP tool handlers.
// Catches any unhandled error and returns a graceful MCP error response
// instead of crashing the server. Includes execution timeout via Promise.race.
import { withLicenseGate, withUsageTracking } from './license-gate.js';
import { sanitizePath } from './sanitize-path.js';
/**
 * Sanitizes path-like fields in tool args before passing to handler.
 * Checks `projectPath` and `path` fields for directory traversal attacks.
 */
function sanitizeArgs(args) {
    if (args === null || typeof args !== 'object') {
        return args;
    }
    const obj = args;
    if (typeof obj.projectPath === 'string') {
        obj.projectPath = sanitizePath(obj.projectPath);
    }
    if (typeof obj.path === 'string') {
        obj.path = sanitizePath(obj.path);
    }
    return args;
}
/** Default timeout: 60 seconds. Override via PLANU_TOOL_TIMEOUT_MS env var. */
const DEFAULT_TIMEOUT_MS = 60_000;
function getTimeoutMs() {
    const envVal = process.env.PLANU_TOOL_TIMEOUT_MS;
    if (envVal === undefined || envVal === '') {
        return DEFAULT_TIMEOUT_MS;
    }
    const parsed = Number(envVal);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
}
/**
 * Races a handler promise against a timeout. Returns a timeout error ToolResult
 * if the handler exceeds the configured duration.
 */
async function withTimeout(fn, ms) {
    let timer;
    const timeoutPromise = new Promise((_resolve, reject) => {
        timer = setTimeout(() => {
            reject(new Error(`Tool execution timed out after ${ms}ms`));
        }, ms);
    });
    try {
        return await Promise.race([fn(), timeoutPromise]);
    }
    finally {
        clearTimeout(timer);
    }
}
/**
 * Wraps a tool handler so that any unhandled error is caught and returned as a
 * graceful MCP error response, preventing server crashes. Also enforces a
 * configurable execution timeout (default 60s, env PLANU_TOOL_TIMEOUT_MS).
 *
 * Usage — only for alwaysAllowed tools (activate_license, license_status, etc.):
 *   server.registerTool('my_tool', schema, safe(async (args) => { ... }));
 */
export function safe(handler) {
    return async (args) => {
        try {
            const safeArgs = sanitizeArgs(args);
            return await withTimeout(() => handler(safeArgs), getTimeoutMs());
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return {
                content: [{ type: 'text', text: `Error: ${message}` }],
                isError: true,
            };
        }
    };
}
/**
 * Composable wrapper: usage tracking + error boundary.
 * Use for free-tier tools that need rate limiting and usage recording.
 *
 * Usage:
 *   server.registerTool('free_tool', schema, safeTracked('free_tool', async (args) => { ... }));
 */
export function safeTracked(toolName, handler) {
    return safe(withUsageTracking(toolName, handler));
}
/**
 * Composable wrapper: usage tracking + license gate + error boundary.
 * Use for tools that require a paid plan.
 *
 * Usage:
 *   server.registerTool('pro_tool', schema, safeLicensed('pro_tool', async (args) => { ... }));
 */
export function safeLicensed(toolName, handler) {
    return safe(withUsageTracking(toolName, withLicenseGate(toolName, handler)));
}
//# sourceMappingURL=safe-handler.js.map