ai-sdk-guardrails
Version:
Input and output guardrails middleware for Vercel AI SDK.
333 lines (327 loc) • 10.3 kB
JavaScript
;
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
});