"use strict";
/**
 * Replicate / Llama Governance Adapter
 *
 * Maps Meta Llama function calling (via Replicate hosting) to the
 * GovernanceAdapter interface. Replicate exposes Llama models as managed
 * predictions with an async lifecycle: create -> poll -> succeeded/failed.
 *
 * Endpoint (create):  https://api.replicate.com/v1/predictions
 * Endpoint (poll):    https://api.replicate.com/v1/predictions/{id}
 * Auth:               Authorization: Token <REPLICATE_API_TOKEN>
 *                     NOTE: "Token" prefix, not "Bearer"
 *
 * Supported models:
 *   meta/meta-llama-3.1-8b-instruct
 *   meta/meta-llama-3.1-70b-instruct
 *   meta/meta-llama-3.1-405b-instruct
 *   meta/meta-llama-3.2-3b-instruct
 *   meta/meta-llama-3.3-70b-instruct
 *
 * Prediction lifecycle:
 *   Replicate predictions are async. The adapter does NOT block on polling
 *   inside wrapFunctionCall -- the predictionId is carried in the audit entry
 *   so callers can correlate polls externally. Streaming (SSE) is the
 *   preferred path; polling is provided for compatibility only.
 *
 * Tool calling:
 *   Llama 3.1+ supports tool calls via the `tools` input field on Replicate.
 *   Tool definitions are JSON Schema objects passed in `input.tools`.
 *   The model responds with a `tool_call` field in the output; this adapter
 *   extracts that and routes through governance.
 *
 * Pack-binding primacy (Thomas Directive, 2026-04-22):
 *   Every record governed by this adapter MUST carry a non-empty
 *   packBinding[]. Callers passing an empty array will receive an
 *   UnboundDataError before any action is dispatched.
 *
 * Security constraints:
 *   - HTTPS only. HTTP endpoints are rejected at construction.
 *   - REPLICATE_API_TOKEN is NEVER written to audit entries, logs, or errors.
 *   - predictionId is opaque (no prompt content leakage).
 *   - versionSha is truncated to 12 chars in audit entries for readability.
 *
 * No Replicate SDK dependency -- pure wire format mapping.
 *
 * @connexum/ai-governance
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReplicateLlamaAdapter = exports.REPLICATE_LLAMA_MODELS = exports.REPLICATE_POLL_ENDPOINT_TEMPLATE = exports.REPLICATE_DEFAULT_ENDPOINT = void 0;
const index_1 = require("../adapters/index");
const index_2 = require("../errors/index");
const prompt_injection_detector_1 = require("../security/prompt-injection-detector");
// ---------------------------------------------------------------------------
// Replicate / Llama constants
// ---------------------------------------------------------------------------
/** Primary Replicate predictions endpoint (HTTPS only). */
exports.REPLICATE_DEFAULT_ENDPOINT = 'https://api.replicate.com/v1/predictions';
/** Replicate poll endpoint template (fill in prediction id). */
exports.REPLICATE_POLL_ENDPOINT_TEMPLATE = 'https://api.replicate.com/v1/predictions/{id}';
/** Supported Llama model identifiers on Replicate as of 2026-04-30. */
exports.REPLICATE_LLAMA_MODELS = [
    'meta/meta-llama-3.1-8b-instruct',
    'meta/meta-llama-3.1-70b-instruct',
    'meta/meta-llama-3.1-405b-instruct',
    'meta/meta-llama-3.2-3b-instruct',
    'meta/meta-llama-3.3-70b-instruct',
];
/** Expected model identifier format: <owner>/<name> */
const REPLICATE_MODEL_FORMAT_RE = /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9._-]+$/;
// ---------------------------------------------------------------------------
// ReplicateLlamaAdapter
// ---------------------------------------------------------------------------
class ReplicateLlamaAdapter extends index_1.BaseGovernanceAdapter {
    blockedFunctions = [];
    approvalRequiredFunctions = [];
    agentIdentity = { name: 'unknown' };
    preActionLog = [];
    postActionLog = [];
    injectionDetector = new prompt_injection_detector_1.PromptInjectionDetector();
    injectionConfig = {};
    /** Effective endpoint after config validation. */
    endpoint = exports.REPLICATE_DEFAULT_ENDPOINT;
    /** Model <owner>/<name> from config. */
    configuredModelId;
    /** Full version SHA from config (truncated in audit entries). */
    configuredVersionSha;
    // ---------------------------------------------------------------------------
    // Lifecycle
    // ---------------------------------------------------------------------------
    /**
     * Initialize with Replicate/Llama-specific configuration.
     *
     * Throws synchronously if:
     *   - endpoint is provided but uses HTTP (not HTTPS)
     *   - modelId is provided but does not match <owner>/<name> format
     *   - apiToken contains only whitespace
     */
    initialize(config) {
        super.initialize(config);
        const cfg = config;
        // Validate endpoint -- HTTPS only, no raw IP addresses
        if (cfg.endpoint) {
            if (!cfg.endpoint.startsWith('https://')) {
                throw new Error(`ReplicateLlamaAdapter: endpoint must use HTTPS. Received: "${cfg.endpoint}". ` +
                    'Only HTTPS endpoints are permitted per web-interaction-security rules.');
            }
            this.endpoint = cfg.endpoint;
        }
        else {
            this.endpoint = exports.REPLICATE_DEFAULT_ENDPOINT;
        }
        // Validate model identifier format: <owner>/<name>
        if (cfg.modelId !== undefined) {
            if (!REPLICATE_MODEL_FORMAT_RE.test(cfg.modelId)) {
                throw new Error(`ReplicateLlamaAdapter: modelId must be in "<owner>/<name>" format. ` +
                    `Received: "${cfg.modelId}". Example: "meta/meta-llama-3.1-70b-instruct".`);
            }
            this.configuredModelId = cfg.modelId;
        }
        // Validate version SHA is present when model is specified
        if (cfg.modelId && !cfg.versionSha) {
            throw new Error('ReplicateLlamaAdapter: versionSha is required when modelId is specified. ' +
                'Replicate requires a pinned version hash for deterministic prediction routing. ' +
                'Example: { modelId: "meta/meta-llama-3.1-70b-instruct", versionSha: "<sha256>" }.');
        }
        if (cfg.versionSha) {
            this.configuredVersionSha = cfg.versionSha;
        }
        this.blockedFunctions = cfg.blockedFunctions ?? [];
        this.approvalRequiredFunctions = cfg.approvalRequiredFunctions ?? [];
        this.agentIdentity = cfg.agentIdentity ?? { name: cfg.adapterName };
        this.injectionConfig = cfg.promptInjection ?? {};
        this.injectionDetector = new prompt_injection_detector_1.PromptInjectionDetector(this.injectionConfig.detectorConfig);
        this.preActionLog = [];
        this.postActionLog = [];
    }
    // ---------------------------------------------------------------------------
    // Pack-binding primacy enforcement (Thomas Directive, 2026-04-22)
    // ---------------------------------------------------------------------------
    /**
     * Assert that packBinding is non-empty.
     * Throws UnboundDataError immediately if the binding is empty or absent.
     * Every caller that writes a governed record must pass their pack bindings.
     *
     * @param packBinding - Array of compliance pack IDs governing this record.
     * @param writeSite   - Identifier of the call site (for error reporting).
     * @param tenantId    - Tenant/org identifier (for error reporting).
     */
    assertPackBinding(packBinding, writeSite, tenantId) {
        if (!packBinding || packBinding.length === 0) {
            throw new index_2.UnboundDataError(writeSite, tenantId);
        }
    }
    // ---------------------------------------------------------------------------
    // Prompt injection scanning
    // ---------------------------------------------------------------------------
    /**
     * Scan an array of LLM messages for prompt injection BEFORE sending to Replicate.
     *
     * Returns ActionDecision DENY if injection detected at denyThreshold severity,
     * ALLOW otherwise. Audit event written to stderr (privacy-safe, no full content).
     */
    scanMessagesForInjection(messages) {
        if (this.injectionConfig.enabled === false) {
            return { type: index_1.ActionDecisionType.ALLOW, reason: 'Prompt injection detection disabled.' };
        }
        const detection = this.injectionDetector.detectInjection(messages);
        if (detection.detected) {
            const denyThreshold = this.injectionConfig.denyThreshold ?? 'high';
            const shouldDeny = detection.severity === 'high' ||
                (denyThreshold === 'medium' && detection.severity === 'medium');
            const auditPayload = (0, prompt_injection_detector_1.buildInjectionAuditEvent)(messages, detection, this.agentIdentity.name, this.agentIdentity.orgId ?? 'unknown');
            process.stderr.write(`[ReplicateLlamaAdapter] prompt-injection:${detection.severity} detected. ` +
                `patterns=${detection.patterns.join(',')} confidence=${detection.confidence.toFixed(2)} ` +
                `contentHash=${auditPayload['contentHash']}\n`);
            if (shouldDeny) {
                const decision = {
                    type: index_1.ActionDecisionType.DENY,
                    reason: `Prompt injection detected (severity=${detection.severity}, ` +
                        `patterns=${detection.patterns.join(', ')}). ` +
                        'Request blocked by @connexum/ai-governance (Onyx #5 / OWASP LLM01).',
                    metadata: {
                        injectionPatterns: detection.patterns,
                        injectionSeverity: detection.severity,
                        injectionConfidence: detection.confidence,
                    },
                };
                this.preActionLog.push({
                    context: {
                        toolName: 'llm.messages.create',
                        input: { injectionDetected: true },
                        agent: { ...this.agentIdentity },
                        timestamp: new Date().toISOString(),
                    },
                    decision,
                });
                return decision;
            }
        }
        return { type: index_1.ActionDecisionType.ALLOW, reason: 'No injection pattern detected.' };
    }
    // ---------------------------------------------------------------------------
    // GovernanceAdapter overrides
    // ---------------------------------------------------------------------------
    /**
     * Pre-action governance check.
     * Evaluates blocked and approval-required rules against the tool name.
     */
    preAction(context) {
        this.ensureInitialized();
        for (const rule of this.blockedFunctions) {
            if (this.matchesRule(context.toolName, rule)) {
                const decision = {
                    type: index_1.ActionDecisionType.DENY,
                    reason: rule.reason,
                    metadata: {
                        rule: 'blocked_function',
                        pattern: rule.pattern instanceof RegExp ? rule.pattern.source : rule.pattern,
                        toolName: context.toolName,
                    },
                };
                this.preActionLog.push({ context, decision });
                return decision;
            }
        }
        for (const rule of this.approvalRequiredFunctions) {
            if (this.matchesRule(context.toolName, rule)) {
                const decision = {
                    type: index_1.ActionDecisionType.REQUIRE_APPROVAL,
                    reason: rule.reason,
                    metadata: {
                        rule: 'approval_required',
                        pattern: rule.pattern instanceof RegExp ? rule.pattern.source : rule.pattern,
                        toolName: context.toolName,
                    },
                };
                this.preActionLog.push({ context, decision });
                return decision;
            }
        }
        const decision = {
            type: index_1.ActionDecisionType.ALLOW,
            reason: 'No governance rule matched. Action permitted.',
        };
        this.preActionLog.push({ context, decision });
        return decision;
    }
    /**
     * Post-action audit logging.
     * Replicate has no native post-action hook; callers invoke this after execution.
     */
    postAction(context, result) {
        this.ensureInitialized();
        this.postActionLog.push({ context, result });
    }
    /**
     * Report adapter capabilities.
     * Replicate (like OpenAI/DeepSeek) lacks native hooks -- middleware wrapper.
     */
    getCapabilities() {
        return {
            preAction: false,
            postAction: false,
            auditLogging: false,
            circuitBreaker: false,
            approvalQueue: false,
            tamperDetection: false,
        };
    }
    // ---------------------------------------------------------------------------
    // Replicate / Llama format parsing
    // ---------------------------------------------------------------------------
    /**
     * Parse a Llama function_call (legacy format) into ActionContext.
     */
    parseFunctionCall(call) {
        this.ensureInitialized();
        let parsedArgs;
        try {
            parsedArgs = JSON.parse(call.arguments);
        }
        catch {
            parsedArgs = { _raw: call.arguments };
        }
        return {
            toolName: call.name,
            input: parsedArgs,
            agent: { ...this.agentIdentity },
            timestamp: new Date().toISOString(),
            sessionInfo: {
                format: 'function_call',
                provider: 'replicate-llama',
                rawArguments: call.arguments,
            },
        };
    }
    /**
     * Parse a Llama tool call (tool_calls array element) into ActionContext.
     */
    parseToolCall(call) {
        this.ensureInitialized();
        let parsedArgs;
        try {
            parsedArgs = JSON.parse(call.function.arguments);
        }
        catch {
            parsedArgs = { _raw: call.function.arguments };
        }
        return {
            toolName: call.function.name,
            input: parsedArgs,
            agent: { ...this.agentIdentity },
            timestamp: new Date().toISOString(),
            sessionInfo: {
                format: 'tool_calls',
                provider: 'replicate-llama',
                toolCallId: call.id,
                rawArguments: call.function.arguments,
            },
        };
    }
    /**
     * Parse either format into ActionContext (auto-detects).
     */
    parseCall(call) {
        if (this.isToolCall(call)) {
            return this.parseToolCall(call);
        }
        return this.parseFunctionCall(call);
    }
    // ---------------------------------------------------------------------------
    // Governance-wrapped execution
    // ---------------------------------------------------------------------------
    /**
     * Wrap a Llama function/tool call with governance enforcement.
     *
     * Primary integration point. Callers pass the Llama tool call extracted from
     * the Replicate prediction output. Governance is applied before execution.
     *
     * Replicate-specific audit fields:
     *   - modelOwner / modelName: split from configured modelId
     *   - versionSha: truncated to 12 chars for readability
     *   - predictionId: opaque async lifecycle ID (no prompt content)
     *
     * Streaming preferred over polling. This wrapper does NOT block on polling.
     * If the caller has a predictionId from an async prediction, pass it here
     * so it appears in the audit entry for correlation.
     *
     * @param call          - The Llama function/tool call object
     * @param executor      - Optional executor invoked if governance allows
     * @param modelId       - Optional model identifier for audit entry
     * @param predictionId  - Optional Replicate prediction ID for audit correlation
     * @returns ReplicateLlamaWrappedCallResult
     */
    async wrapFunctionCall(call, executor, modelId, predictionId) {
        const context = this.parseCall(call);
        const format = this.isToolCall(call) ? 'tool_calls' : 'function_call';
        const decision = this.preAction(context);
        const { modelOwner, modelName } = this.splitModelId(modelId ?? this.configuredModelId);
        const versionShaFull = this.configuredVersionSha;
        const versionSha = versionShaFull ? versionShaFull.slice(0, 12) : undefined;
        const baseAuditEntry = {
            provider: 'replicate-llama',
            modelOwner,
            modelName,
            versionSha,
            predictionId,
            toolName: context.toolName,
            format,
            decisionType: decision.type,
            timestamp: context.timestamp,
            // API token is intentionally absent from the audit entry
        };
        if (decision.type !== index_1.ActionDecisionType.ALLOW) {
            return { decision, originalCall: call, auditEntry: baseAuditEntry };
        }
        if (!executor) {
            return { decision, originalCall: call, auditEntry: baseAuditEntry };
        }
        const startMs = Date.now();
        let actionResult;
        try {
            const output = await executor(context);
            actionResult = {
                success: true,
                outputSummary: typeof output === 'string' ? output : JSON.stringify(output ?? 'ok'),
                durationMs: Date.now() - startMs,
            };
        }
        catch (err) {
            actionResult = {
                success: false,
                outputSummary: '',
                error: err instanceof Error ? err.message : String(err),
                durationMs: Date.now() - startMs,
            };
        }
        this.postAction(context, actionResult);
        return {
            decision,
            result: actionResult,
            originalCall: call,
            auditEntry: {
                ...baseAuditEntry,
                durationMs: actionResult.durationMs,
            },
        };
    }
    /**
     * Synchronous version of wrapFunctionCall for non-async executors.
     */
    wrapFunctionCallSync(call, executor, modelId, predictionId) {
        const context = this.parseCall(call);
        const format = this.isToolCall(call) ? 'tool_calls' : 'function_call';
        const decision = this.preAction(context);
        const { modelOwner, modelName } = this.splitModelId(modelId ?? this.configuredModelId);
        const versionShaFull = this.configuredVersionSha;
        const versionSha = versionShaFull ? versionShaFull.slice(0, 12) : undefined;
        const baseAuditEntry = {
            provider: 'replicate-llama',
            modelOwner,
            modelName,
            versionSha,
            predictionId,
            toolName: context.toolName,
            format,
            decisionType: decision.type,
            timestamp: context.timestamp,
        };
        if (decision.type !== index_1.ActionDecisionType.ALLOW) {
            return { decision, originalCall: call, auditEntry: baseAuditEntry };
        }
        if (!executor) {
            return { decision, originalCall: call, auditEntry: baseAuditEntry };
        }
        const startMs = Date.now();
        let actionResult;
        try {
            const output = executor(context);
            actionResult = {
                success: true,
                outputSummary: typeof output === 'string' ? output : JSON.stringify(output ?? 'ok'),
                durationMs: Date.now() - startMs,
            };
        }
        catch (err) {
            actionResult = {
                success: false,
                outputSummary: '',
                error: err instanceof Error ? err.message : String(err),
                durationMs: Date.now() - startMs,
            };
        }
        this.postAction(context, actionResult);
        return {
            decision,
            result: actionResult,
            originalCall: call,
            auditEntry: {
                ...baseAuditEntry,
                durationMs: actionResult.durationMs,
            },
        };
    }
    // ---------------------------------------------------------------------------
    // Llama tool call extraction helper
    // ---------------------------------------------------------------------------
    /**
     * Extract a ReplicateLlamaToolCall from a Replicate prediction output string.
     *
     * Replicate returns the prediction output as a streamed string (SSE) or a
     * single string. When Llama uses tool calling, the output contains a JSON
     * object with a `tool_call` key. This helper parses that out.
     *
     * Returns null if the output does not contain a tool call.
     * Callers should check for null before passing to wrapFunctionCall.
     *
     * NOTE: streaming is the preferred path; use SSE events and reconstruct
     * the tool call from the delta stream. This synchronous helper is for
     * completed (non-streaming) prediction outputs only.
     */
    extractToolCallFromOutput(output) {
        if (!output)
            return null;
        // Llama 3.1+ tool call format in the output:
        //   {"tool_call": {"id": "...", "type": "function", "function": {"name": "...", "arguments": "..."}}}
        try {
            const parsed = JSON.parse(output);
            const rawToolCall = parsed['tool_call'];
            if (rawToolCall &&
                typeof rawToolCall === 'object' &&
                rawToolCall['type'] === 'function') {
                const tc = rawToolCall;
                const fn = tc['function'];
                if (fn && typeof fn['name'] === 'string') {
                    return {
                        id: typeof tc['id'] === 'string' ? tc['id'] : `llama-${Date.now()}`,
                        type: 'function',
                        function: {
                            name: fn['name'],
                            arguments: typeof fn['arguments'] === 'string'
                                ? fn['arguments']
                                : JSON.stringify(fn['arguments'] ?? {}),
                        },
                    };
                }
            }
        }
        catch {
            // Output is not JSON (plain text response, no tool call)
        }
        return null;
    }
    // ---------------------------------------------------------------------------
    // Replicate response builders
    // ---------------------------------------------------------------------------
    /**
     * Build a Llama-format tool response message from an ActionResult.
     * Compatible with OpenAI/DeepSeek tool response shape for interoperability.
     */
    buildToolResponse(callId, result) {
        return {
            role: 'tool',
            tool_call_id: callId,
            content: result.success
                ? result.outputSummary
                : JSON.stringify({ error: result.error ?? 'Unknown error' }),
        };
    }
    /**
     * Build a governance-denied response in Llama/OpenAI tool response format.
     */
    buildDeniedResponse(callId, decision) {
        return {
            role: 'tool',
            tool_call_id: callId,
            content: JSON.stringify({
                error: 'governance_denied',
                type: decision.type,
                reason: decision.reason,
            }),
        };
    }
    // ---------------------------------------------------------------------------
    // Endpoint / model accessors (for testing; do not expose the API token)
    // ---------------------------------------------------------------------------
    /** Returns the effective endpoint URL configured for this adapter instance. */
    getEndpoint() {
        return this.endpoint;
    }
    /** Returns the configured model identifier, if any. */
    getModelId() {
        return this.configuredModelId;
    }
    /** Returns the configured version SHA, if any. */
    getVersionSha() {
        return this.configuredVersionSha;
    }
    // ---------------------------------------------------------------------------
    // Audit accessors (for testing/monitoring)
    // ---------------------------------------------------------------------------
    /** Get the pre-action decision log. */
    getPreActionLog() {
        return [...this.preActionLog];
    }
    /** Get the post-action result log. */
    getPostActionLog() {
        return [...this.postActionLog];
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    isToolCall(call) {
        return 'id' in call && 'type' in call && call.type === 'function';
    }
    matchesRule(toolName, rule) {
        if (rule.pattern instanceof RegExp) {
            return rule.pattern.test(toolName);
        }
        return toolName === rule.pattern;
    }
    /**
     * Split a Replicate model identifier into owner and name components.
     * Returns empty strings if the model ID is absent or malformed.
     * No exception is thrown here -- validation happens at construction.
     */
    splitModelId(modelId) {
        if (!modelId)
            return {};
        const slashIdx = modelId.indexOf('/');
        if (slashIdx === -1)
            return { modelName: modelId };
        return {
            modelOwner: modelId.slice(0, slashIdx),
            modelName: modelId.slice(slashIdx + 1),
        };
    }
}
exports.ReplicateLlamaAdapter = ReplicateLlamaAdapter;
//# sourceMappingURL=replicate-llama.js.map