UNPKG

ai-sdk-guardrails

Version:

Input and output guardrails middleware for Vercel AI SDK.

909 lines (902 loc) 29.5 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/guardrails/tools.ts var tools_exports = {}; __export(tools_exports, { expectedToolUse: () => expectedToolUse, extractToolNamesFromResult: () => extractToolNamesFromResult, toolEgressPolicy: () => toolEgressPolicy }); module.exports = __toCommonJS(tools_exports); // src/errors.ts var import_provider = require("@ai-sdk/provider"); var marker = "ai-sdk-guardrails.error"; var symbol = Symbol.for(marker); var GuardrailsError = class extends import_provider.AISDKError { [symbol] = true; timestamp; metadata; constructor(name, message, metadata = {}, cause) { super({ name, message, cause }); this.timestamp = /* @__PURE__ */ new Date(); this.metadata = metadata; } /** * Convert the error to a serializable object for logging/reporting */ toJSON() { return { name: this.name, code: this.code, message: this.message, timestamp: this.timestamp.toISOString(), metadata: this.metadata, stack: this.stack }; } /** * Check if this error is of a specific guardrails error subclass. */ is(errorClass) { return this instanceof errorClass; } /** * Checks whether the given value is a guardrails error, across package * versions (marker-based, like `AISDKError.isInstance`). */ static isInstance(error) { return super.hasMarker(error, marker); } }; // src/core.ts function createOutputGuardrail(name, execute) { return { name, execute }; } // src/guardrails/output.ts var EMPTY_CONTENT = { text: "", object: null, usage: void 0, finishReason: void 0, generationTimeMs: void 0, reasoningText: void 0 }; function emptyContent() { return { ...EMPTY_CONTENT }; } function mapUsage(usage) { if (!usage) { return void 0; } const pickNumber = (keys) => { for (const key of keys) { const value = usage[key]; if (typeof value === "number") { return value; } if (value && typeof value === "object" && "total" in value && typeof value.total === "number") { return value.total; } } return void 0; }; const totalTokens = pickNumber(["totalTokens"]); const promptTokens = pickNumber(["inputTokens", "promptTokens"]); const completionTokens = pickNumber(["outputTokens", "completionTokens"]); const computedTotal = totalTokens ?? (promptTokens !== void 0 && completionTokens !== void 0 ? promptTokens + completionTokens : void 0); if (promptTokens === void 0 && completionTokens === void 0 && computedTotal === void 0) { return void 0; } return { promptTokens, completionTokens, totalTokens: computedTotal }; } function extractGenerationTime(result) { return result.experimental_providerMetadata?.generationTimeMs ?? result.providerMetadata?.generationTimeMs ?? 0; } function extractReasoningText(result) { return result.reasoningText || result.experimental_providerMetadata?.reasoningText || void 0; } function createContent(partial) { return { ...EMPTY_CONTENT, ...partial }; } function extractContent(result) { const contentArray = result.content; if ("content" in result && Array.isArray(contentArray) && contentArray.length > 0) { const typedResult = result; const textContent = typedResult.content.filter((item) => item.type === "text" && item.text).map((item) => item.text).join(""); const objectValue = typedResult.output ?? typedResult.object ?? null; return createContent({ text: textContent || "", object: objectValue, usage: mapUsage(typedResult.usage), finishReason: typedResult.finishReason, generationTimeMs: extractGenerationTime(typedResult), reasoningText: extractReasoningText(typedResult) }); } if ("output" in result && result.output !== null && result.output !== void 0) { const outputResult = result; return createContent({ text: outputResult.text || "", object: outputResult.output, usage: mapUsage(outputResult.usage), finishReason: outputResult.finishReason, generationTimeMs: extractGenerationTime(outputResult), reasoningText: extractReasoningText(outputResult) }); } if ("object" in result && result.object !== null && result.object !== void 0) { const objectResult = result; return createContent({ text: objectResult.text || "", object: objectResult.object, usage: mapUsage(objectResult.usage), finishReason: objectResult.finishReason, generationTimeMs: extractGenerationTime(objectResult), reasoningText: extractReasoningText(objectResult) }); } if ("text" in result && typeof result.text === "string") { const textResult = result; return createContent({ text: textResult.text || "", object: null, usage: mapUsage(textResult.usage), finishReason: textResult.finishReason, generationTimeMs: extractGenerationTime(textResult), reasoningText: extractReasoningText(textResult) }); } if ("textStream" in result || "objectStream" in result || "embeddings" in result || "then" in result) { return emptyContent(); } return emptyContent(); } var secretRedaction = createOutputGuardrail( "secret-redaction", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const secretPatterns = [ // API Keys (various formats) - more specific patterns to reduce false positives { name: "API Key", pattern: /(?:api[_-]?key|apikey|access[_-]?key)\s*[:=]\s*['"]?([a-zA-Z0-9]{20,})['"]?/gi }, // AWS Access Keys { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/g }, // AWS Secret Keys { name: "AWS Secret Key", pattern: /[A-Za-z0-9/+=]{40}/g }, // AWS ARNs { name: "AWS ARN", pattern: /arn:aws:[a-zA-Z0-9-]+:[a-zA-Z0-9-]*:[0-9]*:[a-zA-Z0-9-_/.:*]+/g }, // JWT Tokens { name: "JWT Token", pattern: /eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g }, // Bearer Tokens { name: "Bearer Token", pattern: /Bearer\s+[a-zA-Z0-9_.-]+/gi }, // GitHub Personal Access Tokens { name: "GitHub Token", pattern: /ghp_[a-zA-Z0-9]{36}/g }, // Google API Keys { name: "Google API Key", pattern: /AIza[0-9A-Za-z_-]{35}/g }, // PEM Certificate/Key blocks { name: "PEM Certificate/Key", pattern: /-----BEGIN [A-Z ]+-----[\s\S]*?-----END [A-Z ]+-----/g }, // SSH Private Keys { name: "SSH Private Key", pattern: /-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----[\s\S]*?-----END (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----/g }, // Database Connection Strings { name: "Database Connection String", pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^@\s]+:[^@\s]+@[^/\s]+/gi }, // Environment variables with secrets (more specific to reduce false positives) { name: "Environment Secret", pattern: /(?:token|secret|password|key)\s*[:=]\s*['"]?([a-zA-Z0-9_.-]{16,})['"]?/gi } ]; const detectedSecrets = []; for (const { name, pattern } of secretPatterns) { if (pattern.global) { pattern.lastIndex = 0; } let match; while ((match = pattern.exec(content)) !== null) { const maskedSecret = match[0].length > 20 ? match[0].slice(0, 8) + "..." + match[0].slice(-4) : match[0].slice(0, 4) + "..."; detectedSecrets.push({ type: name, pattern: maskedSecret, position: match.index }); if (match[0].length === 0) { pattern.lastIndex++; } } } if (detectedSecrets.length > 0) { return { tripwireTriggered: true, message: `Output contains ${detectedSecrets.length} potential secret(s): ${detectedSecrets.map((s) => s.type).join(", ")}`, severity: "critical", metadata: { secretsDetected: detectedSecrets.length, secretTypes: detectedSecrets.map((s) => s.type), contentLength: content.length }, suggestion: "Remove sensitive information before sharing output", info: { guardrailName: "secret-redaction", secretsDetected: detectedSecrets.length, secretTypes: detectedSecrets.map((s) => s.type), contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "secret-redaction" } }; } ); var unsafeContentDetector = createOutputGuardrail( "unsafe-content-detector", (context) => { const { text, object } = extractContent(context.result); const content = text || (object ? JSON.stringify(object) : ""); const unsafePatterns = [ { category: "Violence", patterns: [ /\b(kill|murder|assassinate|torture|bomb|weapon|gun|knife|explosive)\b/gi, /\b(harm|hurt|injure|attack|assault|fight)\s+(someone|people|person)/gi, /\b(violence|violent|aggression|aggressive)\b/gi ] }, { category: "Hate Speech", patterns: [ /\b(hate|racist|sexist|homophobic|transphobic|xenophobic)\b/gi, /\b(nazi|fascist|supremacist|terrorism|terrorist)\b/gi, /\b(discrimination|prejudice|bigotry)\b/gi ] }, { category: "Self-Harm", patterns: [ /\b(suicide|self-harm|self-hurt|cut myself|end my life)\b/gi, /\b(want to die|kill myself|harm myself)\b/gi, /\b(suicidal|depression|self-destruction)\b/gi ] }, { category: "Illegal Activities", patterns: [ /\b(illegal drugs|drug dealing|money laundering|fraud|scam)\b/gi, /\b(hack|crack|pirate|steal|burglary|theft)\b/gi, /\b(counterfeit|forgery|blackmail|extortion)\b/gi ] }, { category: "Adult Content", patterns: [ /\b(pornography|explicit sexual|adult content|nsfw)\b/gi, /\b(sexual explicit|graphic sexual|sexual imagery)\b/gi ] }, { category: "Personal Information", patterns: [ /\b\d{3}-\d{2}-\d{4}\b/g, // SSN format /\b\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b/g, // Credit card format /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g // Email ] } ]; const detectedIssues = []; for (const { category, patterns } of unsafePatterns) { let totalMatches = 0; for (const pattern of patterns) { const matches = content.match(pattern); if (matches) { totalMatches += matches.length; } } if (totalMatches > 0) { detectedIssues.push({ category, matches: totalMatches }); } } if (detectedIssues.length > 0) { return { tripwireTriggered: true, message: `Unsafe content detected: ${detectedIssues.map((i) => `${i.category} (${i.matches})`).join(", ")}`, severity: "high", metadata: { categoriesDetected: detectedIssues.length, issues: detectedIssues, contentLength: content.length }, suggestion: "Review and modify content to remove potentially harmful material", info: { guardrailName: "unsafe-content-detector", categoriesDetected: detectedIssues.length, issues: detectedIssues, contentLength: content.length } }; } return { tripwireTriggered: false, info: { guardrailName: "unsafe-content-detector" } }; } ); // src/guardrails/tool-egress-scan.ts var BASE64_RUN = /(?:[A-Za-z0-9+/]{4}){8,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?/g; var URL_PATTERN = /https?:\/\/[^\s"'<>]+|ftp:\/\/[^\s"'<>]+|file:\/\/[^\s"'<>]+/gi; function isRecord(value) { return Boolean(value) && typeof value === "object"; } function stringifyToolArgs(value) { if (typeof value === "string") return value; try { return JSON.stringify(value); } catch { return String(value); } } function extractToolCallPayloads(result) { const payloads = []; const resultRecord = result; if (Array.isArray(resultRecord.content)) { for (const item of resultRecord.content) { if (!isRecord(item) || item.type !== "tool-call") continue; const toolName = typeof item.toolName === "string" ? item.toolName : typeof item.name === "string" ? item.name : "unknown"; const rawInput = item.input ?? item.args ?? item.parameters ?? {}; payloads.push({ toolName, argsText: stringifyToolArgs(rawInput) }); } } if (Array.isArray(resultRecord.toolCalls)) { for (const call of resultRecord.toolCalls) { if (!isRecord(call)) continue; const toolName = typeof call.toolName === "string" ? call.toolName : typeof call.name === "string" ? call.name : "unknown"; const rawInput = call.input ?? call.args ?? call.parameters ?? {}; payloads.push({ toolName, argsText: stringifyToolArgs(rawInput) }); } } return payloads; } function hostMatches(host, patterns) { return patterns.some( (pattern) => typeof pattern === "string" ? host === pattern : pattern.test(host) ); } function scanTextEgressViolations(text, options, prefix) { const violations = []; const { allowedHosts = [], blockedHosts = [], suspiciousFilenamePatterns = [], blockBase64Payloads = false, minBase64Length = 64, allowFileUrls = false, allowLocalhost = false } = options; for (const pattern of suspiciousFilenamePatterns) { if (pattern.test(text)) { violations.push( `${prefix}: suspicious coordination pattern (${pattern})` ); } } const urls = text.match(URL_PATTERN) ?? []; for (const url of urls) { try { const parsed = new URL(url); if (parsed.protocol === "file:" && !allowFileUrls) { violations.push(`${prefix}: file URL not allowed: ${url}`); continue; } const host = parsed.hostname; if (!allowLocalhost && (host === "localhost" || host === "127.0.0.1" || /^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^169\.254\./.test( host ))) { violations.push(`${prefix}: local/private URL not allowed: ${url}`); continue; } if (blockedHosts.length > 0 && hostMatches(host, blockedHosts)) { violations.push(`${prefix}: blocked host ${host}`); } if (allowedHosts.length > 0 && !hostMatches(host, allowedHosts)) { violations.push(`${prefix}: host ${host} outside allowlist`); } } catch { violations.push(`${prefix}: invalid URL format: ${url}`); } } if (blockBase64Payloads) { const runs = text.match(BASE64_RUN) ?? []; for (const run of runs) { if (run.length >= minBase64Length) { violations.push( `${prefix}: base64 gadget payload (${run.length} chars)` ); } } } return violations; } // src/guardrails/tools.ts function defaultMarker(tool) { return `TOOL_USED: ${tool}`; } function isRecord2(value) { return Boolean(value) && typeof value === "object"; } function pushIfString(value, target) { if (typeof value === "string" && value.trim().length > 0) { target.push(value); } } function extractFromContentArray(content) { const names = []; for (const contentItem of content) { if (!isRecord2(contentItem)) { continue; } if (contentItem.type !== "tool-call") { continue; } pushIfString(contentItem.toolName, names); } return names; } function extractFromToolCallsArray(toolCalls) { const names = []; for (const toolCall of toolCalls) { if (!isRecord2(toolCall)) { continue; } const potentialName = typeof toolCall.toolName === "string" ? toolCall.toolName : typeof toolCall.name === "string" ? toolCall.name : void 0; pushIfString(potentialName, names); } return names; } function extractToolNameFromItem(item) { if ("name" in item && typeof item.name === "string") { return item.name; } if ("tool" in item && typeof item.tool === "string") { return item.tool; } if ("type" in item && typeof item.type === "string") { return item.type; } if ("toolName" in item && typeof item.toolName === "string") { return item.toolName; } return void 0; } function extractFromCandidateArrays(candidates) { const names = []; for (const candidate of candidates) { if (!Array.isArray(candidate)) { continue; } for (const item of candidate) { if (!isRecord2(item)) { continue; } pushIfString(extractToolNameFromItem(item), names); } } return names; } function extractToolNamesFromResult(result) { const names = /* @__PURE__ */ new Set(); const resultWithUnknownProps = result; const md = resultWithUnknownProps?.experimental_providerMetadata ?? resultWithUnknownProps?.providerMetadata ?? {}; if (Array.isArray(resultWithUnknownProps.content)) { const contentNames = extractFromContentArray( resultWithUnknownProps.content ); for (const name of contentNames) { names.add(name); } } if (Array.isArray(resultWithUnknownProps.toolCalls)) { const toolCallNames = extractFromToolCallsArray( resultWithUnknownProps.toolCalls ); for (const name of toolCallNames) { names.add(name); } } const candidates = [ md.toolCalls, resultWithUnknownProps.toolCalls, md.tools, resultWithUnknownProps.tools, md.calledTools, resultWithUnknownProps.calledTools ].filter(Boolean); const candidateNames = extractFromCandidateArrays(candidates); for (const name of candidateNames) { names.add(name); } return [...names]; } function extractProviderTools(result, mode, providerExtractor) { if (mode === "auto" || mode === "provider") { const providerTools = providerExtractor ? providerExtractor(result) : extractToolNamesFromResult(result); if (providerTools.length > 0) { return { tools: providerTools, usedProvider: true }; } } return { tools: [], usedProvider: false }; } function getToolMarkers(tool, textMarkers) { if (Array.isArray(textMarkers)) { return textMarkers; } if (typeof textMarkers === "function") { const built = textMarkers(tool); return Array.isArray(built) ? built : [built]; } return [defaultMarker(tool)]; } function extractMarkerTools(result, expected, mode, textMarkers, observedToolsLength = 0) { if (mode === "auto" && observedToolsLength === 0 || mode === "marker") { const { text } = extractContent(result); const observedMarkers = []; for (const tool of expected) { const markers = getToolMarkers(tool, textMarkers); const found = markers.find((m) => m && text.includes(m)); if (found) { observedMarkers.push(found); } } return { markers: observedMarkers, usedMarkers: observedMarkers.length > 0 }; } return { markers: [], usedMarkers: false }; } function determineDetectionType(usedProvider, usedMarkers) { if (usedProvider && usedMarkers) { return "mixed"; } if (usedProvider) { return "provider"; } if (usedMarkers) { return "marker"; } return "none"; } function expectedToolUse(options) { const { tools, requireAll = true, mode = "auto", textMarkers, providerExtractor, retry: retryConfig } = options; const expected = Array.isArray(tools) ? tools : [tools]; return { name: "expected-tool-use", retry: retryConfig, execute: (context) => { const { result } = context; const { tools: observedTools, usedProvider } = extractProviderTools( result, mode, providerExtractor ); const { markers: observedMarkers, usedMarkers } = extractMarkerTools( result, expected, mode, textMarkers, observedTools.length ); const detectedTools = /* @__PURE__ */ new Set([ ...observedTools, ...observedMarkers.map((m) => m.replace(/^TOOL_USED:\s*/, "")) ]); const missing = expected.filter((t) => !detectedTools.has(t)); const passed = requireAll ? missing.length === 0 : detectedTools.size > 0; const metadata = { expectedTools: expected, observedTools: [...new Set(observedTools)], observedMarkers, missingTools: missing, detection: determineDetectionType(usedProvider, usedMarkers) }; if (!passed) { return { tripwireTriggered: true, severity: "medium", message: expected.length === 1 ? `Expected tool not used: ${expected[0]}` : `Expected tool(s) missing: ${missing.join(", ")}`, metadata, info: { guardrailName: "expected-tool-use", expectedTools: expected, observedTools: [...new Set(observedTools)], missingTools: missing, detectionMethod: determineDetectionType(usedProvider, usedMarkers) } }; } return { tripwireTriggered: false, metadata: { ...metadata, missingTools: [] }, info: { guardrailName: "expected-tool-use", expectedTools: expected, observedTools: [...new Set(observedTools)], missingTools: [], detectionMethod: determineDetectionType(usedProvider, usedMarkers) } }; }, getRetryInstruction: (ctx) => { const missingTools = ctx.result.metadata?.missingTools ?? expected; const attempt = ctx.attempt; const toolName = missingTools[0] ?? "the required tool"; if (missingTools.length === 1) { const urgency = attempt > 1 ? "CRITICAL: " : ""; return { message: `${urgency}Your previous response did not call any tools. You MUST call the "${toolName}" tool function NOW. Do not respond with text - invoke the tool first. This is attempt ${attempt}.`, context: { missingTools, attempt } }; } return { message: `REQUIRED: You must call these tool functions: ${missingTools.join(", ")}. Do not respond with text until you have called the tools. Attempt ${attempt}.`, context: { missingTools, attempt } }; } }; } function toolEgressPolicy(options = {}) { const { allowedTools = [], deniedTools = [], allowedHosts = [], blockedHosts = [], parameterRules = {}, scanForUrls = true, allowFileUrls = false, allowLocalhost = false } = options; return createOutputGuardrail( "tool-egress-policy", (context) => { const { result } = context; const { text } = extractContent(result); const { tools: observedTools } = extractProviderTools(result, "auto"); const violations = []; const detectedIssues = []; for (const tool of observedTools) { if (deniedTools.includes(tool)) { violations.push(`Tool '${tool}' is explicitly denied`); detectedIssues.push({ tool, issue: "explicitly denied", severity: "high" }); continue; } if (allowedTools.length > 0 && !allowedTools.includes(tool)) { violations.push(`Tool '${tool}' is not in allowlist`); detectedIssues.push({ tool, issue: "not in allowlist", severity: "medium" }); continue; } const rules = parameterRules[tool]; if (rules && rules.maxParamLength) { const toolMention = text.match( new RegExp(String.raw`${tool}.*?(?=\n|$)`, "i") ); if (toolMention && toolMention[0].length > rules.maxParamLength) { violations.push(`Tool '${tool}' parameter length exceeds limit`); detectedIssues.push({ tool, issue: "parameter length exceeded", severity: "medium" }); } } } if (scanForUrls) { const blockedPatterns = blockedHosts.map( (h) => typeof h === "string" ? h : h ); for (const payload of extractToolCallPayloads(result)) { const argViolations = scanTextEgressViolations( payload.argsText, { allowedHosts, blockedHosts: blockedPatterns, allowFileUrls, allowLocalhost }, `tool:${payload.toolName}` ); for (const v of argViolations) { violations.push(v); detectedIssues.push({ tool: payload.toolName, issue: v, severity: "high" }); } } } if (scanForUrls) { const urlPattern = /https?:\/\/[^\s]+|ftp:\/\/[^\s]+|file:\/\/[^\s]+/gi; const urls = text.match(urlPattern) || []; for (const url of urls) { try { const parsed = new URL(url); if (parsed.protocol === "file:" && !allowFileUrls) { violations.push(`File URL detected and not allowed: ${url}`); detectedIssues.push({ tool: "url-scan", issue: "file URL not allowed", severity: "high" }); continue; } if (!allowLocalhost && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname.startsWith("192.168.") || parsed.hostname.startsWith("10.") || parsed.hostname.startsWith("172."))) { violations.push(`Local URL detected and not allowed: ${url}`); detectedIssues.push({ tool: "url-scan", issue: "local URL not allowed", severity: "high" }); continue; } if (blockedHosts.includes(parsed.hostname)) { violations.push(`Blocked host detected: ${parsed.hostname}`); detectedIssues.push({ tool: "url-scan", issue: "blocked host", severity: "high" }); continue; } if (allowedHosts.length > 0 && !allowedHosts.includes(parsed.hostname)) { violations.push(`Host not in allowlist: ${parsed.hostname}`); detectedIssues.push({ tool: "url-scan", issue: "host not in allowlist", severity: "medium" }); } } catch { violations.push(`Invalid URL format detected: ${url}`); detectedIssues.push({ tool: "url-scan", issue: "invalid URL format", severity: "low" }); } } } if (violations.length > 0) { const highSeverityCount = detectedIssues.filter( (i) => i.severity === "high" ).length; const mediumSeverityCount = detectedIssues.filter( (i) => i.severity === "medium" ).length; return { tripwireTriggered: true, message: `Tool egress policy violations: ${violations.join("; ")}`, severity: highSeverityCount > 0 ? "critical" : mediumSeverityCount > 0 ? "high" : "medium", metadata: { violationCount: violations.length, violations, detectedIssues, observedTools, allowedTools, deniedTools, urlsScanned: scanForUrls }, suggestion: "Review tool usage and URL access patterns for security compliance", info: { guardrailName: "tool-egress-policy", violationCount: violations.length, violations, detectedIssues, observedTools, allowedTools, deniedTools, urlsScanned: scanForUrls } }; } return { tripwireTriggered: false, metadata: { observedTools, urlsScanned: scanForUrls, policyEnforced: true }, info: { guardrailName: "tool-egress-policy", observedTools, urlsScanned: scanForUrls, policyEnforced: true } }; } ); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { expectedToolUse, extractToolNamesFromResult, toolEgressPolicy });