UNPKG

llmverify

Version:

AI Output Verification Toolkit — Local-first LLM safety, hallucination detection, PII redaction, prompt injection defense, and runtime monitoring. Zero telemetry. OWASP LLM Top 10 aligned.

189 lines 20.4 kB
"use strict"; /** * Hallucination Risk Heuristic Module * * Detects potential hallucination signals in LLM output. * Uses heuristics - not definitive hallucination detection. * * LIMITATIONS: * - Heuristic-based, not ground-truth verification * - May produce false positives/negatives * - Cannot detect factually incorrect but plausible statements * - Requires prompt context for best results * * @module engines/classification/hallucination * @author Haiec * @license MIT */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DEFAULT_HALLUCINATION_WEIGHTS = void 0; exports.calculateHallucinationSignals = calculateHallucinationSignals; exports.calculateHallucinationRisk = calculateHallucinationRisk; exports.getHallucinationLabel = getHallucinationLabel; const utils_1 = require("./utils"); /** * Overconfident language patterns. */ const OVERCONFIDENT_PATTERNS = [ 'definitely', 'certainly', 'guaranteed', 'proven', 'no doubt', 'undeniable', 'absolutely', 'without question', '100%', 'always', 'never fails' ]; /** * Contradiction patterns (simplified). */ const CONTRADICTION_PATTERNS = [ { positive: /is required/i, negative: /is optional/i }, { positive: /must/i, negative: /does not need to/i }, { positive: /always/i, negative: /never/i }, { positive: /can/i, negative: /cannot/i }, { positive: /will/i, negative: /will not/i } ]; /** * Calculates speculative facts score. * Looks for capitalized entities in output not present in prompt. */ function calculateSpeculativeFactsScore(prompt, output) { const promptEntities = (0, utils_1.extractCapitalizedTokens)(prompt); const outputEntities = (0, utils_1.extractCapitalizedTokens)(output); let newEntities = 0; for (const entity of outputEntities) { if (!promptEntities.has(entity)) { newEntities++; } } // Score: 0 if no new entities, 1 if 5+ new entities return (0, utils_1.clamp)(newEntities / 5, 0, 1); } /** * Calculates overconfident language score. */ function calculateOverconfidentScore(text) { const matches = (0, utils_1.countMatches)(text, OVERCONFIDENT_PATTERNS); if (matches === 0) return 0; if (matches === 1) return 0.5; return 0.8; } /** * Calculates fabricated JSON keys score. * Looks for JSON keys not mentioned in prompt. */ function calculateFabricatedKeysScore(prompt, normalizedJson) { if (!normalizedJson || typeof normalizedJson !== 'object' || Array.isArray(normalizedJson)) { return 0; } const obj = normalizedJson; const keys = Object.keys(obj); const promptLower = prompt.toLowerCase(); let extraKeys = 0; for (const key of keys) { if (!promptLower.includes(key.toLowerCase())) { extraKeys++; } } return (0, utils_1.clamp)(extraKeys / 5, 0, 1); } /** * Calculates contradiction score. * Looks for simple contradictory patterns. */ function calculateContradictionScore(text) { for (const pattern of CONTRADICTION_PATTERNS) { const hasPositive = pattern.positive.test(text); const hasNegative = pattern.negative.test(text); if (hasPositive && hasNegative) { // Found potential contradiction return 0.7; } } return 0; } /** * Calculates hallucination risk signals. * * @param prompt - The original prompt * @param output - The LLM output * @param normalizedJson - Parsed JSON if available * @param customHooks - Optional custom detection hooks * @returns Hallucination signals and scores */ function calculateHallucinationSignals(prompt, output, normalizedJson, customHooks) { const speculativeFactsScore = calculateSpeculativeFactsScore(prompt, output); const overconfidentScore = calculateOverconfidentScore(output); const fabricatedKeysScore = normalizedJson ? calculateFabricatedKeysScore(prompt, normalizedJson) : 0; const contradictionScore = calculateContradictionScore(output); return { speculativeFactsScore, fabricatedKeysScore, overconfidentScore, contradictionScore, customHooksCount: customHooks?.length ?? 0 }; } /** Default weights for hallucination signals (tuned to reduce false positives) */ exports.DEFAULT_HALLUCINATION_WEIGHTS = { speculative: 0.35, // Reduced from 0.4 - new entities are often legitimate fabricated: 0.25, // Reduced from 0.3 - JSON keys often aren't in prompt overconfident: 0.25, // Increased from 0.2 - overconfident language is a stronger signal contradiction: 0.15 // Increased from 0.1 - contradictions are meaningful }; /** * Calculates overall hallucination risk score. * * @param signals - Hallucination signals * @param prompt - Original prompt * @param output - LLM output * @param customHooks - Optional custom hooks * @param weights - Optional weight overrides * @returns Risk score (0-1) */ function calculateHallucinationRisk(signals, prompt, output, customHooks, weights) { // Merge weights with defaults const w = { speculative: weights?.speculative ?? exports.DEFAULT_HALLUCINATION_WEIGHTS.speculative, fabricated: weights?.fabricated ?? exports.DEFAULT_HALLUCINATION_WEIGHTS.fabricated, overconfident: weights?.overconfident ?? exports.DEFAULT_HALLUCINATION_WEIGHTS.overconfident, contradiction: weights?.contradiction ?? exports.DEFAULT_HALLUCINATION_WEIGHTS.contradiction }; // Base risk from internal signals let risk = w.speculative * signals.speculativeFactsScore + w.fabricated * signals.fabricatedKeysScore + w.overconfident * signals.overconfidentScore + w.contradiction * signals.contradictionScore; // Apply custom hooks if (customHooks) { for (const hook of customHooks) { try { const hookScore = (0, utils_1.clamp)(hook(prompt, output), 0, 1); risk += 0.2 * hookScore; } catch { // Ignore hook errors } } } return (0, utils_1.clamp)(risk, 0, 1); } /** * Gets hallucination risk label from score. */ function getHallucinationLabel(risk) { if (risk <= 0.3) return 'low'; if (risk <= 0.6) return 'medium'; return 'high'; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFsbHVjaW5hdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9lbmdpbmVzL2NsYXNzaWZpY2F0aW9uL2hhbGx1Y2luYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7Ozs7Ozs7R0FlRzs7O0FBb0hILHNFQW9CQztBQW9CRCxnRUF3Q0M7QUFLRCxzREFJQztBQTFNRCxtQ0FBcUY7QUFFckY7O0dBRUc7QUFDSCxNQUFNLHNCQUFzQixHQUFHO0lBQzdCLFlBQVk7SUFDWixXQUFXO0lBQ1gsWUFBWTtJQUNaLFFBQVE7SUFDUixVQUFVO0lBQ1YsWUFBWTtJQUNaLFlBQVk7SUFDWixrQkFBa0I7SUFDbEIsTUFBTTtJQUNOLFFBQVE7SUFDUixhQUFhO0NBQ2QsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBTSxzQkFBc0IsR0FBRztJQUM3QixFQUFFLFFBQVEsRUFBRSxjQUFjLEVBQUUsUUFBUSxFQUFFLGNBQWMsRUFBRTtJQUN0RCxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLG1CQUFtQixFQUFFO0lBQ3BELEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFO0lBQzNDLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFO0lBQ3pDLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFO0NBQzdDLENBQUM7QUFFRjs7O0dBR0c7QUFDSCxTQUFTLDhCQUE4QixDQUFDLE1BQWMsRUFBRSxNQUFjO0lBQ3BFLE1BQU0sY0FBYyxHQUFHLElBQUEsZ0NBQXdCLEVBQUMsTUFBTSxDQUFDLENBQUM7SUFDeEQsTUFBTSxjQUFjLEdBQUcsSUFBQSxnQ0FBd0IsRUFBQyxNQUFNLENBQUMsQ0FBQztJQUV4RCxJQUFJLFdBQVcsR0FBRyxDQUFDLENBQUM7SUFDcEIsS0FBSyxNQUFNLE1BQU0sSUFBSSxjQUFjLEVB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LENBQUMsQ0FBQyxDQUFDO2dCQUNwRCxJQUFJLElBQUksR0FBRyxHQUFHLFNBQVMsQ0FBQztZQUMxQixDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNQLHFCQUFxQjtZQUN2QixDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxPQUFPLElBQUEsYUFBSyxFQUFDLElBQUksRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDM0IsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IscUJBQXFCLENBQUMsSUFBWTtJQUNoRCxJQUFJLElBQUksSUFBSSxHQUFHO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDOUIsSUFBSSxJQUFJLElBQUksR0FBRztRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQ2pDLE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEhhbGx1Y2luYXRpb24gUmlzayBIZXVyaXN0aWMgTW9kdWxlXG4gKiBcbiAqIERldGVjdHMgcG90ZW50aWFsIGhhbGx1Y2luYXRpb24gc2lnbmFscyBpbiBMTE0gb3V0cHV0LlxuICogVXNlcyBoZXVyaXN0aWNzIC0gbm90IGRlZmluaXRpdmUgaGFsbHVjaW5hdGlvbiBkZXRlY3Rpb24uXG4gKiBcbiAqIExJTUlUQVRJT05TOlxuICogLSBIZXVyaXN0aWMtYmFzZWQsIG5vdCBncm91bmQtdHJ1dGggdmVyaWZpY2F0aW9uXG4gKiAtIE1heSBwcm9kdWNlIGZhbHNlIHBvc2l0aXZlcy9uZWdhdGl2ZXNcbiAqIC0gQ2Fubm90IGRldGVjdCBmYWN0dWFsbHkgaW5jb3JyZWN0IGJ1dCBwbGF1c2libGUgc3RhdGVtZW50c1xuICogLSBSZXF1aXJlcyBwcm9tcHQgY29udGV4dCBmb3IgYmVzdCByZXN1bHRzXG4gKiBcbiAqIEBtb2R1bGUgZW5naW5lcy9jbGFzc2lmaWNhdGlvbi9oYWxsdWNpbmF0aW9uXG4gKiBAYXV0aG9yIEhhaWVjXG4gKiBAbGljZW5zZSBNSVRcbiAqL1xuXG5pbXBvcnQgeyBIYWxsdWNpbmF0aW9uU2lnbmFscywgSGFsbHVjaW5hdGlvbkxhYmVsIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQgeyBjbGFtcCwgZXh0cmFjdENhcGl0YWxpemVkVG9rZW5zLCBjb250YWluc0FueSwgY291bnRNYXRjaGVzIH0gZnJvbSAnLi91dGlscyc7XG5cbi8qKlxuICogT3ZlcmNvbmZpZGVudCBsYW5ndWFnZSBwYXR0ZXJucy5cbiAqL1xuY29uc3QgT1ZFUkNPTkZJREVOVF9QQVRURVJOUyA9IFtcbiAgJ2RlZmluaXRlbHknLFxuICAnY2VydGFpbmx5JyxcbiAgJ2d1YXJhbnRlZWQnLFxuICAncHJvdmVuJyxcbiAgJ25vIGRvdWJ0JyxcbiAgJ3VuZGVuaWFibGUnLFxuICAnYWJzb2x1dGVseScsXG4gICd3aXRob3V0IHF1ZXN0aW9uJyxcbiAgJzEwMCUnLFxuICAnYWx3YXlzJyxcbiAgJ25ldmVyIGZhaWxzJ1xuXTtcblxuLyoqXG4gKiBDb250cmFkaWN0aW9uIHBhdHRlcm5zIChzaW1wbGlmaWVkKS5cbiAqL1xuY29uc3QgQ09OVFJBRElDVElPTl9QQVRURVJOUyA9IFtcbiAgeyBwb3NpdGl2ZTogL2lzIHJlcXVpcmVkL2ksIG5lZ2F0aXZlOiAvaXMgb3B0aW9uYWwvaSB9LFxuICB7IHBvc2l0aXZlOiAvbXVzdC9pLCBuZWdhdGl2ZTogL2RvZXMgbm90IG5lZWQgdG8vaSB9LFxuICB7IHBvc2l0aXZlOiAvYWx3YXlzL2ksIG5lZ2F0aXZlOiAvbmV2ZXIvaSB9LFxuICB7IHBvc2l0aXZlOiAvY2FuL2ksIG5lZ2F0aXZlOiAvY2Fubm90L2kgfSxcbiAgeyBwb3NpdGl2ZTogL3dpbGwvaSwgbmVnYXRpdmU6IC93aWxsIG5vdC9pIH1cbl07XG5cbi8qKlxuICogQ2FsY3VsYXRlcyBzcGVjdWxhdGl2ZSBmYWN0cyBzY29yZS5cbiAqIExvb2tzIGZvciBjYXBpdGFsaXplZCBlbnRpdGllcyBpbiBvdXRwdXQgbm90IHByZXNlbnQgaW4gcHJvbXB0LlxuICovXG5mdW5jdGlvbiBjYWxjdWxhdGVTcGVjdWxhdGl2ZUZhY3RzU2NvcmUocHJvbXB0OiBzdHJpbmcsIG91dHB1dDogc3RyaW5nKTogbnVtYmVyIHtcbiAgY29uc3QgcHJvbXB0RW50aXRpZXMgPSBleHRyYWN0Q2FwaXRhbGl6ZWRUb2tlbnMocHJvbXB0KTtcbiAgY29uc3Qgb3V0cHV0RW50aXRpZXMgPSBleHRyYWN0Q2FwaXRhbGl6ZWRUb2tlbnMob3V0cHV0KTtcbiAgXG4gIGxldCBuZXdFbnRpdGllcyA9IDA7XG4gIGZvciAoY29uc3QgZW50aXR5IG9mIG91dHB1dEVudGl0aWVzKSB7XG4gICAgaWYgKCFwcm9tcHRFbnRpdGllcy5oYXMoZW50aXR5KSkge1xuICAgICAgbmV3RW50aXRpZXMrKztcbiAgICB9XG4gIH1cbiAgXG4gIC8vIFNjb3JlOiAwIGlmIG5vIG5ldyBlbnRpdGllcywgMSBpZiA1KyBuZXcgZW50aXRpZXNcbiAgcmV0dXJuIGNsYW1wKG5ld0VudGl0aWVzIC8gNSwgMCwgMSk7XG59XG5cbi8qKlxuICogQ2FsY3VsYXRlcyBvdmVyY29uZmlkZW50IGxhbmd1YWdlIHNjb3JlLlxuICovXG5mdW5jdGlvbiBjYWxjdWxhdGVPdmVyY29uZmlkZW50U2NvcmUodGV4dDogc3RyaW5nKTogbnVtYmVyIHtcbiAgY29uc3QgbWF0Y2hlcyA9IGNvdW50TWF0Y2hlcyh0ZXh0LCBPVkVSQ09ORklERU5UX1BBVFRFUk5TKTtcbiAgXG4gIGlmIChtYXRjaGVzID09PSAwKSByZXR1cm4gMDtcbiAgaWYgKG1hdGNoZXMgPT09IDEpIHJldHVybiAwLjU7XG4gIHJldHVybiAwLjg7XG59XG5cbi8qKlxuICogQ2FsY3VsYXRlcyBmYWJyaWNhdGVkIEpTT04ga2V5cyBzY29yZS5cbiAqIExvb2tzIGZvciBKU09OIGtleXMgbm90IG1lbnRpb25lZCBpbiBwcm9tcHQuXG4gKi9cbmZ1bmN0aW9uIGNhbGN1bGF0ZUZhYnJpY2F0ZWRLZXlzU2NvcmUoXG4gIHByb21wdDogc3RyaW5nLFxuICBub3JtYWxpemVkSnNvbjogdW5rbm93blxuKTogbnVtYmVyIHtcbiAgaWYgKCFub3JtYWxpemVkSnNvbiB8fCB0eXBlb2Ygbm9ybWFsaXplZEpzb24gIT09ICdvYmplY3QnIHx8IEFycmF5LmlzQXJyYXkobm9ybWFsaXplZEpzb24pKSB7XG4gICAgcmV0dXJuIDA7XG4gIH1cbiAgXG4gIGNvbnN0IG9iaiA9IG5vcm1hbGl6ZWRKc29uIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICBjb25zdCBrZXlzID0gT2JqZWN0LmtleXMob2JqKTtcbiAgY29uc3QgcHJvbXB0TG93ZXIgPSBwcm9tcHQudG9Mb3dlckNhc2UoKTtcbiAgXG4gIGxldCBleHRyYUtleXMgPSAwO1xuICBmb3IgKGNvbnN0IGtleSBvZiBrZXlzKSB7XG4gICAgaWYgKCFwcm9tcHRMb3dlci5pbmNsdWRlcyhrZXkudG9Mb3dlckNhc2UoKSkpIHtcbiAgICAgIGV4dHJhS2V5cysrO1xuICAgIH1cbiAgfVxuICBcbiAgcmV0dXJuIGNsYW1wKGV4dHJhS2V5cyAvIDUsIDAsIDEpO1xufVxuXG4vKipcbiAqIENhbGN1bGF0ZXMgY29udHJhZGljdGlvbiBzY29yZS5cbiAqIExvb2tzIGZvciBzaW1wbGUgY29udHJhZGljdG9yeSBwYXR0ZXJucy5cbiAqL1xuZnVuY3Rpb24gY2FsY3VsYXRlQ29udHJhZGljdGlvblNjb3JlKHRleHQ6IHN0cmluZyk6IG51bWJlciB7XG4gIGZvciAoY29uc3QgcGF0dGVybiBvZiBDT05UUkFESUNUSU9OX1BBVFRFUk5TKSB7XG4gICAgY29uc3QgaGFzUG9zaXRpdmUgPSBwYXR0ZXJuLnBvc2l0aXZlLnRlc3QodGV4dCk7XG4gICAgY29uc3QgaGFzTmVnYXRpdmUgPSBwYXR0ZXJuLm5lZ2F0aXZlLnRlc3QodGV4dCk7XG4gICAgXG4gICAgaWYgKGhhc1Bvc2l0aXZlICYmIGhhc05lZ2F0aXZlKSB7XG4gICAgICAvLyBGb3VuZCBwb3RlbnRpYWwgY29udHJhZGljdGlvblxuICAgICAgcmV0dXJuIDAuNztcbiAgICB9XG4gIH1cbiAgXG4gIHJldHVybiAwO1xufVxuXG4vKipcbiAqIENhbGN1bGF0ZXMgaGFsbHVjaW5hdGlvbiByaXNrIHNpZ25hbHMuXG4gKiBcbiAqIEBwYXJhbSBwcm9tcHQgLSBUaGUgb3JpZ2luYWwgcHJvbXB0XG4gKiBAcGFyYW0gb3V0cHV0IC0gVGhlIExMTSBvdXRwdXRcbiAqIEBwYXJhbSBub3JtYWxpemVkSnNvbiAtIFBhcnNlZCBKU09OIGlmIGF2YWlsYWJsZVxuICogQHBhcmFtIGN1c3RvbUhvb2tzIC0gT3B0aW9uYWwgY3VzdG9tIGRldGVjdGlvbiBob29rc1xuICogQHJldHVybnMgSGFsbHVjaW5hdGlvbiBzaWduYWxzIGFuZCBzY29yZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZUhhbGx1Y2luYXRpb25TaWduYWxzKFxuICBwcm9tcHQ6IHN0cmluZyxcbiAgb3V0cHV0OiBzdHJpbmcsXG4gIG5vcm1hbGl6ZWRKc29uOiB1bmtub3duIHwgdW5kZWZpbmVkLFxuICBjdXN0b21Ib29rcz86IEFycmF5PChwcm9tcHQ6IHN0cmluZywgb3V0cHV0OiBzdHJpbmcpID0+IG51bWJlcj5cbik6IEhhbGx1Y2luYXRpb25TaWduYWxzIHtcbiAgY29uc3Qgc3BlY3VsYXRpdmVGYWN0c1Njb3JlID0gY2FsY3VsYXRlU3BlY3VsYXRpdmVGYWN0c1Njb3JlKHByb21wdCwgb3V0cHV0KTtcbiAgY29uc3Qgb3ZlcmNvbmZpZGVudFNjb3JlID0gY2FsY3VsYXRlT3ZlcmNvbmZpZGVudFNjb3JlKG91dHB1dCk7XG4gIGNvbnN0IGZhYnJpY2F0ZWRLZXlzU2NvcmUgPSBub3JtYWxpemVkSnNvbiBcbiAgICA/IGNhbGN1bGF0ZUZhYnJpY2F0ZWRLZXlzU2NvcmUocHJvbXB0LCBub3JtYWxpemVkSnNvbikgXG4gICAgOiAwO1xuICBjb25zdCBjb250cmFkaWN0aW9uU2NvcmUgPSBjYWxjdWxhdGVDb250cmFkaWN0aW9uU2NvcmUob3V0cHV0KTtcbiAgXG4gIHJldHVybiB7XG4gICAgc3BlY3VsYXRpdmVGYWN0c1Njb3JlLFxuICAgIGZhYnJpY2F0ZWRLZXlzU2NvcmUsXG4gICAgb3ZlcmNvbmZpZGVudFNjb3JlLFxuICAgIGNvbnRyYWRpY3Rpb25TY29yZSxcbiAgICBjdXN0b21Ib29rc0NvdW50OiBjdXN0b21Ib29rcz8ubGVuZ3RoID8/IDBcbiAgfTtcbn1cblxuLyoqIERlZmF1bHQgd2VpZ2h0cyBmb3IgaGFsbHVjaW5hdGlvbiBzaWduYWxzICh0dW5lZCB0byByZWR1Y2UgZmFsc2UgcG9zaXRpdmVzKSAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfSEFMTFVDSU5BVElPTl9XRUlHSFRTID0ge1xuICBzcGVjdWxhdGl2ZTogMC4zNSwgICAvLyBSZWR1Y2VkIGZyb20gMC40IC0gbmV3IGVudGl0aWVzIGFyZSBvZnRlbiBsZWdpdGltYXRlXG4gIGZhYnJpY2F0ZWQ6IDAuMjUsICAgIC8vIFJlZHVjZWQgZnJvbSAwLjMgLSBKU09OIGtleXMgb2Z0ZW4gYXJlbid0IGluIHByb21wdFxuICBvdmVyY29uZmlkZW50OiAwLjI1LCAvLyBJbmNyZWFzZWQgZnJvbSAwLjIgLSBvdmVyY29uZmlkZW50IGxhbmd1YWdlIGlzIGEgc3Ryb25nZXIgc2lnbmFsXG4gIGNvbnRyYWRpY3Rpb246IDAuMTUgIC8vIEluY3JlYXNlZCBmcm9tIDAuMSAtIGNvbnRyYWRpY3Rpb25zIGFyZSBtZWFuaW5nZnVsXG59O1xuXG4vKipcbiAqIENhbGN1bGF0ZXMgb3ZlcmFsbCBoYWxsdWNpbmF0aW9uIHJpc2sgc2NvcmUuXG4gKiBcbiAqIEBwYXJhbSBzaWduYWxzIC0gSGFsbHVjaW5hdGlvbiBzaWduYWxzXG4gKiBAcGFyYW0gcHJvbXB0IC0gT3JpZ2luYWwgcHJvbXB0XG4gKiBAcGFyYW0gb3V0cHV0IC0gTExNIG91dHB1dFxuICogQHBhcmFtIGN1c3RvbUhvb2tzIC0gT3B0aW9uYWwgY3VzdG9tIGhvb2tzXG4gKiBAcGFyYW0gd2VpZ2h0cyAtIE9wdGlvbmFsIHdlaWdodCBvdmVycmlkZXNcbiAqIEByZXR1cm5zIFJpc2sgc2NvcmUgKDAtMSlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbGN1bGF0ZUhhbGx1Y2luYXRpb25SaXNrKFxuICBzaWduYWxzOiBIYWxsdWNpbmF0aW9uU2lnbmFscyxcbiAgcHJvbXB0OiBzdHJpbmcsXG4gIG91dHB1dDogc3RyaW5nLFxuICBjdXN0b21Ib29rcz86IEFycmF5PChwcm9tcHQ6IHN0cmluZywgb3V0cHV0OiBzdHJpbmcpID0+IG51bWJlcj4sXG4gIHdlaWdodHM/OiB7XG4gICAgc3BlY3VsYXRpdmU/OiBudW1iZXI7XG4gICAgZmFicmljYXRlZD86IG51bWJlcjtcbiAgICBvdmVyY29uZmlkZW50PzogbnVtYmVyO1xuICAgIGNvbnRyYWRpY3Rpb24/OiBudW1iZXI7XG4gIH1cbik6IG51bWJlciB7XG4gIC8vIE1lcmdlIHdlaWdodHMgd2l0aCBkZWZhdWx0c1xuICBjb25zdCB3ID0ge1xuICAgIHNwZWN1bGF0aXZlOiB3ZWlnaHRzPy5zcGVjdWxhdGl2ZSA/PyBERUZBVUxUX0hBTExVQ0lOQVRJT05fV0VJR0hUUy5zcGVjdWxhdGl2ZSxcbiAgICBmYWJyaWNhdGVkOiB3ZWlnaHRzPy5mYWJyaWNhdGVkID8/IERFRkFVTFRfSEFMTFVDSU5BVElPTl9XRUlHSFRTLmZhYnJpY2F0ZWQsXG4gICAgb3ZlcmNvbmZpZGVudDogd2VpZ2h0cz8ub3ZlcmNvbmZpZGVudCA/PyBERUZBVUxUX0hBTExVQ0lOQVRJT05fV0VJR0hUUy5vdmVyY29uZmlkZW50LFxuICAgIGNvbnRyYWRpY3Rpb246IHdlaWdodHM/LmNvbnRyYWRpY3Rpb24gPz8gREVGQVVMVF9IQUxMVUNJTkFUSU9OX1dFSUdIVFMuY29udHJhZGljdGlvblxuICB9O1xuICBcbiAgLy8gQmFzZSByaXNrIGZyb20gaW50ZXJuYWwgc2lnbmFsc1xuICBsZXQgcmlzayA9IFxuICAgIHcuc3BlY3VsYXRpdmUgKiBzaWduYWxzLnNwZWN1bGF0aXZlRmFjdHNTY29yZSArXG4gICAgdy5mYWJyaWNhdGVkICogc2lnbmFscy5mYWJyaWNhdGVkS2V5c1Njb3JlICtcbiAgICB3Lm92ZXJjb25maWRlbnQgKiBzaWduYWxzLm92ZXJjb25maWRlbnRTY29yZSArXG4gICAgdy5jb250cmFkaWN0aW9uICogc2lnbmFscy5jb250cmFkaWN0aW9uU2NvcmU7XG4gIFxuICAvLyBBcHBseSBjdXN0b20gaG9va3NcbiAgaWYgKGN1c3RvbUhvb2tzKSB7XG4gICAgZm9yIChjb25zdCBob29rIG9mIGN1c3RvbUhvb2tzKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBob29rU2NvcmUgPSBjbGFtcChob29rKHByb21wdCwgb3V0cHV0KSwgMCwgMSk7XG4gICAgICAgIHJpc2sgKz0gMC4yICogaG9va1Njb3JlO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIC8vIElnbm9yZSBob29rIGVycm9yc1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBcbiAgcmV0dXJuIGNsYW1wKHJpc2ssIDAsIDEpO1xufVxuXG4vKipcbiAqIEdldHMgaGFsbHVjaW5hdGlvbiByaXNrIGxhYmVsIGZyb20gc2NvcmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRIYWxsdWNpbmF0aW9uTGFiZWwocmlzazogbnVtYmVyKTogSGFsbHVjaW5hdGlvbkxhYmVsIHtcbiAgaWYgKHJpc2sgPD0gMC4zKSByZXR1cm4gJ2xvdyc7XG4gIGlmIChyaXNrIDw9IDAuNikgcmV0dXJuICdtZWRpdW0nO1xuICByZXR1cm4gJ2hpZ2gnO1xufVxuIl19