ai-sdk-guardrails
Version:
Input and output guardrails middleware for Vercel AI SDK.
2,718 lines • 89.1 kB
JavaScript
import {
ToolParameterValidationError,
detectSystemPromptLeak,
withToolParameterGuardrails
} from "../chunk-R2CDCQQ3.js";
import {
createDefaultBuildRetryParams,
defineInputGuardrail,
executeInputGuardrails,
executeOutputGuardrails,
inputGuardrailsMiddleware,
normalizeGuardrailContext,
outputGuardrailsMiddleware,
resolveRetryConfig,
snapshotGenerateResultText,
syncGenerateResultTextAfterGuardrails
} from "../chunk-GYV7GURW.js";
import {
DEFAULT_DETECT_NORMALIZATION,
normalizeForDetection,
resolveDetectNormalization
} from "../chunk-JFULGOXO.js";
import "../chunk-WC2PXTRI.js";
import {
GuardrailsInputError,
GuardrailsOutputError
} from "../chunk-F7POYYOU.js";
// src/guardrails/enhanced-prompt-injection.ts
var RE_CALL_MALICIOUS_PARAMS = /call\s+(\w+)\s+with\s+malicious\s+params/i;
var RE_OVERRIDE_FUNCTION = /override\s+function\s+(\w+)/i;
var ENHANCED_INJECTION_PATTERNS = [
// Direct instruction injection
{
pattern: /ignore\s+previous\s+instructions/i,
weight: 0.9,
type: "direct_injection",
semanticKeywords: ["ignore", "previous", "instructions"],
contextIndicators: ["system", "prompt", "override"]
},
{
pattern: /system\s*:\s*you\s+are\s+now/i,
weight: 0.8,
type: "role_confusion",
semanticKeywords: ["system", "you", "are", "now"],
contextIndicators: ["role", "identity", "persona"]
},
{
pattern: /forget\s+everything\s+above/i,
weight: 0.9,
type: "context_override",
semanticKeywords: ["forget", "everything", "above"],
contextIndicators: ["memory", "history", "context"]
},
// Tool manipulation patterns
{
pattern: RE_CALL_MALICIOUS_PARAMS,
weight: 0.9,
type: "tool_manipulation",
semanticKeywords: ["call", "malicious", "params"],
contextIndicators: ["function", "tool", "execute"]
},
{
pattern: RE_OVERRIDE_FUNCTION,
weight: 0.95,
type: "function_override",
semanticKeywords: ["override", "function"],
contextIndicators: ["code", "execution", "security"]
},
// Cascade attack patterns
{
pattern: /now\s+that\s+you\s+are\s+in\s+(\w+)\s+mode/i,
weight: 0.8,
type: "cascade_attempt",
semanticKeywords: ["now", "that", "you", "are", "mode"],
contextIndicators: ["state", "mode", "persona"]
},
// Authority manipulation
{
pattern: /(?:security\s+team|administrator|system\s+admin|emergency|urgent)/i,
weight: 0.7,
type: "authority_manipulation",
semanticKeywords: ["security", "admin", "emergency", "urgent"],
contextIndicators: ["authority", "priority", "override"]
}
];
var TOOL_CALL_INJECTION_PATTERNS = [
{
pattern: RE_CALL_MALICIOUS_PARAMS,
weight: 0.9,
injectionType: "parameter_manipulation"
},
{
pattern: RE_OVERRIDE_FUNCTION,
weight: 0.95,
injectionType: "function_override"
},
{
pattern: /chain\s+(\w+)\s+to\s+(\w+)\s+to\s+(\w+)/i,
weight: 0.8,
injectionType: "tool_chain_attack"
},
{
pattern: /execute\s+(\w+)\s+as\s+root/i,
weight: 0.9,
injectionType: "function_override"
}
];
function wordOverlap(a, b) {
const aw = a.toLowerCase().split(/\s+/).filter(Boolean);
const bw = b.toLowerCase().split(/\s+/).filter(Boolean);
if (aw.length === 0 || bw.length === 0) return 0;
const common = aw.filter((word) => bw.includes(word));
return common.length / Math.max(aw.length, bw.length);
}
function scorePattern(content, normalize) {
return normalize === false ? calculatePatternScore(content) : Math.max(
calculatePatternScore(content),
calculatePatternScore(normalizeForDetection(content, normalize))
);
}
function calculatePatternScore(content) {
let totalScore = 0;
let matchCount = 0;
for (const { pattern, weight } of ENHANCED_INJECTION_PATTERNS) {
const matches = content.match(pattern);
if (matches) {
totalScore += weight;
matchCount++;
}
}
return matchCount > 0 ? Math.min(totalScore / matchCount, 1) : 0;
}
function analyzeContextCoherence(content) {
const sentences = content.split(/[.!?]+/).filter((s) => s.trim().length > 0);
if (sentences.length === 0) return 0;
let coherenceScore = 1;
for (let i = 1; i < sentences.length; i++) {
const prevSentence = sentences[i - 1];
const currSentence = sentences[i];
if (!prevSentence || !currSentence) continue;
if (wordOverlap(prevSentence, currSentence) < 0.2) {
coherenceScore -= 0.2;
}
}
return Math.max(coherenceScore, 0);
}
function analyzeIncremental(context, currentContent, currentScore, conversationMemory, normalize) {
const history = [];
if (typeof context !== "string" && "messages" in context && Array.isArray(context.messages)) {
for (const message of context.messages) {
const text = typeof message.content === "string" ? message.content : "";
history.push({ content: text, score: scorePattern(text, normalize) });
}
}
if (history.length === 0) {
history.push({ content: currentContent, score: currentScore });
}
const recent = history.slice(-Math.max(conversationMemory, 1));
const cumulativeScore = recent.reduce((sum, m) => sum + m.score, 0) / recent.length;
let contextShifts = 0;
for (let i = 1; i < recent.length; i++) {
if (wordOverlap(recent[i - 1].content, recent[i].content) < 0.3) {
contextShifts++;
}
}
return { cumulativeScore, contextShifts, messageCount: recent.length };
}
function analyzeConversationFlow(context) {
if (typeof context === "string") {
const suspiciousFlowPatterns = [
/suddenly\s+change/i,
/now\s+forget/i,
/switch\s+to/i,
/pretend\s+you\s+are/i
];
let flowScore = 1;
for (const pattern of suspiciousFlowPatterns) {
if (pattern.test(context)) {
flowScore -= 0.3;
}
}
return Math.max(flowScore, 0);
}
return 0.8;
}
function analyzeSemanticSimilarity(content) {
const suspiciousSemanticPatterns = [
/ignore|forget|disregard/i,
/override|bypass|skip/i,
/system|admin|root/i,
/emergency|urgent|critical/i
];
let semanticScore = 1;
for (const pattern of suspiciousSemanticPatterns) {
if (pattern.test(content)) {
semanticScore -= 0.2;
}
}
return Math.max(semanticScore, 0);
}
function detectBehavioralAnomalies(context) {
let contentToCheck;
if (typeof context === "string") {
contentToCheck = context.toLowerCase();
} else if (typeof context === "object" && context !== null) {
try {
contentToCheck = JSON.stringify(context).toLowerCase();
} catch {
contentToCheck = String(context).toLowerCase();
}
} else {
return 0.9;
}
const anomalies = [
contentToCheck.includes("ignore previous"),
contentToCheck.includes("system:"),
contentToCheck.includes("forget everything"),
contentToCheck.includes("act as if"),
contentToCheck.includes("pretend to be")
];
const anomalyCount = anomalies.filter(Boolean).length;
return Math.max(1 - anomalyCount * 0.2, 0);
}
function calculateWeightedScore(scores, weights = {}) {
const defaultWeights = {
pattern: 0.4,
context: 0.2,
flow: 0.2,
semantic: 0.1,
behavior: 0.1
};
const finalWeights = { ...defaultWeights, ...weights };
return scores.pattern * finalWeights.pattern + scores.context * finalWeights.context + scores.flow * finalWeights.flow + scores.semantic * finalWeights.semantic + scores.behavior * finalWeights.behavior;
}
function extractUserIntent(content) {
const suspiciousElements = [];
const manipulationIndicators = [];
if (/ignore|forget|disregard/i.test(content)) {
manipulationIndicators.push("instruction_ignoring");
}
if (/system|admin|root/i.test(content)) {
manipulationIndicators.push("authority_claim");
}
if (/override|bypass/i.test(content)) {
manipulationIndicators.push("system_override");
}
let primaryIntent = "general_query";
if (/help|assist|support/i.test(content)) {
primaryIntent = "help_request";
} else if (/explain|describe|tell/i.test(content)) {
primaryIntent = "information_request";
} else if (/create|generate|make/i.test(content)) {
primaryIntent = "creation_request";
}
return {
primaryIntent,
confidence: manipulationIndicators.length > 0 ? 0.3 : 0.8,
suspiciousElements,
contextShifts: 0,
// Would be calculated from conversation history
manipulationIndicators
};
}
function analyzeToolCalls(content) {
const suspiciousCalls = [];
for (const pattern of TOOL_CALL_INJECTION_PATTERNS) {
const matches = content.matchAll(new RegExp(pattern.pattern.source, "gi"));
for (const match of matches) {
suspiciousCalls.push({
tool: match[1] || "unknown",
confidence: pattern.weight,
injectionType: pattern.injectionType
});
}
}
return suspiciousCalls;
}
var enhancedPromptInjectionDetector = (options = {}) => {
const {
enableIncremental = true,
enableToolCallFocus = true,
enableIntentExtraction = true,
confidenceThreshold = 0.7,
cumulativeThreshold = 0.5,
conversationMemory = 10,
weights = {},
normalize = true
} = options;
return defineInputGuardrail({
name: "enhanced-prompt-injection",
description: "Enhanced prompt injection detection with incremental checking, confidence scoring, tool call focus, and intent extraction",
execute: async (context) => {
let content = "";
if (typeof context === "string") {
content = context;
} else if ("prompt" in context && typeof context.prompt === "string") {
content = context.prompt;
} else if ("messages" in context && Array.isArray(context.messages)) {
content = context.messages.map((m) => typeof m.content === "string" ? m.content : "").join(" ");
}
const patternScore = scorePattern(content, normalize);
const contextScore = analyzeContextCoherence(content);
const flowScore = analyzeConversationFlow(context);
const semanticScore = analyzeSemanticSimilarity(content);
const behaviorScore = detectBehavioralAnomalies(context);
const enhancedScore = {
patternMatch: patternScore,
contextCoherence: contextScore,
conversationFlow: flowScore,
semanticSimilarity: semanticScore,
behavioralAnomaly: behaviorScore,
finalScore: calculateWeightedScore(
{
pattern: patternScore,
context: contextScore,
flow: flowScore,
semantic: semanticScore,
behavior: behaviorScore
},
weights
)
};
const incrementalAnalysis = enableIncremental ? analyzeIncremental(
context,
content,
enhancedScore.finalScore,
conversationMemory,
normalize
) : null;
let toolCallAnalysis = null;
if (enableToolCallFocus) {
const suspiciousCalls = analyzeToolCalls(content);
toolCallAnalysis = {
suspiciousCalls: suspiciousCalls.length,
detectedCalls: suspiciousCalls
};
}
const intentAnalysis = enableIntentExtraction ? extractUserIntent(content) : null;
const isInjectionDetected = Boolean(
enhancedScore.finalScore > confidenceThreshold || incrementalAnalysis && incrementalAnalysis.cumulativeScore > cumulativeThreshold || toolCallAnalysis && toolCallAnalysis.suspiciousCalls > 0
);
const metadata = {
enhancedScore,
incrementalAnalysis,
toolCallAnalysis,
intentAnalysis,
analysisType: "enhanced_multi_factor",
features: {
incremental: enableIncremental,
toolCallFocus: enableToolCallFocus,
intentExtraction: enableIntentExtraction
}
};
return {
tripwireTriggered: isInjectionDetected,
message: isInjectionDetected ? `Enhanced prompt injection detected (confidence: ${(enhancedScore.finalScore * 100).toFixed(1)}%)` : void 0,
severity: enhancedScore.finalScore > 0.8 ? "critical" : "high",
metadata,
suggestion: isInjectionDetected ? "Please rephrase your request without system instructions, role-playing elements, or tool manipulation attempts" : void 0,
info: {
guardrailName: "enhanced-prompt-injection",
confidence: enhancedScore.finalScore,
isInjectionDetected
}
};
}
});
};
var incrementalPromptInjectionDetector = (options = {}) => {
return enhancedPromptInjectionDetector({
...options,
enableIncremental: true,
enableToolCallFocus: false,
enableIntentExtraction: false
});
};
var toolCallInjectionDetector = (options = {}) => {
return enhancedPromptInjectionDetector({
...options,
enableIncremental: false,
enableToolCallFocus: true,
enableIntentExtraction: false
});
};
var intentBasedInjectionDetector = (options = {}) => {
return enhancedPromptInjectionDetector({
...options,
enableIncremental: false,
enableToolCallFocus: false,
enableIntentExtraction: true
});
};
// src/guardrails/middleware.ts
var emptyV4Usage = {
inputTokens: {
total: 0,
noCache: void 0,
cacheRead: void 0,
cacheWrite: void 0
},
outputTokens: {
total: 0,
text: void 0,
reasoning: void 0
}
};
var finishReasonOther = {
unified: "other",
raw: void 0
};
function guardrailMiddleware(config) {
const {
inputGuardrails = [],
outputGuardrails = [],
context,
throwOnBlocked = false,
replaceOnBlocked = true,
onInputBlocked,
onOutputBlocked,
executionOptions = {},
skipGuardrails = false
} = config;
const shouldSkip = (params) => {
if (typeof skipGuardrails === "function") {
return skipGuardrails(params);
}
return skipGuardrails;
};
return {
specificationVersion: "v4",
// Transform params to check input guardrails
transformParams: async ({ params }) => {
if (shouldSkip(params) || inputGuardrails.length === 0) {
return params;
}
const baseContext = normalizeGuardrailContext(params);
const normalizedContext = context ? { ...baseContext, requestContext: context } : baseContext;
const startTime = Date.now();
const results = await executeInputGuardrails(
inputGuardrails,
normalizedContext,
executionOptions
);
const blockedResults = results.filter((r) => r.tripwireTriggered);
if (blockedResults.length > 0) {
const summary = createExecutionSummary(results, startTime);
if (onInputBlocked) {
await onInputBlocked(summary, params);
}
if (throwOnBlocked) {
throw new GuardrailsInputError(
blockedResults.map((r) => ({
name: r.context?.guardrailName || "unknown",
message: r.message || "Blocked",
severity: r.severity || "medium"
}))
);
}
return {
...params,
_guardrailsBlocked: blockedResults
};
}
return params;
},
// Wrap generate to check output guardrails
wrapGenerate: async ({ doGenerate, params }) => {
const paramsWithGuardrails = params;
if (paramsWithGuardrails._guardrailsBlocked) {
const blockedMessage = "Input blocked by guardrails";
const blockedText = `[${blockedMessage}]`;
return {
text: blockedText,
content: [{ type: "text", text: blockedText }],
finishReason: finishReasonOther,
usage: emptyV4Usage,
warnings: [],
rawCall: { rawPrompt: params.prompt, rawSettings: {} },
response: { headers: {} }
};
}
if (shouldSkip(params) || outputGuardrails.length === 0) {
return doGenerate();
}
const result = await doGenerate();
const resultTextBeforeGuardrails = snapshotGenerateResultText(result);
const baseContext = normalizeGuardrailContext(params);
const normalizedContext = context ? { ...baseContext, requestContext: context } : baseContext;
const startTime = Date.now();
const outputResults = await executeOutputGuardrails(
outputGuardrails,
{
input: normalizedContext,
result
},
executionOptions
);
const blockedResults = outputResults.filter((r) => r.tripwireTriggered);
if (blockedResults.length > 0) {
const summary = createExecutionSummary(outputResults, startTime);
if (onOutputBlocked) {
await onOutputBlocked(summary, params, result);
}
if (throwOnBlocked) {
throw new GuardrailsOutputError(
blockedResults.map((r) => ({
name: r.context?.guardrailName || "unknown",
message: r.message || "Blocked",
severity: r.severity || "medium"
}))
);
}
if (replaceOnBlocked) {
const blockedMessage = blockedResults.map((r) => r.message).join("; ");
const blockedText = `[Output blocked: ${blockedMessage}]`;
return {
...result,
text: blockedText,
content: [{ type: "text", text: blockedText }]
};
}
}
return syncGenerateResultTextAfterGuardrails(
result,
resultTextBeforeGuardrails
);
},
// Wrap stream to check output guardrails (buffer mode for simplicity)
wrapStream: async ({ doStream, params }) => {
const paramsWithGuardrails = params;
if (paramsWithGuardrails._guardrailsBlocked) {
const blockedMessage = "Input blocked by guardrails";
const stream = new ReadableStream({
start(controller) {
controller.enqueue({
type: "text-delta",
id: "1",
delta: `[${blockedMessage}]`
});
controller.enqueue({
type: "finish",
finishReason: finishReasonOther,
usage: emptyV4Usage
});
controller.close();
}
});
return { stream };
}
if (shouldSkip(params) || outputGuardrails.length === 0) {
return doStream();
}
const streamResult = await doStream();
let accumulatedText = "";
let streamUsage = {};
let streamFinishReason;
const chunks = [];
const transformStream = new TransformStream({
transform(chunk) {
if (chunk.type === "text-delta") {
accumulatedText += chunk.delta || chunk.textDelta || "";
} else if (chunk.type === "finish") {
if (chunk.usage) {
streamUsage = chunk.usage;
}
if (chunk.finishReason) {
streamFinishReason = chunk.finishReason;
}
}
chunks.push(chunk);
},
async flush(controller) {
const baseContext = normalizeGuardrailContext(params);
const normalizedContext = context ? {
...baseContext,
requestContext: context
} : baseContext;
const streamedResult = {
text: accumulatedText,
content: [{ type: "text", text: accumulatedText }],
usage: streamUsage,
finishReason: streamFinishReason
};
const startTime = Date.now();
const outputResults = await executeOutputGuardrails(
outputGuardrails,
{
input: normalizedContext,
result: streamedResult
},
executionOptions
);
const blockedResults = outputResults.filter(
(r) => r.tripwireTriggered
);
if (blockedResults.length > 0) {
const summary = createExecutionSummary(
outputResults,
startTime
);
if (onOutputBlocked) {
await onOutputBlocked(summary, params, streamedResult);
}
if (throwOnBlocked) {
controller.error(
new GuardrailsOutputError(
blockedResults.map((r) => ({
name: r.context?.guardrailName || "unknown",
message: r.message || "Blocked",
severity: r.severity || "medium"
}))
)
);
return;
}
if (replaceOnBlocked) {
const blockedMessage = blockedResults.map((r) => r.message).join("; ");
controller.enqueue({
type: "text-delta",
id: "1",
delta: `[Output blocked: ${blockedMessage}]`
});
controller.enqueue({
type: "finish",
finishReason: finishReasonOther,
usage: emptyV4Usage
});
return;
}
}
for (const chunk of chunks) {
controller.enqueue(chunk);
}
}
});
return { stream: streamResult.stream.pipeThrough(transformStream) };
}
};
}
function createExecutionSummary(results, startTime) {
const endTime = Date.now();
const blockedResults = results.filter((r) => r.tripwireTriggered);
return {
allResults: results,
blockedResults,
totalExecutionTime: endTime - startTime,
guardrailsExecuted: results.length,
stats: {
passed: results.filter((r) => !r.tripwireTriggered).length,
blocked: blockedResults.length,
failed: results.filter(
(r) => r.severity === "critical" && r.tripwireTriggered
).length,
averageExecutionTime: 0
}
};
}
function noopGuardrailMiddleware() {
return guardrailMiddleware({
skipGuardrails: true
});
}
// src/guardrails/composition.ts
function when(condition, guardrail) {
return {
...guardrail,
name: `when(${guardrail.name})`,
execute: async (context, ...rest) => {
const shouldExecute = await condition(context);
if (!shouldExecute) {
return {
tripwireTriggered: false,
message: "Condition not met, skipped"
};
}
return guardrail.execute(context, ...rest);
}
};
}
function after(prerequisite, guardrail) {
return {
...guardrail,
name: `after(${prerequisite.name}, ${guardrail.name})`,
execute: async (context, ...rest) => {
const prereqResult = await prerequisite.execute(
context,
...rest
);
if (prereqResult.tripwireTriggered) {
return prereqResult;
}
return guardrail.execute(context, ...rest);
}
};
}
function withFallback(primary, fallback, options = {}) {
const { timeoutMs = 3e4 } = options;
return {
...primary,
name: `withFallback(${primary.name}, ${fallback.name})`,
execute: async (context, ...rest) => {
try {
const result = await Promise.race([
primary.execute(context, ...rest),
new Promise(
(_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs)
)
]);
return result;
} catch (error) {
console.warn(
`Primary guardrail "${primary.name}" failed, using fallback "${fallback.name}":`,
error
);
return fallback.execute(context, ...rest);
}
}
};
}
function parallel(guardrails, options = {}) {
const { mode = "any", timeoutMs = 3e4 } = options;
const names = guardrails.map((g) => g.name).join(", ");
return {
name: `parallel(${names})`,
description: `Parallel execution of: ${names}`,
execute: async (context, ...rest) => {
const promises = guardrails.map(async (g) => {
try {
return await Promise.race([
g.execute(context, ...rest),
new Promise(
(_, reject) => setTimeout(
() => reject(new Error(`Timeout: ${g.name}`)),
timeoutMs
)
)
]);
} catch (error) {
return {
tripwireTriggered: true,
message: `Guardrail "${g.name}" failed: ${error instanceof Error ? error.message : "Unknown error"}`,
severity: "high",
metadata: {
error: String(error),
guardrailName: g.name
}
};
}
});
const results = await Promise.all(promises);
const triggered = results.filter((r) => r.tripwireTriggered);
if (mode === "any" && triggered.length > 0) {
const messages = triggered.map((r) => r.message).join("; ");
return {
...triggered[0],
message: messages,
metadata: {
...triggered[0]?.metadata,
allTriggered: triggered
}
};
}
if (mode === "all" && triggered.length === results.length) {
const messages = triggered.map((r) => r.message).join("; ");
return {
tripwireTriggered: true,
message: `All guardrails triggered: ${messages}`,
severity: triggered.reduce(
(max, r) => compareSeverity(r.severity, max) > 0 ? r.severity : max,
"low"
),
metadata: { allTriggered: triggered }
};
}
return { tripwireTriggered: false };
}
};
}
function createPipeline(guardrails, options = {}) {
const { name = "pipeline", shortCircuitOnBlock = true } = options;
return {
name,
description: `Pipeline: ${guardrails.map((g) => g.name).join(" -> ")}`,
execute: async (context, ...rest) => {
const results = [];
for (const guardrail of guardrails) {
const result = await guardrail.execute(context, ...rest);
results.push(result);
if (result.tripwireTriggered && shortCircuitOnBlock) {
return {
...result,
metadata: {
...result.metadata,
pipelineStage: guardrail.name,
completedStages: results.length,
totalStages: guardrails.length
}
};
}
}
return {
tripwireTriggered: false,
metadata: {
completedStages: results.length,
totalStages: guardrails.length
}
};
}
};
}
function not(guardrail) {
return {
...guardrail,
name: `not(${guardrail.name})`,
execute: async (context, ...rest) => {
const result = await guardrail.execute(context, ...rest);
return {
...result,
tripwireTriggered: !result.tripwireTriggered,
message: result.tripwireTriggered ? "Passed (negated)" : `Blocked (negated): ${result.message || "Condition not met"}`
};
}
};
}
function withRetry(guardrail, options = {}) {
const {
maxRetries = 3,
backoffMs = 1e3,
retryOn = (r) => r.severity === "critical" && r.metadata?.error
} = options;
return {
...guardrail,
name: `withRetry(${guardrail.name})`,
execute: async (context, ...rest) => {
let lastResult = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await guardrail.execute(context, ...rest);
if (!result.tripwireTriggered || !retryOn(result)) {
return result;
}
lastResult = result;
} catch (error) {
lastResult = {
tripwireTriggered: true,
message: `Execution failed: ${error instanceof Error ? error.message : "Unknown"}`,
severity: "critical",
metadata: { error: String(error), attempt }
};
}
if (attempt < maxRetries) {
const delay = typeof backoffMs === "function" ? backoffMs(attempt + 1) : backoffMs;
await new Promise((r) => setTimeout(r, delay));
}
}
return lastResult || { tripwireTriggered: false };
}
};
}
function compareSeverity(a, b) {
const order = { low: 1, medium: 2, high: 3, critical: 4 };
return (order[a || "medium"] || 2) - (order[b || "medium"] || 2);
}
var inputPipeline = (guardrails, options) => createPipeline(guardrails, options);
var outputPipeline = (guardrails, options) => createPipeline(guardrails, options);
// src/guardrails/gradual-enforcement.ts
var violationStorage = /* @__PURE__ */ new Map();
function getViolationRecord(key, windowMs) {
const now = Date.now();
let record = violationStorage.get(key);
if (!record || now - record.windowStart > windowMs) {
record = {
count: 0,
windowStart: now,
bySeverity: {}
};
violationStorage.set(key, record);
}
return record;
}
function incrementViolation(key, windowMs, severity) {
const record = getViolationRecord(key, windowMs);
record.count++;
if (severity) {
record.bySeverity[severity] = (record.bySeverity[severity] || 0) + 1;
}
violationStorage.set(key, record);
return record;
}
function withGradualEnforcement(guardrail, options) {
const {
mode,
escalation,
gracePeriod,
onWarn,
onEscalation,
storageKey = guardrail.name
} = options;
return {
...guardrail,
name: `gradual(${guardrail.name})`,
description: `${guardrail.description || guardrail.name} [mode: ${mode}]`,
execute: async (context, ...rest) => {
const result = await guardrail.execute(context, ...rest);
if (!result.tripwireTriggered) {
return result;
}
if (gracePeriod && /* @__PURE__ */ new Date() < gracePeriod.until) {
const logFn = gracePeriod.logLevel === "debug" ? console.debug : gracePeriod.logLevel === "info" ? console.info : console.warn;
logFn(
`[Grace Period] ${guardrail.name}: ${result.message}`,
gracePeriod.message || `Enforcement begins ${gracePeriod.until.toISOString()}`
);
return {
...result,
tripwireTriggered: false,
metadata: {
...result.metadata,
gradualEnforcement: {
mode: "grace-period",
wouldBlock: true,
gracePeriodUntil: gracePeriod.until.toISOString()
}
}
};
}
switch (mode) {
case "warn": {
const record = incrementViolation(
storageKey,
escalation?.windowMs || 6e4,
result.severity
);
const stats = {
count: record.count,
windowStart: new Date(record.windowStart),
isBlocking: false,
bySeverity: record.bySeverity
};
if (onWarn) {
onWarn(result, stats);
}
console.warn(
`[Warn Mode] ${guardrail.name}: ${result.message} (violation #${record.count})`
);
return {
...result,
tripwireTriggered: false,
metadata: {
...result.metadata,
gradualEnforcement: {
mode: "warn",
wouldBlock: true,
violationCount: record.count
}
}
};
}
case "escalate": {
if (!escalation) {
throw new Error("Escalation config required for escalate mode");
}
const record = incrementViolation(
storageKey,
escalation.windowMs,
result.severity
);
const stats = {
count: record.count,
windowStart: new Date(record.windowStart),
isBlocking: record.count > escalation.blockAfter,
bySeverity: record.bySeverity
};
const shouldCheckSeverity = escalation.severities && escalation.severities.length > 0;
const matchesSeverity = !shouldCheckSeverity || result.severity && escalation.severities.includes(result.severity);
if (!matchesSeverity) {
if (onWarn) {
onWarn(result, stats);
}
return {
...result,
tripwireTriggered: false,
metadata: {
...result.metadata,
gradualEnforcement: {
mode: "escalate",
phase: "warn",
wouldBlock: true,
violationCount: record.count,
severityFiltered: true
}
}
};
}
if (record.count <= escalation.warnCount) {
if (onWarn) {
onWarn(result, stats);
}
console.warn(
`[Escalate: Warning] ${guardrail.name}: ${result.message} (${record.count}/${escalation.warnCount} before blocking)`
);
return {
...result,
tripwireTriggered: false,
metadata: {
...result.metadata,
gradualEnforcement: {
mode: "escalate",
phase: "warn",
wouldBlock: true,
violationCount: record.count,
warnThreshold: escalation.warnCount,
blockThreshold: escalation.blockAfter
}
}
};
}
if (record.count > escalation.blockAfter) {
if (record.count === escalation.blockAfter + 1 && onEscalation) {
onEscalation(stats);
}
console.error(
`[Escalate: Blocking] ${guardrail.name}: ${result.message} (${record.count} violations, blocking enabled)`
);
return {
...result,
tripwireTriggered: true,
metadata: {
...result.metadata,
gradualEnforcement: {
mode: "escalate",
phase: "block",
violationCount: record.count,
blockThreshold: escalation.blockAfter
}
}
};
}
if (onWarn) {
onWarn(result, stats);
}
return {
...result,
tripwireTriggered: false,
metadata: {
...result.metadata,
gradualEnforcement: {
mode: "escalate",
phase: "warn-elevated",
wouldBlock: true,
violationCount: record.count,
remainingBeforeBlock: escalation.blockAfter - record.count
}
}
};
}
case "enforce":
default:
return result;
}
}
};
}
function clearViolationHistory(guardrailName) {
if (guardrailName) {
violationStorage.delete(guardrailName);
} else {
violationStorage.clear();
}
}
function getViolationStats(guardrailName, windowMs = 6e4) {
const record = violationStorage.get(guardrailName);
if (!record) {
return null;
}
const now = Date.now();
const isExpired = now - record.windowStart > windowMs;
if (isExpired) {
return null;
}
return {
count: record.count,
windowStart: new Date(record.windowStart),
isBlocking: false,
// Would need escalation config to determine
bySeverity: record.bySeverity
};
}
function warnOnly(guardrail, options) {
return withGradualEnforcement(guardrail, {
mode: "warn",
onWarn: options?.onWarn
});
}
function lenientEscalation(guardrail, options) {
return withGradualEnforcement(guardrail, {
mode: "escalate",
escalation: {
warnCount: 3,
blockAfter: 5,
windowMs: 6e4
// 1 minute
},
onWarn: options?.onWarn,
onEscalation: options?.onEscalation
});
}
function strictEscalation(guardrail, options) {
return withGradualEnforcement(guardrail, {
mode: "escalate",
escalation: {
warnCount: 1,
blockAfter: 2,
windowMs: 3e5
// 5 minutes
},
onWarn: options?.onWarn,
onEscalation: options?.onEscalation
});
}
function withGracePeriod(guardrail, until, options) {
return withGradualEnforcement(guardrail, {
mode: "enforce",
gracePeriod: {
until,
logLevel: options?.logLevel || "warn",
message: options?.message
}
});
}
// src/guardrails/observability.ts
function createMetricsCollector(options = {}) {
const {
onFlush,
flushIntervalMs = 6e4,
sampling = 1,
maxExecutionTimeSamples = 1e3,
autoStart = true,
logger = console
} = options;
const metricsStore = /* @__PURE__ */ new Map();
let periodStart = /* @__PURE__ */ new Date();
let flushInterval = null;
function recordExecution(guardrailName, result, executionTimeMs) {
if (sampling < 1 && Math.random() > sampling) {
return;
}
let metrics = metricsStore.get(guardrailName);
if (!metrics) {
metrics = {
executionCount: 0,
blockCount: 0,
errorCount: 0,
executionTimes: [],
violationsBySeverity: {},
firstSeen: /* @__PURE__ */ new Date(),
lastSeen: /* @__PURE__ */ new Date()
};
metricsStore.set(guardrailName, metrics);
}
metrics.executionCount++;
metrics.lastSeen = /* @__PURE__ */ new Date();
if (metrics.executionTimes.length < maxExecutionTimeSamples) {
metrics.executionTimes.push(executionTimeMs);
} else {
const idx = Math.floor(Math.random() * metrics.executionCount);
if (idx < maxExecutionTimeSamples) {
metrics.executionTimes[idx] = executionTimeMs;
}
}
if (result.tripwireTriggered) {
metrics.blockCount++;
metrics.lastViolation = /* @__PURE__ */ new Date();
const severity = result.severity || "medium";
metrics.violationsBySeverity[severity] = (metrics.violationsBySeverity[severity] || 0) + 1;
}
if (result.severity === "critical" && result.metadata?.error) {
metrics.errorCount++;
}
}
function percentile(sortedArr, p) {
if (sortedArr.length === 0) return 0;
const idx = Math.ceil(sortedArr.length * p) - 1;
return sortedArr[Math.max(0, Math.min(idx, sortedArr.length - 1))];
}
function computeMetrics() {
const now = /* @__PURE__ */ new Date();
const byGuardrail = /* @__PURE__ */ new Map();
let totalExecutions = 0;
let totalBlocks = 0;
let totalErrors = 0;
let totalExecutionTime = 0;
let totalExecutionCount = 0;
for (const [name, internal] of metricsStore) {
const sortedTimes = [...internal.executionTimes].sort((a, b) => a - b);
const avgTime = sortedTimes.length > 0 ? sortedTimes.reduce((a, b) => a + b, 0) / sortedTimes.length : 0;
const guardrailMetrics = {
guardrailName: name,
executionCount: internal.executionCount,
blockCount: internal.blockCount,
errorCount: internal.errorCount,
avgExecutionMs: avgTime,
p95ExecutionMs: percentile(sortedTimes, 0.95),
p99ExecutionMs: percentile(sortedTimes, 0.99),
minExecutionMs: sortedTimes[0] || 0,
maxExecutionMs: sortedTimes[sortedTimes.length - 1] || 0,
blockRate: internal.executionCount > 0 ? internal.blockCount / internal.executionCount : 0,
lastViolation: internal.lastViolation,
violationsBySeverity: { ...internal.violationsBySeverity },
firstSeen: internal.firstSeen,
lastSeen: internal.lastSeen
};
byGuardrail.set(name, guardrailMetrics);
totalExecutions += internal.executionCount;
totalBlocks += internal.blockCount;
totalErrors += internal.errorCount;
totalExecutionTime += avgTime * internal.executionCount;
totalExecutionCount += internal.executionCount;
}
return {
totalExecutions,
totalBlocks,
totalErrors,
overallBlockRate: totalExecutions > 0 ? totalBlocks / totalExecutions : 0,
avgExecutionMs: totalExecutionCount > 0 ? totalExecutionTime / totalExecutionCount : 0,
byGuardrail,
periodStart,
periodEnd: now
};
}
async function flush() {
const metrics = computeMetrics();
if (onFlush) {
try {
await onFlush(metrics);
} catch (error) {
logger.error("Error in metrics flush callback:", error);
}
}
return metrics;
}
function reset() {
metricsStore.clear();
periodStart = /* @__PURE__ */ new Date();
}
function start() {
if (flushInterval) return;
flushInterval = setInterval(async () => {
await flush();
}, flushIntervalMs);
if (flushInterval.unref) {
flushInterval.unref();
}
}
function stop() {
if (!flushInterval) {
return;
}
clearInterval(flushInterval);
flushInterval = null;
}
function track(guardrail) {
return {
...guardrail,
execute: async (context, ...rest) => {
const startTime = Date.now();
try {
const result = await guardrail.execute(context, ...rest);
const executionTime = Date.now() - startTime;
recordExecution(guardrail.name, result, executionTime);
return result;
} catch (error) {
const executionTime = Date.now() - startTime;
const errorResult = {
tripwireTriggered: true,
message: `Execution error: ${error instanceof Error ? error.message : "Unknown"}`,
severity: "critical",
metadata: { error: String(error) }
};
recordExecution(guardrail.name, errorResult, executionTime);
throw error;
}
}
};
}
function trackAll(guardrails) {
return guardrails.map((g) => track(g));
}
if (autoStart) {
start();
}
return {
/** Track a single guardrail */
track,
/** Track multiple guardrails */
trackAll,
/** Get current metrics without flushing */
getMetrics: computeMetrics,
/** Manually flush metrics */
flush,
/** Reset all metrics */
reset,
/** Start automatic flushing */
start,
/** Stop automatic flushing */
stop,
/** Record an execution manually */
recordExecution
};
}
function logExecutionSummary(summary, options = {}) {
const { logger = console, level = "info", includeDetails = false } = options;
const logFn = level === "warn" ? logger.warn : logger.info;
const { stats, totalExecutionTime, guardrailsExecuted, blockedResults } = summary;
logFn(
`Guardrails executed: ${guardrailsExecuted} | Passed: ${stats.passed} | Blocked: ${stats.blocked} | Time: ${totalExecutionTime}ms | Avg: ${stats.averageExecutionTime.toFixed(1)}ms`
);
if (includeDetails && blockedResults.length > 0) {
logFn(
"Blocked by:",
blockedResults.map((r) => ({
guardrail: r.context?.guardrailName || "unknown",
message: r.message,
severity: r.severity
}))
);
}
}
function createHealthCheck(guardrails, options = {}) {
const {
errorRateThreshold = 0.1,
blockRateThreshold = 0.5,
metricsCollector
} = options;
return () => {
const guardrailStatuses = [];
let overallStatus = "healthy";
if (metricsCollector) {
const metrics = metricsCollector.getMetrics();
for (const guardrail of guardrails) {
const guardrailMetrics = metrics.byGuardrail.get(guardrail.name);
if (!guardrailMetrics || guardrailMetrics.executionCount === 0) {
guardrailStatuses.push({
name: guardrail.name,
status: "healthy",
reason: "No executions yet"
});
continue;
}
const errorRate = guardrailMetrics.errorCount / guardrailMetrics.executionCount;
const blockRate = guardrailMetrics.blockRate;
if (errorRate > errorRateThreshold) {
guardrailStatuses.push({
name: guardrail.name,
status: "unhealthy",
reason: `High error rate: ${(errorRate * 100).toFixed(1)}%`
});
overallStatus = "unhealthy";
} else if (blockRate > blockRateThreshold) {
guardrailStatuses.push({
name: guardrail.name,
status: "degraded",
reason: `High block rate: ${(blockRate * 100).toFixed(1)}%`
});
if (overallStatus === "healthy") {
overallStatus = "degraded";
}
} else {
guardrailStatuses.push({
name: guardrail.name,
status: "healthy"
});
}
}
} else {
for (const guardrail of guardrails) {
guardrailStatuses.push({
name: guardrail.name,
status: guardrail.enabled === false ? "degraded" : "healthy",
reason: guardrail.enabled === false ? "Guardrail disabled" : void 0
});
}
}
return {
status: overallStatus,
guardrails: guardrailStatuses,
timestamp: /* @__PURE__ */ new Date()
};
};
}
// src/guardrails/debug.ts
var traceCounter = 0;
function defaultGenerateTraceId() {
traceCounter++;
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).slice(2, 8);
return `trace-${timestamp}-${random}-${traceCounter}`;
}
function createDebugWrapper(options) {
const {
enabled,
verbose = false,
previewLength = 200,
onTrace,
generateTraceId = defaultGenerateTraceId,
includeInputContext = true,
includeOutputContext = true,
logger = console
} = options;
let currentTrace = null;
let traceStartTime = 0;
function startTrace(type) {
const traceId = generateTraceId();
traceStartTime = Date.now();
currentTrace = {
traceId,
timestamp: /* @__PURE__ */ new Date(),
type,
guardrails: [],
totalMs: 0,
finalDecision: "allowed"
};
if (verbose) {
logger.debug(`[${traceId}] Starting ${type} guardrail trace`);
}
return traceId;
}
function addEntry(entry) {
if (!currentTrace) return;
currentTrace.guardrails?.push(entry);
if (verbose) {
const status = entry.triggered ? `BLOCKED (${entry.severity})` : "PASSED";
logger.debug(
`[${currentTrace.traceId}] ${entry.guardrailName}: ${status} (${entry.durationMs}ms)`
);
}
}
async function completeTrace(inputContext, outputContext) {
if (!currentTrace) return null;
const endTime = Date.now();
currentTrace.totalMs = endTime - traceStartTime;
const blockedEntries = currentTrace.guardrails?.filter((e) => e.triggered) || [];
if (blockedEntries.length > 0) {
currentTrace.finalDecision = "blocked";
currentTrace.blockedBy = blockedEntries.map((e) => e.guardrailName);
}
if (includeInputContext && inputContext) {
currentTrace.inputContext = {
promptLength: inputContext.prompt?.length || 0,
messageCount: inputContext.messages?.length || 0,
hasSystemMessage: !!inputContext.system
};
if (verbose && inputContext.prompt) {
currentTrace.inputContext.promptPreview = inputContext.prompt.length > previewLength ? inputContext.prompt.slice(0, previewLength) + "..." : inputContext.prompt;
}
}
if (includeOutputContext && outputContext?.text) {
currentTrace.outputContext = {
responseLength: outputContext.text.length
};
if (verbose) {
currentTrace.outputContext.responsePreview = outputContext.text.length > previewLength ? outputContext.text.slice(0, previewLength) + "..." : outputContext.text;
}
}
const finalTrace = currentTrace;
if (onTrace) {
try {
await onTrace(finalTrace);
} catch (error) {
logger.warn("Error in trace callback:", error);
}
}
if (verbose) {
const status = finalTrace.finalDecision === "blocked" ? "BLOCKED" : "ALLOWED";
logger.info(
`[${finalTrace.traceId}] Trace complete: ${status} | ${finalTrace.guardrails.length} guardrails | ${finalTrace.totalMs}ms`
);
}
currentTrace = null;
return finalTrace;
}
function wrap(guardrail) {
if (!enabled) {
return guardrail;
}
return {
...guardrail,
execute: async (context, ...rest) => {
const startTime = Date.now();
const relativeStart = traceStartTime ? startTime - traceStartTime : 0;
let result;
let error = null;
try {
result = await guardrail.execute(context, ...rest);
} catch (e) {
error = e instanceof Error ? e : new Error(String(e));
result = {
tripwireTriggered: true,
message: `Execution error: ${error.message}`,
severity: "critical",
metadata: {
error: error.message,
stack: error.stack
}
};
}
const endTime = Date.now();
const relativeEnd = traceStartTime ? endTime - traceStartTime : 0;
const entry = {
guardrailName: guardrail.name,
guardrailVersion: guardrail.version,
startMs: relativeStart,
endMs: relativeEnd,
durationMs: endTime - startTime,
result: error ? "error" : result.tripwireTriggered ? "block" : "pass",
triggered: result.tripwireTriggered,
severity: result.severity,
message: result.message,
decision: result
};
if (result.metadata) {
const metadata = result.metadata;
if (metadata.patterns || metadata.matchedPatterns) {
entry.matchedPatterns = metadata.patterns || metadata.matchedPatterns;
}
if (typeof metadata.confidence === "number") {
entry.confidence = metadata.confidence;
}
entry.debugInfo = metadata;
}
addEntry(entry);
if (error) {
throw error;
}
return result;
}
};
}
function wrapAll(guardrails) {
return guardrails.map((g) => wrap(g));
}
return {
/** Wrap a single guardrail with debugging */
wrap,
/** Wrap multiple guardrails */
wrapAll,
/** Start a new trace (call before executing guardrails) */
startTrace,
/** Complete and emit the current trace */
completeTrace,
/** Get the current trace ID */
getCurrentTraceId: () => currentTrace?.traceId,
/** Check if debugging is enabled */
isEnabled: () => enabled
};
}
function formatTraceForConsole(trace) {
const lines = [];
lines.push(
`
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`
);
lines.push(
`\u2551 GUARDRAIL EXECUTION TRACE \u2551`
);
lines.push(
`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`
);
lines.push(`\u2551 Trace ID: ${trace.traceId.padEnd(50)}\u2551`);
lines.push(`\u2551 Timestamp: ${trace.timestamp.toISOString().padEnd(50)}\u2551`);
lines.push(`\u2551 Type: ${trace.type.padEnd(50)}\u2551`);
lines.push(`\u2551 Duration: ${(trace.totalMs + "ms").padEnd(50)}\u2551`);
lines.push(`\u2551 Decision: ${trace.finalDecision.toUpperCase().padEnd(50)}\u2551`);
lines.push(
`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`
);
lines.push(
`\u2551 GUARDRAILS EXECUTED \u2551`
);
lines.push(
`\u255F\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2562`
);
for (const entry of trace.guardrails) {
const status = entry.triggered ? `BLOCK [${entry.severity || "medium"}]` : "PASS";
const statusStr = status.padEnd(15);
const name = entry.guardrailName.slice(0, 25).padEnd(25);
const time = (entry.durationMs + "ms").padEnd(8);
lines.push(`\u2551 ${statusStr} ${name} ${time} \u2551`);
if (entry.triggered && entry.message) {
const msg = entry.message.slice(0, 55).padEnd(55);
lines.push(`\u2551 \u2514\u2500 ${msg} \u2551`);
}
}
if (trace.blockedBy && trace.blockedBy.length > 0) {
lines.push(
`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`
);
lines.push(
`\u2551 BLOCKED BY: ${trace.blockedBy.join(", ").slice(0, 48).padEnd(48)} \u2551`
);
}
lines.push(
`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
`
);
return lines.join("\n");
}
function formatTraceAsJSON(trace) {
return JSON.stringify(trace, null, 2);
}
function formatTraceSummary(trace) {
const blocked = trace.guardrails.filter((g) => g.triggered);
const passed = trace.guardrails.filter((g) => !g.triggered);
return `[${trace.traceId}] ${trace.type.toUpperCase()} | ${trace.finalDecision.toUpperCase()} | ${trace.guardrails.length} guardrails (${passed.length} passed, ${blocked.length} blocked) | ${trace.totalMs}ms` + (trace.blockedBy ? ` | blocked by: ${trace.blockedBy.join(", ")}` : "");
}
function createConsoleDebugger(options = {}) {
const { verbose = false, format = "summary" } = options;
return {
enabled: true,
verbose,
onTrace: (trace) => {
switch (format) {
case "console":
console.log(formatTraceForConsole(trace));
break;
case "json":
console.log(formatTraceAsJSON(trace));
break;
case "summary":
default:
console.log(formatTraceSummary(trace));
}
}
};
}
function envDebugMode() {
const debugEnabled = process.env.GUARDRAILS_DEBUG === "true" || process.env.GUARDRAILS_DEBUG === "1";
const verbose = process.env.GUARDRAILS_DEBUG_VERBOSE === "true";
return {
enabled: debugEnabled,
verbose,
onTrace: debugEnabled ? (trace) => {
console.log(formatTraceSummary(trace));
if (verbose && trace.finalDecision === "blocked") {
console.log(formatTraceForConsole(trace));
}
} : void 0
};
}
// src/guardrails/streaming.ts
function createGuardrailStreamTransform(guardrails, options = {}) {
const {
stopOnSeverity = "critical",
stopCondition,
onViolation,
checkInterval = 1,
timeout = 5e3,
parallel: parallel2 = true
} = options;
const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityOrder[stopOnSeverity];
return ({ stopStream }) => {
let accumulatedText = "";
let chunkCount = 0;
let stopped = false;
return new TransformStream({
async transform(chunk, controller) {
if (stopped) {
return;
}
if (chunk.type !== "text-delta") {
controller.enqueue(chunk);
return;
}
const text = chunk.text || chunk.delta || "";
accumulatedText += text;
chunkCount++;
if (chunkCount % checkInterval !== 0) {
controller.enqueue(chunk);
return;
}
try {
const mockResult = {
text: accumulatedText,
content: [],
finishReason: "stop",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
const context = {
input: {
prompt: "",
messages: [],
system: ""
},
result: mockResult
};
const results = await executeOutputGuardrails(guardrails, context, {
parallel: parallel2,
timeout,
continueOnFailure: true,
logLevel: "none"
});
const summary = {
allResults: results,
blockedResults: results.filter((r) => r.tripwireTriggered),
totalExecutionTime: 0,
guardrailsExecuted: results.length,
stats: {
passed: results.filter((r) => !r.tripwireTriggered).length,
blocked: results.filter((r) => r.tripwireTriggered).length,
failed: 0,
averageExecutionTime: 0
}
};
const shouldStop = stopCondition ? stopCondition(summary) : summary.blockedResults.some((result) => {
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (shouldStop) {
stopped = true;
onViolation?.(summary);
controller.enqueue({
type: "error",
error: `Guardrail violation: ${summary.blockedResults.map((r) => r.message).join(", ")}`
});
stopStream();
return;
}
controller.enqueue(chunk);
} catch (error) {
console.error("Guardrail stream transform error:", error);
controller.enqueue(chunk);
}
},
flush() {
if (!stopped) {
}
}
});
};
}
function createGuardrailStreamTransformBuffered(guardrails, options = {}) {
const {
stopOnSeverity = "critical",
stopCondition,
onViolation,
timeout = 5e3,
parallel: parallel2 = true
} = options;
const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityOrder[stopOnSeverity];
return ({ stopStream }) => {
let accumulatedText = "";
const chunks = [];
return new TransformStream({
transform(chunk) {
if (chunk.type === "text-delta") {
const text = chunk.text || chunk.delta || "";
accumulatedText += text;
}
chunks.push(chunk);
},
async flush(controller) {
try {
const mockResult = {
text: accumulatedText,
content: [],
finishReason: "stop",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
const context = {
input: {
prompt: "",
messages: [],
system: ""
},
result: mockResult
};
const results = await executeOutputGuardrails(guardrails, context, {
parallel: parallel2,
timeout,
continueOnFailure: true,
logLevel: "none"
});
const summary = {
allResults: results,
blockedResults: results.filter((r) => r.tripwireTriggered),
totalExecutionTime: 0,
guardrailsExecuted: results.length,
stats: {
passed: results.filter((r) => !r.tripwireTriggered).length,
blocked: results.filter((r) => r.tripwireTriggered).length,
failed: 0,
averageExecutionTime: 0
}
};
const shouldBlock = stopCondition ? stopCondition(summary) : summary.blockedResults.some((result) => {
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (shouldBlock) {
onViolation?.(summary);
controller.enqueue({
type: "error",
error: `Guardrail violation: ${summary.blockedResults.map((r) => r.message).join(", ")}`
});
stopStream();
return;
}
for (const chunk of chunks) {
controller.enqueue(chunk);
}
} catch (error) {
console.error("Guardrail buffered transform error:", error);
for (const chunk of chunks) {
controller.enqueue(chunk);
}
}
}
});
};
}
// src/guardrails/token-control.ts
function estimateTokenCount(text) {
if (!text) return 0;
const charCount = text.length;
const wordCount = text.split(/\s+/).filter((w) => w.length > 0).length;
return Math.ceil(charCount / 4 + wordCount / 2);
}
function createTokenBudgetTransform(options) {
const {
maxTokens,
tokenizer = estimateTokenCount,
onBudgetExceeded
} = options;
return ({ stopStream }) => {
let accumulatedText = "";
let tokenCount = 0;
let stopped = false;
return new TransformStream({
transform(chunk, controller) {
if (stopped) {
return;
}
if (chunk.type === "text-delta") {
const text = chunk.text || chunk.delta || "";
accumulatedText += text;
tokenCount = tokenizer(accumulatedText);
if (tokenCount > maxTokens) {
stopped = true;
onBudgetExceeded?.({ consumed: tokenCount, budget: maxTokens });
controller.enqueue({
type: "error",
error: `Token budget exceeded: ${tokenCount} > ${maxTokens}`
});
stopStream();
return;
}
}
controller.enqueue(chunk);
}
});
};
}
function createTokenAwareGuardrailTransform(guardrails, options = {}) {
const {
checkEveryTokens = 10,
maxTokens,
stopOnSeverity = "critical",
stopCondition,
onViolation,
tokenizer = estimateTokenCount,
timeout = 5e3,
parallel: parallel2 = true
} = options;
const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityOrder[stopOnSeverity];
return ({ stopStream }) => {
let accumulatedText = "";
let tokenCount = 0;
let lastCheckTokens = 0;
let stopped = false;
return new TransformStream({
async transform(chunk, controller) {
if (stopped) {
return;
}
if (chunk.type !== "text-delta") {
controller.enqueue(chunk);
return;
}
const text = chunk.text || chunk.delta || "";
accumulatedText += text;
tokenCount = tokenizer(accumulatedText);
if (typeof maxTokens === "number" && tokenCount > maxTokens) {
stopped = true;
controller.enqueue({
type: "error",
error: `Token limit exceeded: ${tokenCount} > ${maxTokens}`
});
stopStream();
return;
}
const tokensSinceLastCheck = tokenCount - lastCheckTokens;
if (tokensSinceLastCheck < checkEveryTokens) {
controller.enqueue(chunk);
return;
}
lastCheckTokens = tokenCount;
try {
const mockResult = {
text: accumulatedText,
content: [],
finishReason: "stop",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
const context = {
input: {
prompt: "",
messages: [],
system: ""
},
result: mockResult
};
const results = await executeOutputGuardrails(guardrails, context, {
parallel: parallel2,
timeout,
continueOnFailure: true,
logLevel: "none"
});
const summary = {
allResults: results,
blockedResults: results.filter((r) => r.tripwireTriggered),
totalExecutionTime: 0,
guardrailsExecuted: results.length,
stats: {
passed: results.filter((r) => !r.tripwireTriggered).length,
blocked: results.filter((r) => r.tripwireTriggered).length,
failed: 0,
averageExecutionTime: 0
}
};
const shouldStop = stopCondition ? stopCondition(summary) : summary.blockedResults.some((result) => {
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (shouldStop) {
stopped = true;
onViolation?.(summary);
controller.enqueue({
type: "error",
error: `Guardrail violation: ${summary.blockedResults.map((r) => r.message).join(", ")}`
});
stopStream();
return;
}
controller.enqueue(chunk);
} catch (error) {
console.error("Token-aware guardrail error:", error);
controller.enqueue(chunk);
}
}
});
};
}
// src/guardrails/stream-transform.ts
function createGuardrailTransform(guardrails, options = {}) {
const {
onViolation = "stop",
redactPatterns = [],
redactionText = "[REDACTED]",
replacementText = "[Content filtered]",
minCharsBeforeCheck = 0,
checkEveryNChunks = 1,
requestContext,
onStreamStopped,
onViolationDetected
} = options;
return ({ stopStream }) => {
let accumulatedText = "";
let chunkCount = 0;
const violations = [];
let stopped = false;
const context = {
accumulatedText: "",
chunkCount: 0,
violations: [],
requestContext
};
return new TransformStream({
async transform(chunk, controller) {
if (stopped) {
return;
}
const chunkText = chunk.delta || chunk.textDelta || chunk.text || "";
if (chunk.type === "text-delta" || chunk.type === "text") {
accumulatedText += chunkText;
chunkCount++;
context.accumulatedText = accumulatedText;
context.chunkCount = chunkCount;
context.violations = violations;
const shouldCheck = accumulatedText.length >= minCharsBeforeCheck && chunkCount % checkEveryNChunks === 0;
if (shouldCheck && guardrails.length > 0) {
for (const guardrail of guardrails) {
if (guardrail.enabled === false) continue;
try {
const result = await guardrail.execute(
{
input: {
prompt: "",
messages: [],
system: "",
requestContext
},
result: { text: accumulatedText }
},
accumulatedText
);
if (result.tripwireTriggered) {
violations.push(result);
context.violations = violations;
if (onViolationDetected) {
onViolationDetected(result, chunk);
}
const handlerResult = await handleViolation(
chunk,
result,
context,
{
onViolation,
redactPatterns,
redactionText,
replacementText
}
);
switch (handlerResult.action) {
case "stop":
stopped = true;
stopStream();
if (onStreamStopped) {
onStreamStopped(violations, accumulatedText);
}
controller.enqueue({
...chunk,
type: "text-delta",
delta: `
[Stream stopped: ${handlerResult.reason || result.message}]`
});
return;
case "drop":
return;
case "replace":
controller.enqueue({
...chunk,
delta: handlerResult.replacement || replacementText,
textDelta: handlerResult.replacement || replacementText
});
return;
case "pass":
default:
break;
}
}
} catch (error) {
console.error(`Guardrail "${guardrail.name}" error:`, error);
}
}
}
if (redactPatterns.length > 0 && chunkText) {
const redactedText = applyRedaction(
chunkText,
redactPatterns,
redactionText
);
if (redactedText !== chunkText) {
controller.enqueue({
...chunk,
delta: redactedText,
textDelta: redactedText
});
return;
}
}
}
controller.enqueue(chunk);
},
flush(controller) {
if (stopped && violations.length > 0) {
}
}
});
};
}
async function handleViolation(chunk, violation, context, options) {
const { onViolation, redactPatterns, redactionText, replacementText } = options;
if (typeof onViolation === "function") {
return onViolation(chunk, violation, context);
}
switch (onViolation) {
case "stop":
return { action: "stop", reason: violation.message };
case "drop":
return { action: "drop", reason: violation.message };
case "redact":
const chunkText = chunk.delta || chunk.textDelta || "";
const redacted = applyRedaction(chunkText, redactPatterns, redactionText);
if (redacted !== chunkText) {
return {
action: "replace",
replacement: redacted,
reason: "Content redacted"
};
}
return { action: "pass" };
case "replace":
return {
action: "replace",
replacement: replacementText,
reason: violation.message
};
default:
return { action: "pass" };
}
}
function applyRedaction(text, patterns, redactionText) {
let result = text;
for (const pattern of patterns) {
if (typeof pattern === "string") {
result = result.split(pattern).join(redactionText);
} else {
result = result.replace(pattern, () => redactionText);
}
}
return result;
}
var PII_PATTERNS = {
/** US Social Security Number */
SSN: /\b\d{3}-\d{2}-\d{4}\b/g,
/** Email addresses */
EMAIL: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
/** Phone numbers (various formats) */
PHONE: /\b(\+\d{1,2}\s?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}\b/g,
/** Credit card numbers */
CREDIT_CARD: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
/** IP addresses */
IP_ADDRESS: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
/** API keys (generic pattern) */
API_KEY: /\b(sk|pk|api)[_-]?[a-zA-Z0-9]{20,}\b/gi
};
function createPIIRedactionTransform(options = {}) {
const patterns = options.patterns || [
PII_PATTERNS.SSN,
PII_PATTERNS.EMAIL,
PII_PATTERNS.PHONE,
PII_PATTERNS.CREDIT_CARD
];
return createGuardrailTransform([], {
onViolation: "redact",
// No guardrails, just pattern redaction
redactPatterns: patterns,
redactionText: options.redactionText || "[REDACTED]",
requestContext: options.requestContext
});
}
function createContentFilterTransform(options) {
const {
blockedKeywords,
caseSensitive = false,
onBlocked,
requestContext
} = options;
return ({ stopStream }) => {
let accumulatedText = "";
let stopped = false;
return new TransformStream({
transform(chunk, controller) {
if (stopped) return;
const chunkText = chunk.delta || chunk.textDelta || chunk.text || "";
if (chunk.type === "text-delta" || chunk.type === "text") {
accumulatedText += chunkText;
const textToCheck = caseSensitive ? accumulatedText : accumulatedText.toLowerCase();
for (const keyword of blockedKeywords) {
const keywordToCheck = caseSensitive ? keyword : keyword.toLowerCase();
if (textToCheck.includes(keywordToCheck)) {
stopped = true;
stopStream();
if (onBlocked) {
onBlocked(keyword, accumulatedText);
}
controller.enqueue({
...chunk,
type: "text-delta",
delta: `
[Content blocked: prohibited content detected]`
});
return;
}
}
}
controller.enqueue(chunk);
}
});
};
}
// src/guardrails/prepare-step.ts
function createGuardrailPrepareStep(violations, options = {}) {
const {
lookback = 2,
temperatureReduction = 0.3,
stopOnCritical = false,
warningMessage = "Previous responses violated guidelines. Please be more careful and follow all safety guidelines."
} = options;
return ({ stepNumber }) => {
const recentViolations = violations.filter((v) => {
if ("step" in v) {
return v.step >= stepNumber - lookback && v.step < stepNumber;
}
return false;
});
if (recentViolations.length === 0) {
return;
}
const hasCritical = recentViolations.some(
(v) => v.summary.blockedResults.some((r) => r.severity === "critical")
);
const result = {
temperature: temperatureReduction,
system: warningMessage
};
if (hasCritical && stopOnCritical) {
result.stopWhen = () => true;
}
return result;
};
}
function createAdaptivePrepareStep(options) {
const {
violations,
strategy,
onViolationDetected,
escalateAfter = 5,
lookback = 3
} = options;
return ({ stepNumber }) => {
const recentViolations = violations.filter((v) => {
if ("step" in v) {
return v.step >= stepNumber - lookback && v.step < stepNumber;
}
return false;
});
if (recentViolations.length === 0) {
return;
}
onViolationDetected?.(recentViolations);
if (strategy) {
return strategy(recentViolations);
}
const violationCount = recentViolations.length;
const temperatureReduction = Math.max(0.1, 0.7 - violationCount * 0.15);
const result = {
temperature: temperatureReduction,
system: `Warning: ${violationCount} guardrail violation(s) detected in recent steps. Please ensure responses comply with all safety guidelines.`
};
if (violationCount >= escalateAfter) {
result.stopWhen = () => true;
result.system += " Execution will stop due to repeated violations.";
}
return result;
};
}
// src/guardrails/tool-abortion.ts
var ToolAbortionController = class {
controller;
minSeverity;
timeout;
constructor(options = {}) {
this.controller = new AbortController();
this.minSeverity = options.minSeverity ?? "critical";
this.timeout = options.timeout ?? 3e3;
}
get signal() {
return this.controller.signal;
}
/**
* Check guardrails and abort if violations detected
*/
async checkAndAbort(guardrails, context) {
const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityOrder[this.minSeverity];
const results = await executeOutputGuardrails(guardrails, context, {
parallel: true,
timeout: this.timeout,
continueOnFailure: true,
logLevel: "none"
});
const shouldAbort = results.some((result) => {
if (!result.tripwireTriggered) return false;
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (shouldAbort) {
this.controller.abort("Guardrail violation detected");
return true;
}
return false;
}
/**
* Manually abort
*/
abort(reason) {
this.controller.abort(reason);
}
};
function createToolAbortionController(options) {
return new ToolAbortionController(options);
}
function wrapToolWithAbortion(tool, guardrails, options = {}) {
const {
checkBefore = false,
monitorDuring = false,
monitorInterval = 100,
checkInputDelta = false,
abortOnSeverity = "critical",
timeout = 3e3
} = options;
const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityOrder[abortOnSeverity];
const originalExecute = tool.execute;
const originalOnInputDelta = tool.onInputDelta;
async function checkGuardrails(input) {
const mockResult = {
text: JSON.stringify(input),
content: [],
finishReason: "stop",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
const context = {
input: {
prompt: "",
messages: [],
system: ""
},
result: mockResult
};
const results = await executeOutputGuardrails(guardrails, context, {
parallel: true,
timeout,
continueOnFailure: true,
logLevel: "none"
});
const shouldAbort = results.some((result) => {
if (!result.tripwireTriggered) return false;
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (shouldAbort) {
const messages = results.filter((r) => r.tripwireTriggered).map((r) => r.message).join(", ");
throw new Error(`Tool execution aborted: ${messages}`);
}
}
const wrappedExecute = async (input, executeOptions) => {
if (checkBefore) {
await checkGuardrails(input);
}
const internalController = new AbortController();
let monitorInterval_;
let monitorError;
if (monitorDuring) {
monitorInterval_ = setInterval(async () => {
try {
await checkGuardrails(input);
} catch (error) {
monitorError = error;
internalController.abort();
clearInterval(monitorInterval_);
}
}, monitorInterval);
}
try {
const combinedSignal = monitorDuring && executeOptions?.abortSignal ? AbortSignal.any([
executeOptions.abortSignal,
internalController.signal
]) : executeOptions?.abortSignal ?? (monitorDuring ? internalController.signal : void 0);
const callOptions = combinedSignal ? { ...executeOptions, abortSignal: combinedSignal } : executeOptions;
const result = await originalExecute.call(tool, input, callOptions);
if (monitorInterval_) {
clearInterval(monitorInterval_);
}
if (monitorError) {
throw monitorError;
}
return result;
} catch (error) {
if (monitorInterval_) {
clearInterval(monitorInterval_);
}
throw monitorError ?? error;
}
};
const wrappedOnInputDelta = checkInputDelta && originalOnInputDelta ? async (deltaOptions) => {
const mockResult = {
text: deltaOptions.inputTextDelta,
content: [],
finishReason: "stop",
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
};
const context = {
input: {
prompt: "",
messages: [],
system: ""
},
result: mockResult
};
const results = await executeOutputGuardrails(guardrails, context, {
parallel: true,
timeout,
continueOnFailure: true,
logLevel: "none"
});
const shouldAbort = results.some((result) => {
if (!result.tripwireTriggered) return false;
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (shouldAbort) {
const violation = results.find((r) => r.tripwireTriggered);
throw new Error(
`Tool input delta blocked by guardrail: ${violation?.message || "Guardrail violation"}`
);
}
await originalOnInputDelta?.call(tool, deltaOptions);
} : originalOnInputDelta;
return {
...tool,
execute: wrappedExecute,
...wrappedOnInputDelta && { onInputDelta: wrappedOnInputDelta }
};
}
// src/guardrails/abort-controller.ts
var GuardrailViolationAbort = class extends Error {
summary;
constructor(summary) {
const messages = summary.blockedResults.map((r) => r.message).filter(Boolean).join(", ");
super(`Guardrail violation: ${messages}`);
this.name = "GuardrailViolationAbort";
this.summary = summary;
}
};
function createGuardrailAbortController() {
const controller = new AbortController();
return {
/**
* The AbortSignal that can be passed to AI SDK functions
*/
signal: controller.signal,
/**
* Creates a callback that aborts on violations of specified severity or higher
*
* @param minSeverity - Minimum severity to trigger abort (default: 'critical')
* @returns Callback function for use with onInputBlocked/onOutputBlocked
*
* @example
* ```typescript
* const { signal, abortOnViolation } = createGuardrailAbortController();
*
* withGuardrails({ model,
* outputGuardrails: [toxicityFilter()],
* onOutputBlocked: abortOnViolation('high'), // Abort on high or critical
* });
* ```
*/
abortOnViolation: (minSeverity = "critical") => {
const severityOrder = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityOrder[minSeverity];
return (summary) => {
const hasViolation = summary.blockedResults.some((result) => {
const resultSeverity = result.severity ?? "medium";
return severityOrder[resultSeverity] >= minLevel;
});
if (hasViolation) {
controller.abort(new GuardrailViolationAbort(summary));
}
};
},
/**
* Creates a callback that aborts based on custom condition
*
* @param condition - Function that returns true to trigger abort
* @returns Callback function for use with onInputBlocked/onOutputBlocked
*
* @example
* ```typescript
* const { signal, abortOnCondition } = createGuardrailAbortController();
*
* withGuardrails({ model,
* outputGuardrails: [qualityCheck()],
* onOutputBlocked: abortOnCondition(
* (summary) => summary.blockedResults.length > 2
* ),
* });
* ```
*/
abortOnCondition: (condition) => {
return (summary) => {
if (condition(summary)) {
controller.abort(new GuardrailViolationAbort(summary));
}
};
},
/**
* Manually abort with custom reason
*
* @param reason - Custom abort reason
*
* @example
* ```typescript
* const { abort } = createGuardrailAbortController();
* abort('User requested cancellation');
* ```
*/
abort: (reason) => {
controller.abort(reason);
}
};
}
// src/guardrails/finish-reason.ts
function getGuardrailFinishReason(summary, options) {
const { blocked = "content_filter", success = "stop" } = options ?? {};
if (summary.blockedResults.length > 0) {
return blocked;
}
return success;
}
function createGuardrailProviderMetadata(summary, options) {
const { includeMetadata = false, includeStats = true } = options ?? {};
const violations = summary.blockedResults.map((result) => {
const violation = {
message: result.message,
severity: result.severity,
guardrailName: result.context?.guardrailName
};
if (includeMetadata && result.metadata) {
violation.metadata = result.metadata;
}
return violation;
});
return {
guardrails: {
blocked: summary.blockedResults.length > 0,
violations,
executionTime: summary.totalExecutionTime,
guardrailsExecuted: summary.guardrailsExecuted,
...includeStats && { stats: summary.stats }
}
};
}
function createFinishReasonEnhancement(summary, result, options) {
if (summary.blockedResults.length === 0) {
return result;
}
const finishReason = getGuardrailFinishReason(summary, options);
const guardrailMetadata = createGuardrailProviderMetadata(summary, options);
return {
...result,
finishReason,
providerMetadata: result.providerMetadata ? { ...result.providerMetadata, ...guardrailMetadata } : guardrailMetadata
};
}
// src/backoff.ts
function exponentialBackoff(options = {}) {
const { base = 1e3, max = 3e4, jitter = 0, multiplier = 2 } = options;
return (attempt) => {
const exponentialDelay = base * Math.pow(multiplier, attempt - 1);
const cappedDelay = Math.min(exponentialDelay, max);
if (jitter > 0) {
const jitterAmount = cappedDelay * jitter * Math.random();
return Math.round(
cappedDelay + jitterAmount - cappedDelay * jitter / 2
);
}
return cappedDelay;
};
}
function linearBackoff(options = {}) {
const { base = 1e3, max = 3e4, jitter = 0 } = options;
return (attempt) => {
const linearDelay = base * attempt;
const cappedDelay = Math.min(linearDelay, max);
if (jitter > 0) {
const jitterAmount = cappedDelay * jitter * Math.random();
return Math.round(
cappedDelay + jitterAmount - cappedDelay * jitter / 2
);
}
return cappedDelay;
};
}
function fixedBackoff(options = {}) {
const { base = 1e3, jitter = 0 } = options;
return (_attempt) => {
if (jitter > 0) {
const jitterAmount = base * jitter * Math.random();
return Math.round(base + jitterAmount - base * jitter / 2);
}
return base;
};
}
function noBackoff() {
return (_attempt) => 0;
}
function compositeBackoff(strategies) {
return (attempt) => {
for (const strategy of strategies) {
if (attempt <= strategy.maxAttempts) {
return strategy.backoff(attempt);
}
}
const lastStrategy = strategies.at(-1);
return lastStrategy ? lastStrategy.backoff(attempt) : 0;
};
}
var jitteredExponentialBackoff = (options = {}) => exponentialBackoff({ ...options, jitter: 0.1 });
var presets = {
/** Fast retry: 500ms, 1s, 2s, 4s (max 4s) */
fast: () => exponentialBackoff({ base: 500, max: 4e3 }),
/** Standard retry: 1s, 2s, 4s, 8s, 16s (max 16s) */
standard: () => exponentialBackoff({ base: 1e3, max: 16e3 }),
/** Slow retry: 2s, 4s, 8s, 16s, 32s (max 32s) */
slow: () => exponentialBackoff({ base: 2e3, max: 32e3 }),
/** Network resilient: jittered exponential with longer delays */
networkResilient: () => jitteredExponentialBackoff({ base: 1e3, max: 3e4 }),
/** Aggressive: very fast with short max delay for quick failures */
aggressive: () => exponentialBackoff({ base: 200, max: 2e3 })
};
export {
DEFAULT_DETECT_NORMALIZATION,
GuardrailViolationAbort,
PII_PATTERNS,
ToolParameterValidationError,
after,
presets as backoffPresets,
clearViolationHistory,
compositeBackoff,
createAdaptivePrepareStep,
createConsoleDebugger,
createContentFilterTransform,
createDebugWrapper,
createDefaultBuildRetryParams,
createFinishReasonEnhancement,
createGuardrailAbortController,
createGuardrailPrepareStep,
createGuardrailProviderMetadata,
createGuardrailStreamTransform,
createGuardrailStreamTransformBuffered,
createGuardrailTransform,
createHealthCheck,
createMetricsCollector,
createPIIRedactionTransform,
createPipeline,
createTokenAwareGuardrailTransform,
createTokenBudgetTransform,
createToolAbortionController,
detectSystemPromptLeak,
enhancedPromptInjectionDetector,
envDebugMode,
estimateTokenCount,
exponentialBackoff,
fixedBackoff,
formatTraceAsJSON,
formatTraceForConsole,
formatTraceSummary,
getGuardrailFinishReason,
getViolationStats,
guardrailMiddleware,
incrementalPromptInjectionDetector,
inputGuardrailsMiddleware,
inputPipeline,
intentBasedInjectionDetector,
jitteredExponentialBackoff,
lenientEscalation,
linearBackoff,
logExecutionSummary,
noBackoff,
noopGuardrailMiddleware,
normalizeForDetection,
not,
outputGuardrailsMiddleware,
outputPipeline,
parallel,
resolveDetectNormalization,
resolveRetryConfig,
strictEscalation,
toolCallInjectionDetector,
warnOnly,
when,
withFallback,
withGracePeriod,
withGradualEnforcement,
withRetry,
withToolParameterGuardrails,
wrapToolWithAbortion
};