"use strict";
/**
 * LangChainAdapter -- Orchestrator adapter for the LangChain framework.
 *
 * Hook surface: LangChain's stable BaseCallbackHandler callback interface.
 * The adapter exposes a toCallbackHandler() factory that returns a
 * BaseCallbackHandler subclass. Consumers wire the returned handler into
 * their chain config:
 *
 *   const adapter = new LangChainAdapter(config);
 *   adapter.initialize(orchConfig);
 *   const chain = new LLMChain({ ..., callbacks: [adapter.toCallbackHandler()] });
 *
 * Agent support: both legacy AgentExecutor pattern and the newer
 * LangGraph-based create_react_agent. When the adapter detects a
 * LangGraph-based agent at dispatch time, it delegates to LangGraphAdapter
 * and emits orchestrator:framework-dispatch DEBUG at the handoff boundary.
 * No duplicate audit event is emitted for the same operation.
 *
 * Agent scratchpad auditing: when the chain invokes a tool, the scratchpad
 * contents from the run's metadata feed into TaskContext.
 *
 * No real @langchain/core package is installed. This adapter shims the
 * BaseCallbackHandler shape and can be exercised fully in tests without
 * the live SDK.
 *
 * Spec: P3-ORCH-01, Section 2.6, AC 12-13
 * @connexum/ai-governance
 */
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.LangChainAdapter = exports.LangChainCallbackHandler = void 0;
const crypto = __importStar(require("crypto"));
const base_1 = require("./base");
const index_1 = require("./index");
const index_2 = require("../adapters/index");
// ---------------------------------------------------------------------------
// LangChainCallbackHandler -- shimmed BaseCallbackHandler subclass
// ---------------------------------------------------------------------------
/**
 * Shimmed LangChain BaseCallbackHandler.
 *
 * Mirrors the stable interface from @langchain/core/callbacks/base:
 *   on_chain_start, on_chain_end, on_tool_start, on_tool_end, on_tool_error
 *
 * Consumers integrate via:
 *   const handler = adapter.toCallbackHandler();
 *   chain.invoke(input, { callbacks: [handler] });
 */
class LangChainCallbackHandler {
    /** The adapter this handler dispatches to */
    adapter;
    constructor(adapter) {
        this.adapter = adapter;
    }
    /** Maps to LangChain's on_chain_start. Governance approval check before chain runs. */
    async on_chain_start(event) {
        await this.adapter.handleChainStart(event);
    }
    /** Maps to LangChain's on_chain_end. Audit hook after chain completes. */
    async on_chain_end(event) {
        await this.adapter.handleChainEnd(event);
    }
    /** Maps to LangChain's on_tool_start. Governance check before tool invocation. */
    async on_tool_start(event) {
        await this.adapter.handleToolStart(event);
    }
    /** Maps to LangChain's on_tool_end. Audit hook after tool invocation. */
    async on_tool_end(event) {
        await this.adapter.handleToolEnd(event);
    }
    /** Maps to LangChain's on_tool_error. Error translation hook. */
    async on_tool_error(event) {
        await this.adapter.handleToolError(event);
    }
}
exports.LangChainCallbackHandler = LangChainCallbackHandler;
// ---------------------------------------------------------------------------
// LangChainAdapter
// ---------------------------------------------------------------------------
class LangChainAdapter extends base_1.BaseOrchestratorAdapter {
    adapterId = 'langchain';
    orchestratorId = 'langchain';
    langchainConfig;
    /**
     * Reference to the LangGraphAdapter for delegation when a LangGraph-based
     * agent is detected. Injected lazily; consumers can set this explicitly.
     */
    langgraphAdapter;
    /** Track active run IDs for concurrency enforcement */
    activeRunIds = new Set();
    /** C1 fix: track tool-start auditIds by runId so handleToolEnd can call postActionClassify */
    _toolAuditIds = new Map();
    constructor(config) {
        super();
        this.langchainConfig = config;
    }
    // ---------------------------------------------------------------------------
    // toCallbackHandler -- public factory for the callback handler
    // ---------------------------------------------------------------------------
    /**
     * Return a LangChainCallbackHandler shimmed to this adapter.
     * The consumer passes the returned handler into the chain's callbacks config.
     */
    toCallbackHandler() {
        this.assertInitialized();
        return new LangChainCallbackHandler(this);
    }
    // ---------------------------------------------------------------------------
    // Internal chain lifecycle dispatchers
    // ---------------------------------------------------------------------------
    /**
     * Governance check on chain start (on_chain_start).
     * If a LangGraph-based agent is detected, delegates to LangGraphAdapter
     * and emits orchestrator:framework-dispatch at the handoff boundary.
     * Does NOT emit a duplicate audit event for the same operation.
     */
    async handleChainStart(event) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        // LangGraph delegation -- handoff boundary
        if (event.isLangGraph && this.langgraphAdapter) {
            this.emitCanonical('orchestrator:framework-dispatch', {
                fromFramework: 'langchain',
                toFramework: 'langgraph',
                runId: event.runId,
                chainType: event.chainType,
                reason: 'LangGraph-based agent detected; delegating to LangGraphAdapter',
            });
            // Delegate: the LangGraphAdapter will emit its own canonical events
            const wfCtx = {
                workflowId: event.runId,
                name: event.chainType,
                initiator: { name: 'langchain-chain', sessionId: event.runId },
                timestamp: new Date().toISOString(),
                metadata: event.metadata,
            };
            const decision = await this.langgraphAdapter.startWorkflow(wfCtx);
            return {
                type: decision.type === 'ALLOW' ? index_2.ActionDecisionType.ALLOW : index_2.ActionDecisionType.DENY,
                reason: decision.reason,
                auditId: decision.auditId,
            };
        }
        // Circuit breaker
        if (this.isCircuitOpen()) {
            const reason = 'Circuit breaker is open; chain start denied';
            this.emitCanonical('orchestrator:task-denied', {
                runId: event.runId,
                chainType: event.chainType,
                reason,
            });
            this.recordFailure();
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        // Concurrency enforcement
        const maxRuns = this.langchainConfig.maxConcurrentRuns ?? Infinity;
        if (this.activeRunIds.size >= maxRuns) {
            const reason = `Maximum concurrent runs (${maxRuns}) reached; chain start denied`;
            this.emitCanonical('orchestrator:task-denied', {
                runId: event.runId,
                chainType: event.chainType,
                reason,
            });
            this.recordFailure();
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        // Chain type allowlist
        if (!this.langchainConfig.allowedChainTypes.includes(event.chainType)) {
            const reason = `Chain type "${event.chainType}" is not in the allowed list`;
            this.emitCanonical('orchestrator:task-denied', {
                runId: event.runId,
                chainType: event.chainType,
                reason,
            });
            this.recordFailure();
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        this.activeRunIds.add(event.runId);
        this.emitCanonical('orchestrator:workflow-start', {
            runId: event.runId,
            chainType: event.chainType,
        });
        this.recordSuccess();
        return {
            type: index_2.ActionDecisionType.ALLOW,
            reason: `Chain type "${event.chainType}" is allowed`,
            auditId,
        };
    }
    /** Audit hook on chain end (on_chain_end). */
    async handleChainEnd(event) {
        this.assertInitialized();
        this.activeRunIds.delete(event.runId);
        this.emitCanonical('orchestrator:workflow-complete', {
            runId: event.runId,
            outputKeys: Object.keys(event.outputs),
        });
    }
    /**
     * Governance check on tool start (on_tool_start).
     * Scratchpad contents from event.scratchpad feed into TaskContext.input.
     * Sensitive tools route to human-reviewer tier.
     */
    async handleToolStart(event) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const isSensitive = this.langchainConfig.sensitiveTools.includes(event.toolName);
        if (isSensitive) {
            // Sensitive tool: require approval via reviewer queue
            const tier = 'human-reviewer';
            const queueId = this.config.reviewerQueues[tier];
            if (!queueId) {
                // Fail closed: no queue binding
                this.emitCanonical('orchestrator:task-denied', {
                    runId: event.runId,
                    toolName: event.toolName,
                    reason: `Sensitive tool "${event.toolName}" requires human-reviewer tier but no queue binding exists`,
                });
                return {
                    type: index_2.ActionDecisionType.DENY,
                    reason: `Sensitive tool "${event.toolName}" requires human-reviewer tier but no queue binding exists`,
                    auditId,
                };
            }
            this.emitCanonical('orchestrator:reviewer-routed', {
                runId: event.runId,
                toolName: event.toolName,
                tier,
                queueId,
                scratchpadPresent: !!event.scratchpad,
            });
            return {
                type: index_2.ActionDecisionType.REQUIRE_APPROVAL,
                reason: `Tool "${event.toolName}" is in the sensitive tools list; routing to ${tier}`,
                auditId,
                metadata: {
                    reviewerTier: tier,
                    scratchpad: event.scratchpad,
                },
            };
        }
        this.emitCanonical('orchestrator:task-approved', {
            runId: event.runId,
            toolName: event.toolName,
            scratchpadPresent: !!event.scratchpad,
        });
        // C1 fix: store auditId so handleToolEnd can call postActionClassify
        this._toolAuditIds.set(event.runId, auditId);
        return {
            type: index_2.ActionDecisionType.ALLOW,
            reason: `Tool "${event.toolName}" is permitted`,
            auditId,
        };
    }
    /** Audit hook on tool end (on_tool_end).
     *
     * C1 fix: calls postActionClassify() to run G-series classification
     * on the tool output. Previously only emitted a canonical event with
     * no output screening.
     */
    async handleToolEnd(event) {
        this.assertInitialized();
        // C1 fix: retrieve audit ID for this run and classify the output
        const auditId = this._toolAuditIds.get(event.runId);
        if (auditId) {
            this._toolAuditIds.delete(event.runId);
            this.postActionClassify(auditId, event.output, event.toolName, this.config.packBinding ?? []);
        }
        this.emitCanonical('orchestrator:plan-step-end', {
            runId: event.runId,
            toolName: event.toolName,
            classified: !!auditId,
        });
    }
    /** Error translation on tool error (on_tool_error). */
    async handleToolError(event) {
        this.assertInitialized();
        this.recordFailure();
        const translated = this.translateError(event.error);
        this.emitCanonical('orchestrator:task-denied', {
            runId: event.runId,
            toolName: event.toolName,
            errorCode: translated.code,
            errorMessage: translated.message,
        });
    }
    // ---------------------------------------------------------------------------
    // OrchestratorAdapter surface -- workflow lifecycle
    // ---------------------------------------------------------------------------
    async startWorkflow(ctx) {
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:workflow-start', {
            workflowId: ctx.workflowId,
            name: ctx.name,
            initiator: ctx.initiator.name,
        });
        return { type: 'ALLOW', reason: 'Workflow started', auditId };
    }
    async stepWorkflow(ctx) {
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:plan-step-begin', {
            workflowId: ctx.workflowId,
            stepId: ctx.stepId,
            stepIndex: ctx.stepIndex,
        });
        if (this.isCircuitOpen()) {
            this.emitCanonical('orchestrator:circuit-breaker-tripped', {
                workflowId: ctx.workflowId,
                stepId: ctx.stepId,
            });
            return { type: 'DENY', reason: 'Circuit breaker is open', auditId };
        }
        return { type: 'ALLOW', reason: 'Step permitted', auditId };
    }
    async completeWorkflow(ctx, result) {
        this.emitCanonical('orchestrator:workflow-complete', {
            workflowId: ctx.workflowId,
            success: result.success,
            summary: result.summary,
        });
    }
    // ---------------------------------------------------------------------------
    // Task / plan / delegation surfaces
    // ---------------------------------------------------------------------------
    async preTask(task) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        if (this.isCircuitOpen()) {
            const reason = 'Circuit breaker is open; task denied';
            this.emitCanonical('orchestrator:task-denied', { taskId: task.taskId, reason });
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        this.emitCanonical('orchestrator:task-approved', { taskId: task.taskId });
        return {
            type: index_2.ActionDecisionType.ALLOW,
            reason: 'Task approved via LangChain adapter',
            auditId,
        };
    }
    async postTask(task, result) {
        this.emitCanonical('orchestrator:plan-step-end', { taskId: task.taskId, result });
    }
    async prePlan(plan) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:plan-proposed', { planId: plan.planId });
        // Step-count limit check (if configured)
        const maxSteps = this.langchainConfig.maxPlanSteps;
        if (maxSteps !== undefined && plan.steps.length > maxSteps) {
            const reason = `Plan has ${plan.steps.length} steps, exceeding the maximum of ${maxSteps}`;
            this.emitCanonical('orchestrator:plan-denied', { planId: plan.planId, reason });
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        try {
            this.validatePlanStructure(plan);
        }
        catch (err) {
            const reason = err instanceof Error ? err.message : String(err);
            this.emitCanonical('orchestrator:plan-denied', { planId: plan.planId, reason });
            return { type: index_2.ActionDecisionType.DENY, reason, auditId };
        }
        this.emitCanonical('orchestrator:plan-approved', { planId: plan.planId });
        return { type: index_2.ActionDecisionType.ALLOW, reason: 'Plan approved', auditId };
    }
    async postPlan(plan, result) {
        this.emitCanonical('orchestrator:workflow-complete', { planId: plan.planId, result });
    }
    async preDelegation(d) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        const restricted = d.permissions.filter((p) => this.langchainConfig.sensitiveTools.includes(p));
        if (restricted.length > 0) {
            const reason = `Delegation includes restricted permissions: ${restricted.join(', ')}`;
            this.emitCanonical('orchestrator:delegation-denied', {
                delegationId: d.delegationId,
                from: d.fromAgent.name,
                to: d.toAgent.name,
                restricted,
            });
            return {
                type: index_2.ActionDecisionType.DENY,
                reason,
                restrictedPermissions: restricted,
                auditId,
            };
        }
        this.emitCanonical('orchestrator:delegation-approved', {
            delegationId: d.delegationId,
            from: d.fromAgent.name,
            to: d.toAgent.name,
        });
        return { type: index_2.ActionDecisionType.ALLOW, reason: 'Delegation approved', auditId };
    }
    async postDelegation(d, result) {
        this.emitCanonical('orchestrator:delegation-approved', {
            delegationId: d.delegationId,
            result,
        });
    }
    async invokeRoleEdge(edge) {
        this.assertInitialized();
        const auditId = crypto.randomUUID();
        this.emitCanonical('orchestrator:role-edge-invoked', {
            edgeId: edge.edgeId,
            fromRole: edge.fromRole,
            toRole: edge.toRole,
        });
        return { type: 'ALLOW', reason: 'Role edge permitted', auditId };
    }
    // ---------------------------------------------------------------------------
    // Error translation
    // ---------------------------------------------------------------------------
    translateError(raw) {
        if (raw instanceof index_1.OrchestratorError)
            return raw;
        const message = raw instanceof Error ? raw.message : String(raw);
        const code = raw instanceof Error ? raw.constructor.name : 'LANGCHAIN_ERROR';
        // Map known LangChain error patterns to typed subclasses
        if (message.includes('not found') || message.includes('NotFound')) {
            return new index_1.OrchestratorError({
                message: `LangChain tool not found: ${message}`,
                code: 'LANGCHAIN_TOOL_NOT_FOUND',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        if (message.includes('timeout') || message.includes('Timeout')) {
            return new index_1.OrchestratorError({
                message: `LangChain run timeout: ${message}`,
                code: 'LANGCHAIN_TIMEOUT',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
                raw,
            });
        }
        return new index_1.OrchestratorError({
            message,
            code: `LANGCHAIN_${code.toUpperCase()}`,
            orchestratorId: this.orchestratorId,
            adapterId: this.adapterId,
            raw,
        });
    }
    // ---------------------------------------------------------------------------
    // Capabilities
    // ---------------------------------------------------------------------------
    getOrchestratorCapabilities() {
        return {
            workflowGovernance: true,
            taskGovernance: true,
            planGovernance: true,
            delegationGovernance: true,
            roleEdgeGovernance: true,
            parallelExecution: false,
            nestedPlans: false,
            circuitBreaker: true,
        };
    }
}
exports.LangChainAdapter = LangChainAdapter;
//# sourceMappingURL=langchain.js.map