ai-sdk-guardrails 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AI SDK Guardrails
2
2
 
3
- **Safety and quality controls for Vercel AI SDK**
3
+ ## Safety and quality controls for Vercel AI SDK
4
4
 
5
5
  Add guardrails to your AI applications in one line of code. Block PII, prevent prompt injection, enforce output quality - while keeping your existing telemetry and observability stack intact.
6
6
 
@@ -35,6 +35,25 @@ await generateText({ model: safeModel, prompt: '...' });
35
35
  npm install ai-sdk-guardrails
36
36
  ```
37
37
 
38
+ ## 🧙‍♂️ No-Code Wizard (New!)
39
+
40
+ **Don't want to write code?** Use our visual wizard to configure guardrails:
41
+
42
+ 1. **Open the wizard**: [wizard-prototype/index.html](./wizard-prototype/index.html)
43
+ 2. **Choose your use case**: Content moderation, data protection, quality assurance, or security
44
+ 3. **Select guardrails**: Pick from 40+ built-in guardrails
45
+ 4. **Configure settings**: Adjust thresholds and parameters with sliders and toggles
46
+ 5. **Copy generated code**: Get production-ready TypeScript code instantly
47
+
48
+ **Perfect for:**
49
+
50
+ - 🎯 **Non-technical users** who need AI safety
51
+ - 🚀 **Quick prototyping** of guardrail configurations
52
+ - 📚 **Learning** how to use the library
53
+ - 👥 **Team onboarding** and training
54
+
55
+ The wizard generates code that works out of the box - just copy, paste, and run!
56
+
38
57
  ## Why Guardrails Matter
39
58
 
40
59
  Real problems that guardrails solve:
@@ -367,6 +386,191 @@ const agent = withAgentGuardrails(
367
386
  const result = await agent.generate({ prompt: '...' });
368
387
  ```
369
388
 
389
+ ## Advanced Stopping Mechanisms
390
+
391
+ Control exactly **when and how** guardrails stop execution with powerful, composable stopping mechanisms:
392
+
393
+ ### 1. AbortSignal-Based Stopping
394
+
395
+ Clean, standard API for canceling AI operations:
396
+
397
+ ```ts
398
+ import { createGuardrailAbortController } from 'ai-sdk-guardrails';
399
+
400
+ const { signal, abortOnViolation } = createGuardrailAbortController();
401
+
402
+ const model = withGuardrails(openai('gpt-4o'), {
403
+ outputGuardrails: [toxicityFilter()],
404
+ onOutputBlocked: abortOnViolation('critical'), // Abort on critical violations
405
+ });
406
+
407
+ // Signal will be aborted if critical violation detected
408
+ await streamText({ model, prompt: '...', abortSignal: signal });
409
+ ```
410
+
411
+ **Features:**
412
+
413
+ - Standard AbortController API
414
+ - Severity-based abortion (`'low' | 'medium' | 'high' | 'critical'`)
415
+ - Custom abort conditions
416
+ - Manual abortion support
417
+
418
+ ### 2. Stream Transform with Source-Level Stopping
419
+
420
+ Stop streaming at the **source** (most efficient):
421
+
422
+ ```ts
423
+ import { createGuardrailStreamTransform } from 'ai-sdk-guardrails';
424
+
425
+ const result = streamText({
426
+ model,
427
+ prompt: 'Tell me a story',
428
+ experimental_transform: createGuardrailStreamTransform(
429
+ [toxicityFilter(), piiDetector()],
430
+ {
431
+ stopOnSeverity: 'high', // Stop on high/critical
432
+ checkInterval: 1, // Check every chunk
433
+ onViolation: (summary) => {
434
+ // Violation callback
435
+ console.log('Stopped:', summary);
436
+ },
437
+ },
438
+ ),
439
+ });
440
+ ```
441
+
442
+ **Modes:**
443
+
444
+ - `createGuardrailStreamTransform` - Progressive checking (each chunk)
445
+ - `createGuardrailStreamTransformBuffered` - Buffered checking (on flush)
446
+
447
+ ### 3. Token-Level Control
448
+
449
+ Reduce overhead with smart token-based checking:
450
+
451
+ ```ts
452
+ import {
453
+ createTokenBudgetTransform,
454
+ createTokenAwareGuardrailTransform,
455
+ } from 'ai-sdk-guardrails';
456
+
457
+ experimental_transform: [
458
+ // Hard token limit
459
+ createTokenBudgetTransform({
460
+ maxTokens: 1000,
461
+ onBudgetExceeded: (info) => console.log(info),
462
+ }),
463
+
464
+ // Check guardrails every N tokens (not every chunk!)
465
+ createTokenAwareGuardrailTransform([toxicityFilter()], {
466
+ checkEveryTokens: 50, // Check every 50 tokens
467
+ maxTokens: 1000, // Combined with budget
468
+ stopOnSeverity: 'high',
469
+ }),
470
+ ];
471
+ ```
472
+
473
+ **Benefits:**
474
+
475
+ - Reduce guardrail overhead by 80%+
476
+ - Cost control with token budgets
477
+ - Custom tokenizer support
478
+
479
+ ### 4. Adaptive Multi-Step Execution
480
+
481
+ Self-correcting behavior across multi-step agent execution:
482
+
483
+ ```ts
484
+ import {
485
+ createAdaptivePrepareStep,
486
+ type GuardrailViolation,
487
+ } from 'ai-sdk-guardrails';
488
+
489
+ const violations: GuardrailViolation[] = [];
490
+
491
+ const agent = new Agent({
492
+ model,
493
+ tools: { search: searchTool },
494
+ prepareStep: createAdaptivePrepareStep({
495
+ violations,
496
+ escalateAfter: 3, // Stop after 3 violations
497
+ strategy: (violations) => ({
498
+ temperature: Math.max(0.1, 0.7 - violations.length * 0.1),
499
+ system: `${violations.length} violations detected. Be careful.`,
500
+ }),
501
+ }),
502
+ });
503
+
504
+ // Track violations
505
+ withAgentGuardrails(agent, {
506
+ outputGuardrails: [toxicityFilter()],
507
+ onOutputBlocked: (summary, context, step) => {
508
+ violations.push({ step, summary });
509
+ },
510
+ });
511
+ ```
512
+
513
+ **Features:**
514
+
515
+ - Progressive temperature reduction
516
+ - Custom adaptive strategies
517
+ - Escalation to auto-stop
518
+ - Lookback window configuration
519
+
520
+ ### 5. Tool Execution Abortion
521
+
522
+ Prevent dangerous tool execution before or during execution:
523
+
524
+ ```ts
525
+ import { wrapToolWithAbortion } from 'ai-sdk-guardrails';
526
+
527
+ const safeTool = wrapToolWithAbortion(
528
+ dangerousApiTool,
529
+ [urlValidator, paramValidator],
530
+ {
531
+ checkBefore: true, // Validate before execution
532
+ monitorDuring: true, // Monitor during execution
533
+ monitorInterval: 1000, // Check every second
534
+ checkInputDelta: true, // Monitor streaming inputs
535
+ abortOnSeverity: 'critical',
536
+ },
537
+ );
538
+ ```
539
+
540
+ **Protection:**
541
+
542
+ - Pre-execution validation
543
+ - Real-time monitoring
544
+ - Streaming input checking
545
+ - Manual abortion control
546
+
547
+ ### 6. Finish Reason & Metadata
548
+
549
+ Better observability with proper finish reasons and metadata:
550
+
551
+ ```ts
552
+ import { createFinishReasonEnhancement } from 'ai-sdk-guardrails';
553
+
554
+ // Automatically set in middleware, or manually:
555
+ const enhanced = createFinishReasonEnhancement(summary, result);
556
+
557
+ console.log(enhanced.finishReason); // 'content_filter' for blocks
558
+ console.log(enhanced.providerMetadata.guardrails);
559
+ // {
560
+ // blocked: true,
561
+ // violations: [{ message: '...', severity: 'high', ... }],
562
+ // executionTime: 50,
563
+ // stats: { passed: 2, blocked: 1, failed: 0 }
564
+ // }
565
+ ```
566
+
567
+ **Features:**
568
+
569
+ - Standard `content_filter` finish reason
570
+ - Structured violation metadata
571
+ - Execution statistics
572
+ - Custom metadata preservation
573
+
370
574
  ## MCP Security Guardrails (Advanced)
371
575
 
372
576
  **Production-Ready**: Protect against the ["lethal trifecta" vulnerability](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/) when using Model Context Protocol (MCP) tools.
@@ -540,7 +744,7 @@ See source for all built-in guardrails:
540
744
 
541
745
  ## Examples
542
746
 
543
- Browse 48+ runnable examples: [examples/README.md](./examples/README.md)
747
+ Browse 48+ runnable examples: [examples/README.md](./examples/README.md) |
544
748
 
545
749
  ### Quick Starts
546
750
 
@@ -623,6 +827,10 @@ Changes:
623
827
 
624
828
  **Type-safe**: Rich TypeScript types and inference throughout.
625
829
 
830
+ **Comprehensive**: 40+ built-in guardrails covering security, quality, compliance, and performance.
831
+
832
+ **Advanced features**: Early detection, parallel execution, enhanced prompt injection detection, MCP security, and more.
833
+
626
834
  ## Contributing
627
835
 
628
836
  Issues and PRs are welcome.
@@ -0,0 +1,393 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/errors.ts
9
+ var GuardrailsError = class extends Error {
10
+ timestamp;
11
+ metadata;
12
+ constructor(message, metadata = {}) {
13
+ super(message);
14
+ this.timestamp = /* @__PURE__ */ new Date();
15
+ this.metadata = metadata;
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ /**
19
+ * Convert the error to a serializable object for logging/reporting
20
+ */
21
+ toJSON() {
22
+ return {
23
+ name: this.name,
24
+ code: this.code,
25
+ message: this.message,
26
+ timestamp: this.timestamp.toISOString(),
27
+ metadata: this.metadata,
28
+ stack: this.stack
29
+ };
30
+ }
31
+ /**
32
+ * Check if this error is of a specific type
33
+ */
34
+ is(errorClass) {
35
+ return this instanceof errorClass;
36
+ }
37
+ };
38
+ var GuardrailValidationError = class extends GuardrailsError {
39
+ name = "GuardrailValidationError";
40
+ code = "GUARDRAIL_VALIDATION_FAILED";
41
+ guardrailName;
42
+ validationErrors;
43
+ constructor(guardrailName, validationErrors, metadata = {}) {
44
+ const message = `Guardrail "${guardrailName}" validation failed: ${validationErrors.map((e) => e.message).join(", ")}`;
45
+ super(message, { ...metadata, guardrailName, validationErrors });
46
+ this.guardrailName = guardrailName;
47
+ this.validationErrors = validationErrors;
48
+ }
49
+ };
50
+ var GuardrailExecutionError = class extends GuardrailsError {
51
+ name = "GuardrailExecutionError";
52
+ code = "GUARDRAIL_EXECUTION_FAILED";
53
+ guardrailName;
54
+ originalError;
55
+ constructor(guardrailName, originalError, metadata = {}) {
56
+ const message = originalError ? `Guardrail "${guardrailName}" execution failed: ${originalError.message}` : `Guardrail "${guardrailName}" execution failed`;
57
+ super(message, {
58
+ ...metadata,
59
+ guardrailName,
60
+ originalError: originalError?.message
61
+ });
62
+ this.guardrailName = guardrailName;
63
+ this.originalError = originalError;
64
+ }
65
+ };
66
+ var GuardrailTimeoutError = class extends GuardrailsError {
67
+ name = "GuardrailTimeoutError";
68
+ code = "GUARDRAIL_TIMEOUT";
69
+ guardrailName;
70
+ timeoutMs;
71
+ constructor(guardrailName, timeoutMs, metadata = {}) {
72
+ const message = `Guardrail "${guardrailName}" timed out after ${timeoutMs}ms`;
73
+ super(message, { ...metadata, guardrailName, timeoutMs });
74
+ this.guardrailName = guardrailName;
75
+ this.timeoutMs = timeoutMs;
76
+ }
77
+ };
78
+ var GuardrailConfigurationError = class extends GuardrailsError {
79
+ name = "GuardrailConfigurationError";
80
+ code = "GUARDRAIL_CONFIG_INVALID";
81
+ configPath;
82
+ configErrors;
83
+ constructor(configErrors, configPath, metadata = {}) {
84
+ const message = `Guardrail configuration error${configPath ? ` in ${configPath}` : ""}: ${configErrors.join(", ")}`;
85
+ super(message, { ...metadata, configPath, configErrors });
86
+ this.configPath = configPath;
87
+ this.configErrors = configErrors;
88
+ }
89
+ };
90
+ var GuardrailsInputError = class extends GuardrailsError {
91
+ name = "GuardrailsInputError";
92
+ code = "INPUT_BLOCKED";
93
+ blockedGuardrails;
94
+ constructor(blockedGuardrails, metadata = {}) {
95
+ const guardrailNames = blockedGuardrails.map((g) => g.name).join(", ");
96
+ const message = `Input blocked by guardrail${blockedGuardrails.length > 1 ? "s" : ""}: ${guardrailNames}`;
97
+ super(message, { ...metadata, blockedGuardrails });
98
+ this.blockedGuardrails = blockedGuardrails;
99
+ }
100
+ };
101
+ var GuardrailsOutputError = class extends GuardrailsError {
102
+ name = "GuardrailsOutputError";
103
+ code = "OUTPUT_BLOCKED";
104
+ blockedGuardrails;
105
+ constructor(blockedGuardrails, metadata = {}) {
106
+ const guardrailNames = blockedGuardrails.map((g) => g.name).join(", ");
107
+ const message = `Output blocked by guardrail${blockedGuardrails.length > 1 ? "s" : ""}: ${guardrailNames}`;
108
+ super(message, { ...metadata, blockedGuardrails });
109
+ this.blockedGuardrails = blockedGuardrails;
110
+ }
111
+ };
112
+ var MiddlewareError = class extends GuardrailsError {
113
+ name = "MiddlewareError";
114
+ code = "MIDDLEWARE_ERROR";
115
+ middlewareType;
116
+ phase;
117
+ originalError;
118
+ constructor(middlewareType, phase, originalError, metadata = {}) {
119
+ const message = originalError ? `${middlewareType} middleware ${phase} error: ${originalError.message}` : `${middlewareType} middleware ${phase} error`;
120
+ super(message, {
121
+ ...metadata,
122
+ middlewareType,
123
+ phase,
124
+ originalError: originalError?.message
125
+ });
126
+ this.middlewareType = middlewareType;
127
+ this.phase = phase;
128
+ this.originalError = originalError;
129
+ }
130
+ };
131
+ function isGuardrailsError(error) {
132
+ return error instanceof GuardrailsError;
133
+ }
134
+ function extractErrorInfo(error) {
135
+ if (isGuardrailsError(error)) {
136
+ return {
137
+ name: error.name,
138
+ message: error.message,
139
+ code: error.code,
140
+ metadata: error.metadata
141
+ };
142
+ }
143
+ if (error instanceof Error) {
144
+ return {
145
+ name: error.name,
146
+ message: error.message
147
+ };
148
+ }
149
+ return {
150
+ name: "UnknownError",
151
+ message: String(error)
152
+ };
153
+ }
154
+
155
+ // src/core.ts
156
+ function createInputGuardrail(name, description, execute) {
157
+ return { name, description, execute };
158
+ }
159
+ function createOutputGuardrail(name, execute) {
160
+ return { name, execute };
161
+ }
162
+ function createGenerateWithErrorHandling(generate, signal, onError, retryOnError, maxRetries) {
163
+ return async (params, attemptNum) => {
164
+ try {
165
+ return signal ? await generate(params, signal) : await generate(params);
166
+ } catch (error) {
167
+ onError?.(error, attemptNum);
168
+ if (retryOnError?.(error, attemptNum) && attemptNum <= maxRetries) {
169
+ throw error;
170
+ }
171
+ throw error;
172
+ }
173
+ };
174
+ }
175
+ function buildRetrySummary(attemptHistory, validationResult, isUsingEnhancedFeatures, maxRetries) {
176
+ const blockedResults = attemptHistory.filter((historyAttempt) => historyAttempt.blocked).map(() => ({
177
+ message: validationResult.message,
178
+ metadata: validationResult.metadata
179
+ }));
180
+ const summary = { blockedResults };
181
+ if (isUsingEnhancedFeatures) {
182
+ summary.totalAttempts = maxRetries + 1;
183
+ summary.attempts = [...attemptHistory];
184
+ }
185
+ return summary;
186
+ }
187
+ async function performInitialAttempt(params, generateFn, validate, onAttempt, maxRetries, retryOnError) {
188
+ const attemptHistory = [];
189
+ onAttempt?.({
190
+ attempt: 0,
191
+ totalAttempts: maxRetries + 1,
192
+ isRetry: false
193
+ });
194
+ try {
195
+ const result = await generateFn(params, 0);
196
+ const validationResult = await Promise.resolve(validate(result));
197
+ attemptHistory.push({
198
+ attempt: 0,
199
+ result,
200
+ blocked: validationResult.blocked
201
+ });
202
+ return { result, validationResult, attemptHistory };
203
+ } catch (error) {
204
+ if (!retryOnError?.(error, 0)) {
205
+ throw error;
206
+ }
207
+ return {
208
+ result: void 0,
209
+ validationResult: {
210
+ blocked: true,
211
+ message: `Generation error: ${error}`
212
+ },
213
+ attemptHistory
214
+ };
215
+ }
216
+ }
217
+ async function performBackoffWait(backoffMs, attempt, signal) {
218
+ const wait = typeof backoffMs === "function" ? backoffMs(attempt) : backoffMs ?? 0;
219
+ if (wait && wait > 0) {
220
+ await new Promise((resolve, reject) => {
221
+ const timeout = setTimeout(resolve, wait);
222
+ signal?.addEventListener("abort", () => {
223
+ clearTimeout(timeout);
224
+ reject(new Error("Aborted during retry backoff"));
225
+ });
226
+ });
227
+ }
228
+ return;
229
+ }
230
+ async function retry(options) {
231
+ const {
232
+ generate,
233
+ params,
234
+ validate,
235
+ buildRetryParams,
236
+ maxRetries = 1,
237
+ backoffMs,
238
+ signal,
239
+ onAttempt,
240
+ retryOnError,
241
+ onError,
242
+ onExhausted = "return-last"
243
+ } = options;
244
+ signal?.throwIfAborted();
245
+ const generateFn = createGenerateWithErrorHandling(
246
+ generate,
247
+ signal,
248
+ onError,
249
+ retryOnError,
250
+ maxRetries
251
+ );
252
+ const {
253
+ result: initialResult,
254
+ validationResult,
255
+ attemptHistory
256
+ } = await performInitialAttempt(
257
+ params,
258
+ generateFn,
259
+ validate,
260
+ onAttempt,
261
+ maxRetries,
262
+ retryOnError
263
+ );
264
+ let result = initialResult;
265
+ let v = validationResult;
266
+ let lastParams = params;
267
+ let attempt = 0;
268
+ while (v.blocked && attempt < maxRetries) {
269
+ attempt++;
270
+ signal?.throwIfAborted();
271
+ const enhancedFeatures = !!(signal || onAttempt || retryOnError || onError || onExhausted !== "return-last");
272
+ const summary = buildRetrySummary(
273
+ attemptHistory,
274
+ v,
275
+ enhancedFeatures,
276
+ maxRetries
277
+ );
278
+ const nextParams = buildRetryParams({
279
+ summary,
280
+ originalParams: params,
281
+ lastParams,
282
+ lastResult: result
283
+ });
284
+ const wait = typeof backoffMs === "function" ? backoffMs(attempt) : backoffMs ?? 0;
285
+ onAttempt?.({
286
+ attempt,
287
+ totalAttempts: maxRetries + 1,
288
+ lastResult: result,
289
+ waitMs: wait,
290
+ isRetry: true
291
+ });
292
+ await performBackoffWait(backoffMs, attempt, signal);
293
+ lastParams = nextParams;
294
+ try {
295
+ result = await generateFn(lastParams, attempt);
296
+ v = await Promise.resolve(validate(result));
297
+ attemptHistory.push({
298
+ attempt,
299
+ result,
300
+ blocked: v.blocked,
301
+ waitMs: wait
302
+ });
303
+ } catch (error) {
304
+ if (!retryOnError?.(error, attempt)) {
305
+ throw error;
306
+ }
307
+ v = { blocked: true, message: `Generation error: ${error}` };
308
+ }
309
+ }
310
+ if (v.blocked && onExhausted === "throw") {
311
+ throw new Error(
312
+ `Retry exhausted after ${maxRetries} attempts: ${v.message}`
313
+ );
314
+ }
315
+ return result;
316
+ }
317
+ var retryHelpers = {
318
+ /**
319
+ * Increases max output tokens for retry attempts
320
+ */
321
+ increaseTokens: (increase = 200) => ({
322
+ lastParams
323
+ }) => ({
324
+ ...lastParams,
325
+ maxOutputTokens: Math.max(
326
+ 400,
327
+ (lastParams.maxOutputTokens ?? 400) + increase
328
+ )
329
+ }),
330
+ /**
331
+ * Adds encouraging prompt for retry attempts
332
+ */
333
+ addEncouragingPrompt: (encouragement = "Please provide a more detailed and comprehensive response.") => ({
334
+ lastParams,
335
+ summary
336
+ }) => {
337
+ const basePrompt = Array.isArray(lastParams.prompt) ? lastParams.prompt : [
338
+ {
339
+ role: "user",
340
+ content: [
341
+ { type: "text", text: String(lastParams.prompt || "") }
342
+ ]
343
+ }
344
+ ];
345
+ return {
346
+ ...lastParams,
347
+ prompt: [
348
+ ...basePrompt,
349
+ {
350
+ role: "user",
351
+ content: [
352
+ {
353
+ type: "text",
354
+ text: `${summary.blockedResults[0]?.message ? `Note: ${summary.blockedResults[0].message}.` : ""} ${encouragement}`
355
+ }
356
+ ]
357
+ }
358
+ ]
359
+ };
360
+ },
361
+ /**
362
+ * Combines token increase with encouraging prompt
363
+ */
364
+ improveResponse: (tokenIncrease = 200, encouragement) => (args) => {
365
+ const withTokens = retryHelpers.increaseTokens(tokenIncrease)(args);
366
+ const withEncouragement = retryHelpers.addEncouragingPrompt(
367
+ encouragement
368
+ )({ ...args, lastParams: withTokens });
369
+ return { ...withTokens, ...withEncouragement };
370
+ },
371
+ /**
372
+ * Simple parameter passthrough (no changes)
373
+ */
374
+ noChange: () => ({ lastParams }) => lastParams
375
+ };
376
+
377
+ export {
378
+ __require,
379
+ GuardrailsError,
380
+ GuardrailValidationError,
381
+ GuardrailExecutionError,
382
+ GuardrailTimeoutError,
383
+ GuardrailConfigurationError,
384
+ GuardrailsInputError,
385
+ GuardrailsOutputError,
386
+ MiddlewareError,
387
+ isGuardrailsError,
388
+ extractErrorInfo,
389
+ createInputGuardrail,
390
+ createOutputGuardrail,
391
+ retry,
392
+ retryHelpers
393
+ };