import { safeJsonStringify, sanitizeLogMessage, sanitizeLogObject, } from "../../util/log-sanitizer.js";
import { scanContentForSecrets, SecretRedactor } from "../security/index.js";
import { getApiDocsToolFactory } from "./tools/apis/get-api-docs.js";
import { testApiToolFactory } from "./tools/apis/test-api.js";
import { buildReadFileToolFactory } from "./tools/build-read-file.js";
import { executeRequestToIntegrationToolFactory } from "./tools/index.js";
import { grepToolFactory } from "./tools2/tools/grep.js";
import { webFetchToolFactory } from "./tools2/tools/web-fetch.js";
import { ToolExecutionError } from "./tools2/types.js";
// I am seeing 1 character map to 2 tokens for these large JSON outputs in practice, so setting the limit
// here to 25k characters. 50k tokens is a huge output.
export const DEFAULT_MAX_OUTPUT_CHARS = 25_000;
/**
 * Middleware to limit the output length of a tool's execute method.
 * If the output exceeds the specified maxChars, an error is thrown that includes the truncated output.
 *
 * This is to prevent excessively large outputs that could lead to performance issues or exceed system limits.
 *
 * **Skipping the limit:**
 * Tools can provide a `skipOutputLimit` function that receives the output and returns true to bypass
 * this check. This is useful for tools that output content type with media parts (e.g., screenshots),
 * as media parts are handled separately by the AI SDK and don't contribute to token count in the
 * same way as text/JSON outputs.
 */
export function outputLimiter(maxChars = DEFAULT_MAX_OUTPUT_CHARS) {
    return (tool) => {
        return {
            ...tool,
            execute: async (input, options) => {
                const result = await tool.execute(input, options);
                // Skip limit check if tool's skipOutputLimit function returns true
                if (tool.skipOutputLimit && tool.skipOutputLimit(result)) {
                    return result;
                }
                // Check the output length
                const resultString = safeJsonStringify(result);
                if (resultString.length > maxChars) {
                    const truncated = resultString.slice(0, maxChars);
                    return `Output too long (${resultString.length} chars). Max output size is ${maxChars} characters. Result:
<result>
${truncated}
</result>`;
                }
                return result;
            },
        };
    };
}
/**
 * Tools that should have secret scanning applied.
 *
 * Only these tools can return data that may contain secrets:
 * - build_readFile: reads file contents which may contain secrets
 * - testApi: executes APIs that may return sensitive data from integrations
 * - executeRequestToIntegration: queries integrations directly
 */
const SECRET_SCAN_ENABLED_TOOLS = new Set([
    buildReadFileToolFactory.name,
    testApiToolFactory.name,
    executeRequestToIntegrationToolFactory.name,
]);
const TOOL_LOG_SUMMARY_NAMES = {
    buildReadFile: buildReadFileToolFactory.name,
    executeRequestToIntegration: executeRequestToIntegrationToolFactory.name,
    getApiDocs: getApiDocsToolFactory.name,
    grep: grepToolFactory.name,
    testApi: testApiToolFactory.name,
    webFetch: webFetchToolFactory.name,
};
/**
 * Middleware to scan and redact secrets from tool outputs.
 *
 * Only scans outputs from tools that may return sensitive data (file reads,
 * API tests, integration queries). Other tools are passed through without scanning.
 *
 * @param options - Configuration options
 * @param options.enabled - Whether secret scanning is enabled (default: true)
 * @param options.timeout - Timeout for secret scanning in ms (default: scanner's DEFAULT_SCAN_TIMEOUT_MS)
 */
export function secretScanningMiddleware(options) {
    const { enabled = true, timeout } = options ?? {};
    return (tool) => {
        // If disabled globally, return the tool unmodified
        if (!enabled) {
            return tool;
        }
        // Only scan tools that may return sensitive data
        if (!SECRET_SCAN_ENABLED_TOOLS.has(tool.name)) {
            return tool;
        }
        return {
            ...tool,
            execute: async (input, executeOptions) => {
                const result = await tool.execute(input, executeOptions);
                // Convert result to string for scanning
                const resultString = safeJsonStringify(result);
                // Scan the result for secrets
                const scanResult = await scanContentForSecrets(resultString, {
                    timeout,
                });
                // If no secrets found, return original result
                if (!scanResult.hasSecrets) {
                    return result;
                }
                // Redact secrets from the result
                const redactor = new SecretRedactor(scanResult.findings);
                return redactor.redactDeep(result);
            },
        };
    };
}
/**
 * Extracts error metadata safe for Tier 2 logging.
 * Excludes stack traces (contain file paths) and response bodies (may contain secrets).
 *
 * Contrast with `getErrorMeta` from logger.ts which preserves stack traces
 * for local/Tier 1 logging.
 *
 * @param error - The error to extract metadata from
 * @returns Error metadata suitable for Tier 2 logging (no stack traces)
 */
export function getTier2ErrorMeta(error) {
    if (error instanceof Error) {
        return {
            error: {
                kind: error.name,
                message: sanitizeLogMessage(error.message),
                // No stack trace - contains file paths (Tier 1 only)
            },
        };
    }
    return {
        error: {
            kind: "Unknown Error",
            message: sanitizeLogMessage(String(error)),
        },
    };
}
function summarizeToolResultForLogs(toolName, result) {
    const sanitizedResult = sanitizeLogObject(result);
    const serializedSanitizedResult = JSON.stringify(sanitizedResult);
    const fallbackSummary = (type) => ({
        fallback: true,
        outputChars: serializedSanitizedResult?.length ?? 0,
        type,
    });
    if (toolName === TOOL_LOG_SUMMARY_NAMES.buildReadFile) {
        if (typeof sanitizedResult !== "string") {
            return fallbackSummary(Array.isArray(sanitizedResult) ? "array" : typeof sanitizedResult);
        }
        const rangeMatches = [...sanitizedResult.matchAll(/<content range="/g)];
        const totalLinesMatch = sanitizedResult.match(/<file lines="(\d+)">/);
        return {
            outputChars: sanitizedResult.length,
            rangeCount: rangeMatches.length,
            totalLines: totalLinesMatch?.[1] ? Number(totalLinesMatch[1]) : undefined,
        };
    }
    if (toolName === TOOL_LOG_SUMMARY_NAMES.executeRequestToIntegration) {
        if (!sanitizedResult ||
            typeof sanitizedResult !== "object" ||
            Array.isArray(sanitizedResult)) {
            return fallbackSummary(Array.isArray(sanitizedResult) ? "array" : typeof sanitizedResult);
        }
        const root = sanitizedResult;
        const outputValue = root.output;
        const outputRecord = outputValue &&
            typeof outputValue === "object" &&
            !Array.isArray(outputValue)
            ? outputValue
            : undefined;
        const records = outputRecord && Array.isArray(outputRecord.records)
            ? outputRecord.records
            : undefined;
        return {
            hasOutput: outputValue !== undefined,
            isPartial: root.isPartial === true,
            outputChars: JSON.stringify(outputValue)?.length ?? 0,
            recordCount: Array.isArray(outputValue)
                ? outputValue.length
                : records?.length,
        };
    }
    if (toolName === TOOL_LOG_SUMMARY_NAMES.grep) {
        if (!sanitizedResult ||
            typeof sanitizedResult !== "object" ||
            Array.isArray(sanitizedResult)) {
            return fallbackSummary(Array.isArray(sanitizedResult) ? "array" : typeof sanitizedResult);
        }
        const root = sanitizedResult;
        const outputValue = JSON.stringify(root.output);
        return {
            matches: root.matches,
            outputChars: outputValue?.length ?? 0,
        };
    }
    if (toolName === TOOL_LOG_SUMMARY_NAMES.webFetch) {
        if (!sanitizedResult ||
            typeof sanitizedResult !== "object" ||
            Array.isArray(sanitizedResult)) {
            return fallbackSummary(Array.isArray(sanitizedResult) ? "array" : typeof sanitizedResult);
        }
        const root = sanitizedResult;
        const content = typeof root.content === "string" ? root.content : undefined;
        return {
            contentChars: content?.length,
            isRedirect: root.type === "redirect",
            redirectUrl: root.type === "redirect" && typeof root.redirectUrl === "string"
                ? root.redirectUrl
                : undefined,
        };
    }
    if (toolName === TOOL_LOG_SUMMARY_NAMES.getApiDocs) {
        if (!sanitizedResult ||
            typeof sanitizedResult !== "object" ||
            Array.isArray(sanitizedResult)) {
            return fallbackSummary(Array.isArray(sanitizedResult) ? "array" : typeof sanitizedResult);
        }
        const root = sanitizedResult;
        const documentation = typeof root.documentation === "string" ? root.documentation : undefined;
        const sectionsIncluded = Array.isArray(root.sectionsIncluded)
            ? root.sectionsIncluded
            : undefined;
        return {
            documentationChars: documentation?.length,
            integrationCount: root.integrationCount,
            sectionsCount: sectionsIncluded?.length,
            verbosity: root.verbosity,
        };
    }
    if (toolName !== TOOL_LOG_SUMMARY_NAMES.testApi) {
        return sanitizedResult;
    }
    if (!sanitizedResult ||
        typeof sanitizedResult !== "object" ||
        Array.isArray(sanitizedResult)) {
        return sanitizedResult;
    }
    const root = sanitizedResult;
    const resultObject = root.result &&
        typeof root.result === "object" &&
        !Array.isArray(root.result)
        ? root.result
        : undefined;
    const stepOutputs = resultObject?.stepOutputs &&
        typeof resultObject.stepOutputs === "object" &&
        !Array.isArray(resultObject.stepOutputs)
        ? resultObject.stepOutputs
        : undefined;
    return {
        apiName: root.apiName,
        message: root.message,
        result: resultObject
            ? {
                errorCount: Array.isArray(resultObject.errors)
                    ? resultObject.errors.length
                    : undefined,
                executionTime: resultObject.executionTime,
                hasOutputs: resultObject.outputs !== undefined && resultObject.outputs !== null,
                hasStepOutputs: Boolean(stepOutputs),
                stepOutputCount: stepOutputs ? Object.keys(stepOutputs).length : 0,
                success: resultObject.success,
            }
            : undefined,
    };
}
/**
 * Middleware to log tool execution with safe error handling.
 *
 * Tool inputs and outputs are logged through the wrapped logger, which applies
 * regex-based secret sanitization (sanitizeLogMessage/sanitizeLogObject) automatically.
 * Heavy secret scanning (Trufflehog) is intentionally NOT done on inputs here --
 * tool inputs are LLM-generated and the user prompt is already scanned upstream.
 *
 * @param logger - Logger instance to use for logging
 * @returns ToolMiddleware that logs tool execution
 */
export function toolLoggingMiddleware(logger) {
    return (tool) => {
        return {
            ...tool,
            async execute(input, executeOptions) {
                logger.info(`[tool:${tool.name}] ●`, input);
                try {
                    const result = await tool.execute?.(input, executeOptions);
                    logger.info(`[tool:${tool.name}] ✔`, summarizeToolResultForLogs(tool.name, result));
                    return result;
                }
                catch (error) {
                    // Use Tier 2 error metadata (no stack traces, no sensitive context)
                    const errorMeta = getTier2ErrorMeta(error);
                    const message = `[tool:${tool.name}] ✗ ${errorMeta.error.message}`;
                    const severity = error instanceof ToolExecutionError ? error.severity : "error";
                    logger[severity](message, errorMeta);
                    throw error;
                }
            },
        };
    };
}
//# sourceMappingURL=middleware.js.map