UNPKG

ai-sdk-guardrails

Version:

Input and output guardrails middleware for Vercel AI SDK.

333 lines (327 loc) 10.3 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/governance/index.ts var governance_exports = {}; __export(governance_exports, { ToolScopeDeniedError: () => ToolScopeDeniedError, __setAutotelAgentModule: () => __setAutotelAgentModule, approvalStatusToPolicyDecision: () => approvalStatusToPolicyDecision, guardrailGovernance: () => guardrailGovernance, guardrailGovernanceApproval: () => guardrailGovernanceApproval, guardrailNameOf: () => guardrailNameOf, guardrailTelemetry: () => guardrailTelemetry, policyDecision: () => policyDecision, recordPlanRisk: () => recordPlanRisk, severityToRiskScore: () => severityToRiskScore, toPolicyDecisionMetadata: () => toPolicyDecisionMetadata, withGuardedTool: () => withGuardedTool }); module.exports = __toCommonJS(governance_exports); // src/governance/peer.ts var cached; async function loadAutotelAgent() { if (cached !== void 0) return cached; try { const specifier = "autotel-genai/agent"; const mod = await import( /* @vite-ignore */ specifier ); cached = mod; } catch { cached = null; } return cached; } function __setAutotelAgentModule(mod) { cached = mod; } async function withAgent(options, body) { const agent = await loadAutotelAgent(); if (!agent) return; body(agent, { onMissingContext: options.onMissingContext ?? "skip" }); } function tryEmit(fn) { try { fn(); } catch { } } // src/severity.ts var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 }; function severityToRisk(severity) { return (SEVERITY_RANK[severity] + 1) / 4; } // src/governance/mappers.ts function severityToRiskScore(result) { if (typeof result.confidence === "number") return result.confidence; if (result.severity) return severityToRisk(result.severity); return void 0; } function guardrailNameOf(result) { const fromContext = result.context?.guardrailName; const fromInfo = result.info?.guardrailName; return typeof fromContext === "string" && fromContext || typeof fromInfo === "string" && fromInfo || "guardrail"; } function observedToolsOf(result) { const fromMeta = result.metadata?.observedTools; const fromInfo = result.info?.observedTools; const tools = Array.isArray(fromMeta) ? fromMeta : Array.isArray(fromInfo) ? fromInfo : []; return tools.filter((t) => typeof t === "string"); } function riskScoreOf(result, options) { return (options.riskScore ?? severityToRiskScore)(result); } function policyDecision(input) { return { action: input.action, resource: input.resource, agent: input.agent, eventKind: "policy_decision", category: input.category, delegation: input.delegation, policy: { decision: input.decision, riskScore: input.riskScore, reason: input.reason }, reasoningSummary: input.reasoningSummary }; } function toPolicyDecisionMetadata(result, options) { return policyDecision({ action: guardrailNameOf(result), agent: options.agent, decision: "deny", riskScore: riskScoreOf(result, options), reason: result.message, category: result.severity, reasoningSummary: result.message }); } function approvalStatusToPolicyDecision(statusType) { switch (statusType) { case "approved": { return "permit"; } case "denied": { return "deny"; } case "user-approval": { return "challenge"; } case "not-applicable": { return void 0; } } } // src/governance/index.ts async function emitBlocked(stage, summary, options) { await withAgent(options, (agent, emit) => { for (const result of summary.blockedResults) { tryEmit( () => agent.recordPolicyDecision( toPolicyDecisionMetadata(result, options), emit ) ); for (const toolName of observedToolsOf(result)) { const riskClass = options.toolRiskClass?.(toolName); if (riskClass) tryEmit(() => agent.recordActionRiskClass(riskClass)); } if (options.signBlockedEvents && agent.createSignedEventEnvelope) { const signOpts = typeof options.signBlockedEvents === "object" ? options.signBlockedEvents : void 0; const raw = { action: `guardrail.block.${stage}`, resource: guardrailNameOf(result), category: "policy", outcome: "denied", agent: options.agent, policy: { decision: "deny", reason: result.message } }; const metadata = agent.createAgentAuditMetadata ? agent.createAgentAuditMetadata(raw) : raw; void agent.createSignedEventEnvelope(metadata, signOpts).catch(() => { }); } } if (stage === "input") { const { controllerId, hashSalt } = options; if (controllerId) { tryEmit(() => agent.recordControllerId({ controllerId, hashSalt })); } tryEmit( () => agent.recordInputProvenance({ provenance: options.inputProvenance ?? "external_untrusted" }) ); } }); } function guardrailGovernance(options) { return { onInputBlocked: (summary) => { void emitBlocked("input", summary, options); }, onOutputBlocked: (summary) => { void emitBlocked("output", summary, options); } }; } function guardrailGovernanceApproval(options) { return (info) => { void withAgent(options, (agent, emit) => { const riskClass = options.toolRiskClass?.(info.toolName); if (riskClass) tryEmit(() => agent.recordActionRiskClass(riskClass)); const decision = approvalStatusToPolicyDecision(info.status.type); if (!decision) return; tryEmit( () => agent.recordPolicyDecision( policyDecision({ action: info.guardrail ?? info.toolName, agent: options.agent, decision, riskScore: info.result ? riskScoreOf(info.result, options) : void 0, reason: info.result?.message, category: info.result?.severity }), emit ) ); if (info.status.type === "denied" || info.status.type === "user-approval") { tryEmit( () => agent.recordHumanApproval({ // The real SDK tool-call id when threaded; omitted otherwise rather // than forged from the tool name. toolCallId: info.toolCallId, toolName: info.toolName, approved: false, required: info.status.type === "user-approval", controllerId: options.controllerId, hashSalt: options.hashSalt }) ); } }); }; } function guardrailTelemetry(options) { const observeTool = (toolName) => { void withAgent(options, (agent, emit) => { const riskClass = options.toolRiskClass?.(toolName); if (riskClass) tryEmit(() => agent.recordActionRiskClass(riskClass)); tryEmit( () => agent.recordPolicyDecision( policyDecision({ action: `tool.${toolName}`, resource: toolName, agent: options.agent, decision: "observe" }), emit ) ); }); }; return { onToolExecutionStart: ({ toolCall }) => { observeTool(toolCall.toolName); } }; } function recordPlanRisk(assessment, toolSequence, options = {}) { void withAgent({}, (agent) => { tryEmit( () => agent.recordPlanRiskAssessment({ assessment, toolSequence, emitSecurityEvent: options.emitSecurityEvent }) ); }); } var ToolScopeDeniedError = class extends Error { toolName; missingScopes; constructor(toolName, missingScopes) { super( `Tool "${toolName}" denied \u2014 missing scope(s): ${missingScopes.join(", ")}` ); this.name = "ToolScopeDeniedError"; this.toolName = toolName; this.missingScopes = missingScopes; } }; function withGuardedTool(tool, options) { if (typeof tool.execute !== "function") return tool; const required = options.requiredScopes ?? []; const granted = new Set(options.grantedScopes); const original = tool.execute.bind(tool); const guardedExecute = async (...args) => { const missing = required.filter((scope) => !granted.has(scope)); const denied = missing.length > 0; await withAgent(options, (agent, emit) => { const { riskClass, controllerId, hashSalt } = options; if (riskClass) tryEmit(() => agent.recordActionRiskClass(riskClass)); tryEmit( () => agent.recordPolicyDecision( policyDecision({ action: `tool.${options.toolName}`, resource: options.toolName, agent: options.agent, delegation: options.delegation, decision: denied ? "deny" : "permit", reason: denied ? `missing_scope:${missing.join(",")}` : void 0 }), emit ) ); if (denied && controllerId) { tryEmit(() => agent.recordControllerId({ controllerId, hashSalt })); } }); if (denied) { throw new ToolScopeDeniedError(options.toolName, missing); } return original(...args); }; return { ...tool, execute: guardedExecute }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ToolScopeDeniedError, __setAutotelAgentModule, approvalStatusToPolicyDecision, guardrailGovernance, guardrailGovernanceApproval, guardrailNameOf, guardrailTelemetry, policyDecision, recordPlanRisk, severityToRiskScore, toPolicyDecisionMetadata, withGuardedTool });