"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.callServerTool = callServerTool;
exports.toolActivities = toolActivities;
const logger_1 = require("../../../lib/logger");
const defaults_1 = require("../../../modules/defaults");
const context_1 = require("../../iam/context");
const mcpDbService = __importStar(require("../db"));
const connection_1 = require("./connection");
/**
 * Derive auth context from the ambient ToolContext (AsyncLocalStorage).
 * Returns undefined when no ToolContext is active (e.g., direct CLI calls).
 */
function deriveAuthFromToolContext() {
    const ctx = (0, context_1.getToolContext)();
    if (!ctx?.principal.id)
        return undefined;
    return {
        userId: ctx.principal.id,
        delegationToken: ctx.credentials.delegationToken,
    };
}
/**
 * Call a tool on a connected server.
 * Resolves the server by ID or name, auto-connecting built-in servers.
 */
async function callServerTool(serverId, toolName, args, authContext) {
    logger_1.loggerRegistry.debug(`[lt-mcp:call] entering ${serverId}/${toolName} argKeys=[${Object.keys(args).join(',')}]`);
    // Resolve auth context before dispatch — both paths need it
    const resolvedAuth = authContext ?? deriveAuthFromToolContext();
    const toolArgs = resolvedAuth?.userId || resolvedAuth?.delegationToken
        ? { ...args, _auth: { userId: resolvedAuth.userId, token: resolvedAuth.delegationToken } }
        : args;
    // Audit: log tool invocation with principal identity
    const ctx = (0, context_1.getToolContext)();
    if (ctx?.principal.id) {
        logger_1.loggerRegistry.debug(`[lt-mcp:audit] ${toolName} on ${serverId} by ${ctx.principal.type}:${ctx.principal.id}`);
    }
    // Direct dispatch for built-in servers — bypasses MCP Client/Transport.
    // Each built-in server is a cached singleton; tool handlers are called
    // as plain functions. No transport contention under concurrent load.
    const builtin = await (0, connection_1.dispatchBuiltinTool)(serverId, toolName, toolArgs);
    if (builtin) {
        logger_1.loggerRegistry.debug(`[lt-mcp:call] leaving ${serverId}/${toolName} (builtin) resultKeys=[${typeof builtin.result === 'object' && builtin.result ? Object.keys(builtin.result).join(',') : 'raw'}]`);
        return builtin.result;
    }
    // External servers — use MCP Client/Transport with timeout guard
    const client = await (0, connection_1.resolveClient)(serverId);
    if (!client) {
        throw new Error(`MCP server ${serverId} is not connected`);
    }
    // Guard against hung transports: the MCP SDK timeout relies on the transport
    // to respond, which fails when InMemoryTransport is saturated under concurrency.
    // Promise.race ensures we throw on timeout regardless of transport state.
    const callPromise = client.callTool({ name: toolName, arguments: toolArgs }, undefined, { timeout: defaults_1.MCP_TOOL_TIMEOUT_MS });
    const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP tool ${serverId}/${toolName} timed out after ${defaults_1.MCP_TOOL_TIMEOUT_MS}ms`)), defaults_1.MCP_TOOL_TIMEOUT_MS));
    const result = await Promise.race([callPromise, timeoutPromise]);
    // Extract text content from MCP response
    if (Array.isArray(result.content)) {
        const textContent = result.content.find((c) => c.type === 'text');
        if (textContent && 'text' in textContent) {
            try {
                const parsed = JSON.parse(textContent.text);
                const isError = result.isError || ('error' in parsed);
                logger_1.loggerRegistry.debug(`[lt-mcp:call] leaving ${serverId}/${toolName} ok=${!isError} resultKeys=[${Object.keys(parsed).join(',')}]`);
                return parsed;
            }
            catch {
                // Non-JSON text — wrap as error object when isError flag is set
                // to prevent raw strings from being spread as character indices downstream.
                if (result.isError) {
                    logger_1.loggerRegistry.warn(`[lt-mcp:call] leaving ${serverId}/${toolName} error: ${textContent.text.slice(0, 200)}`);
                    return { error: textContent.text };
                }
                logger_1.loggerRegistry.debug(`[lt-mcp:call] leaving ${serverId}/${toolName} raw text (${textContent.text.length} chars)`);
                return textContent.text;
            }
        }
    }
    logger_1.loggerRegistry.debug(`[lt-mcp:call] leaving ${serverId}/${toolName} non-text content (${Array.isArray(result.content) ? result.content.length : 0} blocks)`);
    return result.content;
}
/**
 * Return a map of tool functions suitable for proxyActivities().
 *
 * Each tool is wrapped as:
 *   `mcp_{serverName}_{toolName}(args) => Promise<any>`
 *
 * Usage in a workflow:
 * ```typescript
 * const tools = await mcpClient.toolActivities(serverId);
 * const proxied = Durable.workflow.proxyActivities({ activities: tools });
 * const result = await proxied.mcp_myserver_search({ query: 'hello' });
 * ```
 */
async function toolActivities(serverId) {
    const server = await mcpDbService.getMcpServer(serverId);
    if (!server)
        throw new Error(`MCP server ${serverId} not found`);
    const tools = server.tool_manifest || await (0, connection_1.listServerTools)(serverId);
    const activities = {};
    const safeName = server.name.replace(/[^a-zA-Z0-9]/g, '_');
    for (const tool of tools) {
        const activityName = `mcp_${safeName}_${tool.name.replace(/[^a-zA-Z0-9]/g, '_')}`;
        activities[activityName] = async (args) => {
            return callServerTool(serverId, tool.name, args);
        };
    }
    return activities;
}
