@directive-run/ai 1.20.2 → 1.21.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.
@@ -0,0 +1,884 @@
1
+ import { m as GuardrailFn, I as InputGuardrailData, O as OutputGuardrailData, aW as SchemaValidator, T as ToolCallGuardrailData } from './types-DJ09LjZX.cjs';
2
+ import { a as Embedding } from './semantic-cache-DM7ev7NQ.cjs';
3
+ import { Plugin } from '@directive-run/core';
4
+
5
+ /**
6
+ * Built-in guardrails for AI adapter — PII, moderation, rate limiting, tool allowlists, schema validation.
7
+ */
8
+
9
+ /**
10
+ * Create a PII detection guardrail that scans input text for personally identifiable
11
+ * information. Blocks input when PII is detected, or optionally redacts matches and
12
+ * passes the sanitized text through.
13
+ *
14
+ * @param options - Configuration for PII detection: `patterns` sets the RegExp list (defaults to SSN, credit card, email), `redact` replaces matches instead of blocking, `redactReplacement` sets the replacement string (defaults to `"[REDACTED]"`).
15
+ * @returns An input guardrail that blocks or redacts PII in user input.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const piiGuardrail = createPIIGuardrail({
20
+ * patterns: [
21
+ * /\b\d{3}-\d{2}-\d{4}\b/, // SSN
22
+ * /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i, // Email
23
+ * ],
24
+ * redact: true,
25
+ * });
26
+ * ```
27
+ *
28
+ * @public
29
+ */
30
+ declare function createPIIGuardrail(options: {
31
+ patterns?: RegExp[];
32
+ redact?: boolean;
33
+ redactReplacement?: string;
34
+ }): GuardrailFn<InputGuardrailData>;
35
+ /**
36
+ * Create a content moderation guardrail that delegates to a user-supplied check function.
37
+ * Works on both input and output data — the guardrail extracts the text content
38
+ * automatically and passes it to {@link options.checkFn}.
39
+ *
40
+ * @param options - Configuration for content moderation: `checkFn` returns `true` when content should be flagged (supports async), `message` sets the rejection reason (defaults to `"Content flagged by moderation"`).
41
+ * @returns A guardrail that blocks content flagged by the check function.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const moderationGuardrail = createModerationGuardrail({
46
+ * checkFn: async (text) => {
47
+ * const result = await openai.moderations.create({ input: text });
48
+ * return result.results[0].flagged;
49
+ * },
50
+ * });
51
+ * ```
52
+ *
53
+ * @public
54
+ */
55
+ declare function createModerationGuardrail(options: {
56
+ checkFn: (text: string) => boolean | Promise<boolean>;
57
+ message?: string;
58
+ }): GuardrailFn<InputGuardrailData | OutputGuardrailData>;
59
+ /** Rate limiter with reset capability for testing */
60
+ interface RateLimitGuardrail extends GuardrailFn<InputGuardrailData> {
61
+ reset(): void;
62
+ }
63
+ /**
64
+ * Create a rate limit guardrail that tracks token and request counts over a sliding
65
+ * one-minute window. Returns a {@link RateLimitGuardrail} — a guardrail function with
66
+ * an additional `reset()` method for testing.
67
+ *
68
+ * @param options - Configuration for rate limiting: `maxTokensPerMinute` caps tokens in the sliding window (defaults to `100000`), `maxRequestsPerMinute` caps requests (defaults to `60`).
69
+ * @returns A rate limit guardrail with an attached `reset()` method.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const rateLimiter = createRateLimitGuardrail({
74
+ * maxTokensPerMinute: 50000,
75
+ * maxRequestsPerMinute: 30,
76
+ * });
77
+ * ```
78
+ *
79
+ * @public
80
+ */
81
+ declare function createRateLimitGuardrail(options: {
82
+ maxTokensPerMinute?: number;
83
+ maxRequestsPerMinute?: number;
84
+ }): RateLimitGuardrail;
85
+ /**
86
+ * Create a tool-call guardrail that restricts which tools an agent may invoke.
87
+ * Supports allowlist mode, denylist mode, or both — when both are provided, a tool
88
+ * must appear in the allowlist and not appear in the denylist.
89
+ *
90
+ * @param options - Configuration for tool filtering: `allowlist` sets permitted tool names, `denylist` sets blocked tool names, `caseSensitive` controls case matching (defaults to `false`).
91
+ * @returns A tool-call guardrail that enforces the allowlist/denylist rules.
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const toolGuardrail = createToolGuardrail({
96
+ * allowlist: ["search", "calculate"],
97
+ * denylist: ["delete_account"],
98
+ * });
99
+ * ```
100
+ *
101
+ * @public
102
+ */
103
+ declare function createToolGuardrail(options: {
104
+ allowlist?: string[];
105
+ denylist?: string[];
106
+ /** @default false */
107
+ caseSensitive?: boolean;
108
+ }): GuardrailFn<ToolCallGuardrailData>;
109
+ /**
110
+ * Create an output guardrail that validates agent output against a schema using
111
+ * a user-supplied {@link SchemaValidator}. Compatible with any validation library
112
+ * (Zod, Valibot, ArkType, etc.) via the adapter pattern.
113
+ *
114
+ * @param options - Configuration for schema validation: `validate` checks the output against the expected schema, `errorPrefix` is prepended to error messages (defaults to `"Output schema validation failed"`).
115
+ * @returns An output guardrail that rejects output failing schema validation.
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * import { z } from "zod";
120
+ *
121
+ * const schemaGuardrail = createOutputSchemaGuardrail({
122
+ * validate: (value) => {
123
+ * const result = z.object({ answer: z.string() }).safeParse(value);
124
+ * return { valid: result.success, errors: result.error?.issues.map(i => i.message) };
125
+ * },
126
+ * });
127
+ * ```
128
+ *
129
+ * @public
130
+ */
131
+ declare function createOutputSchemaGuardrail<T = unknown>(options: {
132
+ validate: SchemaValidator<T>;
133
+ errorPrefix?: string;
134
+ }): GuardrailFn<OutputGuardrailData>;
135
+ /**
136
+ * Create an output guardrail that performs lightweight runtime type checks without
137
+ * requiring a schema library. Supports `"string"`, `"number"`, `"boolean"`, `"object"`,
138
+ * and `"array"` with optional size and field constraints.
139
+ *
140
+ * @param options - Configuration for type checking: `type` sets the expected JS type, `requiredFields` lists object keys that must exist, `minLength`/`maxLength` constrain array size, `minStringLength`/`maxStringLength` constrain string length.
141
+ * @returns An output guardrail that rejects output not matching the expected type or constraints.
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * const typeGuardrail = createOutputTypeGuardrail({
146
+ * type: "object",
147
+ * requiredFields: ["id", "name"],
148
+ * });
149
+ * ```
150
+ *
151
+ * @public
152
+ */
153
+ declare function createOutputTypeGuardrail(options: {
154
+ type: "string" | "number" | "boolean" | "object" | "array";
155
+ requiredFields?: string[];
156
+ minLength?: number;
157
+ maxLength?: number;
158
+ minStringLength?: number;
159
+ maxStringLength?: number;
160
+ }): GuardrailFn<OutputGuardrailData>;
161
+ /**
162
+ * Create an output guardrail that enforces maximum length constraints on agent output,
163
+ * measured in characters or estimated tokens.
164
+ *
165
+ * @param options - Configuration for length limits: `maxCharacters` caps character count, `maxTokens` caps estimated token count, `estimateTokens` provides a custom token estimator (defaults to `Math.ceil(text.length / 4)`).
166
+ * @returns An output guardrail that rejects output exceeding the configured length limits.
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const lengthGuardrail = createLengthGuardrail({
171
+ * maxCharacters: 5000,
172
+ * maxTokens: 1200,
173
+ * });
174
+ * ```
175
+ *
176
+ * @public
177
+ */
178
+ declare function createLengthGuardrail(options: {
179
+ /** Maximum characters in output */
180
+ maxCharacters?: number;
181
+ /** Maximum estimated tokens in output */
182
+ maxTokens?: number;
183
+ /** Custom token estimator (default: chars / 4) */
184
+ estimateTokens?: (text: string) => number;
185
+ }): GuardrailFn<OutputGuardrailData>;
186
+ /**
187
+ * Create an output guardrail that blocks content matching any of the provided patterns.
188
+ * String patterns are escaped and compiled to RegExp; RegExp patterns are used as-is.
189
+ *
190
+ * @remarks
191
+ * A warning is logged when `blockedPatterns` is empty, since an empty list means no
192
+ * content will ever be filtered.
193
+ *
194
+ * @param options - Configuration for content filtering: `blockedPatterns` lists strings or RegExps to match against output, `caseSensitive` controls case matching for string patterns (defaults to `false`).
195
+ * @returns An output guardrail that rejects output containing any blocked pattern.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const contentFilter = createContentFilterGuardrail({
200
+ * blockedPatterns: [
201
+ * /\bpassword\b/i,
202
+ * /\bsecret\b/i,
203
+ * 'internal-only',
204
+ * ],
205
+ * });
206
+ * ```
207
+ *
208
+ * @public
209
+ */
210
+ declare function createContentFilterGuardrail(options: {
211
+ /** Patterns to block — strings or RegExp */
212
+ blockedPatterns: Array<string | RegExp>;
213
+ /** Case-sensitive matching for string patterns (default: false) */
214
+ caseSensitive?: boolean;
215
+ }): GuardrailFn<OutputGuardrailData>;
216
+
217
+ /**
218
+ * Fact-PII Guardrail — input guardrail at the fact-store boundary
219
+ *
220
+ * Closes the source → fact → agent prompt PII bypass surfaced by R5's
221
+ * red-team / privacy / AI-integration reviewers: `createPIIGuardrail`
222
+ * only inspects the `data.input` string at runStream entry, so PII that
223
+ * a source publishes into a fact (a Supabase realtime row carrying a
224
+ * customer email, a webhook payload with a SSN, an MCP server's
225
+ * resource notification with a card number) reaches the agent's
226
+ * prompt — via fact injection — without ever hitting the input
227
+ * guardrail chain.
228
+ *
229
+ * This plugin runs as a Directive plugin (`onFactSet` / `onFactsBatch`),
230
+ * scans every write to a pii-tagged fact against a sync regex matcher
231
+ * for the three highest-volume PII categories (SSN, credit card, email),
232
+ * and either **redacts** the value (the default — safe shipping posture)
233
+ * or **rejects** the write (throws so the source's publish handler can
234
+ * surface the violation). Operators wire it once at `createSystem`; no
235
+ * per-source / per-fact changes are required.
236
+ *
237
+ * The async PII detector from `pii-enhanced.ts` is unsuitable here:
238
+ * `onFactSet` is synchronous and a deferred detection would let the
239
+ * raw PII reach observers + breakpoints + audit-ledger before the
240
+ * redaction completed. Built-in matching is therefore inlined as
241
+ * synchronous regex. Consumers who need richer detection pass a
242
+ * synchronous `customDetector`.
243
+ *
244
+ * ## Observability
245
+ *
246
+ * Every detection emits a typed `"guardrail.blocked"` `ObservationEvent`
247
+ * via `system.observe()` (RFC 0010). Backend wiring (OTel exporters,
248
+ * `@directive-run/timeline`, audit-ledger plugins) subscribes to the
249
+ * event stream and gets:
250
+ *
251
+ * ```ts
252
+ * {
253
+ * type: "guardrail.blocked",
254
+ * plugin: "fact-pii-guardrail",
255
+ * key: "<fact-key>",
256
+ * kind: "redact" | "alert" | "detect",
257
+ * count: <number-of-matches>,
258
+ * category: "ssn" | "credit_card" | "email" | <customDetector-type>
259
+ * }
260
+ * ```
261
+ *
262
+ * The user `onBlocked` callback fires INDEPENDENTLY for backwards
263
+ * compatibility and ad-hoc paging (Sentry, Honeycomb). Prefer
264
+ * `system.observe()` for new integrations — observers see every
265
+ * registered guardrail's activity through the same typed stream.
266
+ *
267
+ * `kind: "detect"` distinguishes "couldn't redact" from "chose not
268
+ * to redact": `Error` instances always surface as `"detect"` because
269
+ * the walker matches `Error.message` / `Error.cause` but cannot mint
270
+ * a redacted Error with guaranteed `.stack` parity. A subscriber
271
+ * counting redactions should sum `kind === "redact" || kind === "detect"`.
272
+ *
273
+ * @example Defensive (redact PII writes into pii-tagged facts)
274
+ * ```ts
275
+ * import { createSystem, t } from '@directive-run/core';
276
+ * import { createFactPIIGuardrail } from '@directive-run/ai/guardrails';
277
+ *
278
+ * const customer = createModule('customer', {
279
+ * schema: {
280
+ * facts: {
281
+ * email: t.string().meta({ tags: ['pii'] }),
282
+ * ssn: t.string().meta({ tags: ['pii'] }),
283
+ * },
284
+ * },
285
+ * sources: {
286
+ * supabase: { attach: (publish) => subscribe(publish) },
287
+ * },
288
+ * });
289
+ *
290
+ * const system = createSystem({
291
+ * module: customer,
292
+ * plugins: [
293
+ * createFactPIIGuardrail({
294
+ * mode: 'redact',
295
+ * onBlocked: (key, detected) => {
296
+ * console.warn(`[fact-pii] redacted ${detected.length} match(es) in ${key}`);
297
+ * },
298
+ * }),
299
+ * ],
300
+ * });
301
+ * ```
302
+ *
303
+ * @example Monitor-only (alert on every PII match; don't mutate the fact)
304
+ * ```ts
305
+ * createFactPIIGuardrail({
306
+ * mode: 'alert',
307
+ * onBlocked: (key) => Sentry.captureException(new Error(`pii match: ${key}`)),
308
+ * });
309
+ * ```
310
+ *
311
+ * @example Allow specific keys (not just by tag)
312
+ * ```ts
313
+ * createFactPIIGuardrail({
314
+ * includeKeys: ['customer.email', 'customer.phone'],
315
+ * });
316
+ * ```
317
+ */
318
+
319
+ /**
320
+ * Public match record for a single PII finding. Mirrors `DetectedPII` from
321
+ * `pii-enhanced.ts` so a downstream guardrail can normalize against either
322
+ * detection path.
323
+ */
324
+ interface FactPIIMatch {
325
+ type: FactPIICategory;
326
+ value: string;
327
+ start: number;
328
+ end: number;
329
+ }
330
+ /** PII categories the built-in synchronous detector covers. */
331
+ type FactPIICategory = "ssn" | "credit_card" | "email";
332
+ /**
333
+ * Behavior when a pii-tagged fact's incoming value contains detected PII.
334
+ *
335
+ * - `"redact"` (default): the fact is rewritten with redacted text (e.g.
336
+ * `"[SSN]"`) via a follow-up store write. The system briefly observes
337
+ * the raw value during the same microtask the publish landed in, then
338
+ * the redacted value overwrites it before any reconcile / agent runs.
339
+ * Safe shipping posture for production: the LLM call always sees the
340
+ * redacted value.
341
+ * - `"alert"`: fire `onBlocked` but DO NOT mutate the fact. The raw value
342
+ * stays in the store. Use this for monitoring-only deployments where
343
+ * the source's transport is already trusted but a regression detector
344
+ * is needed (paging ops on every match).
345
+ *
346
+ * Note: Directive plugin hooks (`onFactSet`, `onFactsBatch`) are
347
+ * wrapped by the plugin manager's `safeCall` so a throw from inside the
348
+ * hook is swallowed. The guardrail therefore cannot reject the write
349
+ * itself — it can only observe + redact-via-followup-write or alert.
350
+ * For hard rejection at the publish boundary, a future RFC will add a
351
+ * pre-commit transform hook on the source primitive.
352
+ */
353
+ type FactPIIGuardrailMode = "redact" | "alert";
354
+ /**
355
+ * How to handle Error / AggregateError instances whose `.message`,
356
+ * `.cause`, or `.errors` carry PII matches.
357
+ *
358
+ * - `"redact"` (default in `mode: "redact"`): replace the value in the
359
+ * store with a new `Error(redactedMessage)`. The redacted Error
360
+ * preserves its `.name` but loses the original class identity
361
+ * (subclasses become plain `Error`), `.stack`, `.cause`, and
362
+ * `AggregateError.errors`, because those cannot be redacted
363
+ * losslessly without re-invoking the original constructor.
364
+ * - `"preserve"`: keep the original Error in the store. Only fire
365
+ * `onBlocked` + emit `guardrail.blocked` with kind `"detect"`. Use
366
+ * this for back-compat when downstream code relies on
367
+ * `instanceof YourErrorSubclass` or on `.stack` parity. The raw PII
368
+ * remains in store under this setting.
369
+ * - `"alert-only"`: same as `"preserve"` but emit kind `"alert"` so
370
+ * observability flags the leak as urgent.
371
+ */
372
+ type FactPIIErrorMode = "redact" | "preserve" | "alert-only";
373
+ interface FactPIIGuardrailOptions {
374
+ /** Default: `"redact"` */
375
+ mode?: FactPIIGuardrailMode;
376
+ /**
377
+ * Error / AggregateError handling. Defaults to `"redact"` when top-level
378
+ * `mode` is `"redact"`, `"alert-only"` when `mode` is `"alert"`. See
379
+ * {@link FactPIIErrorMode} for the per-option semantics.
380
+ *
381
+ * **Migration note:** prior to v1.21, the guardrail did NOT redact
382
+ * Errors regardless of the top-level `mode`. Consumers depending on
383
+ * the original Error class identity or `.stack` parity after a block
384
+ * must opt out by setting `errorMode: "preserve"`.
385
+ */
386
+ errorMode?: FactPIIErrorMode;
387
+ /**
388
+ * Built-in categories to scan for. Default: all three (`ssn`,
389
+ * `credit_card`, `email`). Pass `[]` to opt out of the built-ins and
390
+ * rely entirely on `customDetector`.
391
+ */
392
+ types?: readonly FactPIICategory[];
393
+ /**
394
+ * Specific fact keys to scan in addition to the auto-detected
395
+ * `pii`-tagged set. Useful when a consumer can't change the schema's
396
+ * meta but knows the key should be screened.
397
+ */
398
+ includeKeys?: readonly string[];
399
+ /**
400
+ * Exclude these fact keys even if they're pii-tagged. Escape hatch for
401
+ * a key that's already pre-sanitized upstream of the manager.
402
+ */
403
+ excludeKeys?: readonly string[];
404
+ /**
405
+ * Called whenever the guardrail detects PII and acts on it. Receives the
406
+ * fact key, the detected matches, and the action that was taken. Fires
407
+ * AFTER redact + before any throw in `reject` mode. Use this to alert
408
+ * SREs without coupling the guardrail to a specific logging backend.
409
+ */
410
+ onBlocked?: (key: string, detected: readonly FactPIIMatch[], action: FactPIIGuardrailMode) => void;
411
+ /**
412
+ * Custom synchronous detector that runs alongside the built-in regex
413
+ * scanner. The union of detections is acted on. Useful when the
414
+ * consumer ships a domain-specific PII detector (e.g. internal
415
+ * account-number format). MUST be synchronous — `onFactSet` cannot
416
+ * await deferred work.
417
+ */
418
+ customDetector?: (text: string) => readonly FactPIIMatch[];
419
+ /**
420
+ * Maximum nesting depth to walk when scanning a structured fact
421
+ * value. Default `4` (raised from `2` to cover Supabase realtime row
422
+ * shapes and MCP resource notification lists zero-config — those
423
+ * payloads are `payload.new = [{ email }]` which is four levels:
424
+ * object → array → object → string). At depth 4 the scanner
425
+ * inspects top-level strings, recurses into nested objects / arrays,
426
+ * and recurses three more levels. Each array level burns one depth
427
+ * slot. Maps and Sets are still NOT walked by the built-in scanner;
428
+ * PII inside those structures must go through a `customDetector`.
429
+ *
430
+ * Real-world common shapes and the `walkDepth` they need:
431
+ * - **Flat object** (`{ email, ssn }`) — works at any `walkDepth >= 1`.
432
+ * - **Nested object** (`{ profile: { email } }`) — needs `walkDepth >= 2`
433
+ * (covered by the default).
434
+ * - **Error with cause** (`new Error(m, { cause: prev })`) — the cause
435
+ * chain is recursed at `depth - 1`, so `walkDepth >= 2` is needed
436
+ * to scan one cause level (covered by the default). Deeper cause
437
+ * chains (`cause.cause.cause`) need `walkDepth >= 3`.
438
+ * - **AggregateError** with nested error PII — `walkDepth >= 2` for
439
+ * the first layer; deeper for nested aggregates.
440
+ * - **Supabase realtime row payload** (`payload.new = [{ email }]`,
441
+ * passed as the fact value) — the fact's value is the object
442
+ * `{ new: [...] }`, so the chain is object → array → object →
443
+ * string and needs `walkDepth: 4`.
444
+ * - **MCP resource notification list** (`{ resources: [{...}] }`) —
445
+ * same shape, needs `walkDepth: 4`.
446
+ *
447
+ * For deeper or non-object structures (Maps, Sets, class instances
448
+ * with getters, Symbol-keyed properties), pass a `customDetector`
449
+ * that walks the consumer-specific shape and returns concrete
450
+ * matches.
451
+ *
452
+ * **Hard caps for safety:**
453
+ * - `walkDepth` clamps to `[1, 5]`. Non-finite values (`NaN`,
454
+ * `Infinity`, non-number casts) clamp to `1`.
455
+ * - The walker caps any single array's element count at 10,000
456
+ * (warns + truncates). Pass a `customDetector` for larger
457
+ * payloads.
458
+ * - Before walking, the value is passed through `structuredClone`
459
+ * to strip any Proxies, exotic getters, Symbol-iterator
460
+ * overrides, and functions the consumer might have attached.
461
+ * Cyclic inputs throw `DataCloneError` from `structuredClone`
462
+ * and the walker treats them as "no match" with a warning.
463
+ * Non-cloneable values (functions, DOM nodes, WeakMaps, etc.)
464
+ * throw the same way — wire a `customDetector` for those shapes.
465
+ *
466
+ * Property iteration uses `Object.entries`, which skips
467
+ * Symbol-keyed properties and non-enumerable string keys. If you
468
+ * store PII under a Symbol key (unusual), a `customDetector` is the
469
+ * right escape hatch.
470
+ */
471
+ walkDepth?: number;
472
+ }
473
+ /**
474
+ * Create a Directive plugin that scans pii-tagged fact writes for PII and
475
+ * redacts or rejects them at the manager boundary.
476
+ *
477
+ * Wire it once at `createSystem({ plugins: [...] })`. The plugin caches
478
+ * the pii-tagged key set on `onInit` so per-write hooks are O(1) lookups.
479
+ *
480
+ * @returns a `Plugin` instance ready to add to `SystemConfig.plugins`.
481
+ */
482
+ declare function createFactPIIGuardrail(options?: FactPIIGuardrailOptions): Plugin;
483
+
484
+ /**
485
+ * Enhanced PII Detection Guardrail
486
+ *
487
+ * Provides comprehensive PII detection beyond basic regex patterns:
488
+ * - Multiple PII types (SSN, credit cards, emails, phones, addresses, names)
489
+ * - Pluggable detection backends (regex, custom, or external services)
490
+ * - Context-aware detection (reduces false positives)
491
+ * - Redaction with reversible or irreversible options
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * import { createEnhancedPIIGuardrail } from '@directive-run/ai';
496
+ *
497
+ * const guardrail = createEnhancedPIIGuardrail({
498
+ * types: ['ssn', 'credit_card', 'email'],
499
+ * redact: true,
500
+ * detector: 'regex', // or 'custom' with custom detector
501
+ * });
502
+ * ```
503
+ */
504
+
505
+ /** Supported PII types */
506
+ type PIIType = "ssn" | "credit_card" | "email" | "phone" | "address" | "name" | "date_of_birth" | "passport" | "driver_license" | "ip_address" | "bank_account" | "medical_id" | "national_id";
507
+ /** Detected PII instance */
508
+ interface DetectedPII {
509
+ type: PIIType;
510
+ value: string;
511
+ position: {
512
+ start: number;
513
+ end: number;
514
+ };
515
+ confidence: number;
516
+ context?: string;
517
+ }
518
+ /** PII detection result */
519
+ interface PIIDetectionResult {
520
+ detected: boolean;
521
+ items: DetectedPII[];
522
+ typeCounts: Partial<Record<PIIType, number>>;
523
+ /** Text with PII redacted (if requested) */
524
+ redactedText?: string;
525
+ }
526
+ /** Custom PII detector interface */
527
+ interface PIIDetector {
528
+ detect(text: string, types: PIIType[]): Promise<DetectedPII[]>;
529
+ name: string;
530
+ }
531
+ /** Redaction style */
532
+ type RedactionStyle =
533
+ /** Replace with [REDACTED] */
534
+ "placeholder"
535
+ /** Replace with type-specific placeholder like [EMAIL] */
536
+ | "typed"
537
+ /**
538
+ * Replace with a fixed-width `****` mask plus the last 4 characters
539
+ * (e.g. `****6789`). Does not preserve or reveal the original length.
540
+ */
541
+ | "masked"
542
+ /**
543
+ * Replace with a deterministic FNV-1a hash.
544
+ *
545
+ * **WARNING — this is PSEUDONYMIZATION, not anonymization.** FNV-1a is a
546
+ * fast, non-cryptographic 32-bit hash. For low-entropy structured PII (SSNs,
547
+ * card numbers, phone numbers) the output is trivially RE-IDENTIFIABLE via
548
+ * brute force or a precomputed rainbow table. It MUST NOT be relied on for
549
+ * GDPR / HIPAA de-identification. Use it only for referential integrity
550
+ * (correlating the same value across audit logs).
551
+ */
552
+ | "hashed";
553
+ /** Redact detected PII from text */
554
+ declare function redactPII(text: string, items: DetectedPII[], style?: RedactionStyle): string;
555
+ /** Options for enhanced PII guardrail */
556
+ interface EnhancedPIIGuardrailOptions {
557
+ /** PII types to detect (default: all) */
558
+ types?: PIIType[];
559
+ /** Detection backend (default: 'regex') */
560
+ detector?: "regex" | PIIDetector;
561
+ /** Redact instead of blocking */
562
+ redact?: boolean;
563
+ /** Redaction style (default: 'typed') */
564
+ redactionStyle?: RedactionStyle;
565
+ /** Minimum confidence to flag (0-1, default: 0.7) */
566
+ minConfidence?: number;
567
+ /** Callback when PII is detected */
568
+ onDetected?: (items: DetectedPII[]) => void;
569
+ /** Allow specific values (whitelist) */
570
+ allowlist?: string[];
571
+ /** Block only if count exceeds threshold */
572
+ minItemsToBlock?: number;
573
+ /** Timeout for custom detector in milliseconds (default: 5000) */
574
+ detectorTimeout?: number;
575
+ }
576
+ /**
577
+ * Create an enhanced PII detection guardrail.
578
+ *
579
+ * @example
580
+ * ```typescript
581
+ * // Basic usage
582
+ * const guardrail = createEnhancedPIIGuardrail();
583
+ *
584
+ * // Redact instead of blocking
585
+ * const redactGuardrail = createEnhancedPIIGuardrail({
586
+ * redact: true,
587
+ * redactionStyle: 'masked',
588
+ * });
589
+ *
590
+ * // Custom detection with external service
591
+ * const customGuardrail = createEnhancedPIIGuardrail({
592
+ * detector: myPresidioDetector,
593
+ * types: ['ssn', 'credit_card', 'medical_id'],
594
+ * });
595
+ * ```
596
+ */
597
+ declare function createEnhancedPIIGuardrail(options?: EnhancedPIIGuardrailOptions): GuardrailFn<InputGuardrailData>;
598
+ /**
599
+ * Create an output PII guardrail (for checking agent responses).
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * const outputGuardrail = createOutputPIIGuardrail({
604
+ * types: ['ssn', 'credit_card'],
605
+ * redact: true,
606
+ * });
607
+ * ```
608
+ */
609
+ declare function createOutputPIIGuardrail(options?: EnhancedPIIGuardrailOptions): GuardrailFn<OutputGuardrailData>;
610
+ /**
611
+ * Detect PII in text without using as a guardrail.
612
+ * Useful for analysis and logging.
613
+ *
614
+ * @example
615
+ * ```typescript
616
+ * const result = await detectPII('My SSN is 123-45-6789');
617
+ * console.log(result.items); // [{ type: 'ssn', value: '123-45-6789', ... }]
618
+ *
619
+ * // With custom detector and timeout
620
+ * const result = await detectPII(text, {
621
+ * detector: myPresidioDetector,
622
+ * timeout: 10000, // 10 seconds
623
+ * });
624
+ * ```
625
+ */
626
+ declare function detectPII(text: string, options?: {
627
+ types?: PIIType[];
628
+ detector?: "regex" | PIIDetector;
629
+ minConfidence?: number;
630
+ /** Timeout for custom detectors in milliseconds (default: 5000) */
631
+ timeout?: number;
632
+ }): Promise<PIIDetectionResult>;
633
+ /**
634
+ * Detect PII in text and return a result whose `redactedText` is populated.
635
+ *
636
+ * This composes {@link detectPII} and {@link redactPII} so callers do not have
637
+ * to manually wire the two together (and do not have to mutate the detection
638
+ * result). When no PII is detected, `redactedText` is left `undefined` — it is
639
+ * deliberately NOT defaulted to the raw input.
640
+ *
641
+ * @example
642
+ * ```typescript
643
+ * const result = await detectAndRedactPII('My SSN is 123-45-6789', {
644
+ * types: ['ssn'],
645
+ * style: 'typed',
646
+ * });
647
+ * console.log(result.detected); // true
648
+ * // ssn is a sensitive-category type, so `typed` emits a generic [REDACTED]
649
+ * console.log(result.redactedText); // 'My SSN is [REDACTED]'
650
+ *
651
+ * const clean = await detectAndRedactPII('nothing here', { types: ['ssn'] });
652
+ * console.log(clean.detected); // false
653
+ * console.log(clean.redactedText); // undefined
654
+ * ```
655
+ */
656
+ declare function detectAndRedactPII(text: string, options?: {
657
+ types?: PIIType[];
658
+ detector?: "regex" | PIIDetector;
659
+ minConfidence?: number;
660
+ /** Timeout for custom detectors in milliseconds (default: 5000) */
661
+ timeout?: number;
662
+ /** Redaction style applied when PII is detected (default: 'typed') */
663
+ style?: RedactionStyle;
664
+ }): Promise<PIIDetectionResult>;
665
+
666
+ /**
667
+ * Prompt Injection Detection Guardrail
668
+ *
669
+ * Detects and blocks prompt injection attacks including:
670
+ * - Direct injection attempts ("ignore previous instructions")
671
+ * - Jailbreak patterns ("DAN mode", "pretend you can")
672
+ * - Indirect injection via external content
673
+ * - Encoding-based evasion attempts
674
+ *
675
+ * @example
676
+ * ```typescript
677
+ * import { createPromptInjectionGuardrail } from '@directive-run/ai';
678
+ *
679
+ * const guardrail = createPromptInjectionGuardrail({
680
+ * strictMode: true,
681
+ * onBlocked: (input, patterns) => logSecurityEvent(input, patterns),
682
+ * });
683
+ * ```
684
+ */
685
+
686
+ /** Pattern with metadata for better debugging */
687
+ interface InjectionPattern {
688
+ pattern: RegExp;
689
+ name: string;
690
+ severity: "low" | "medium" | "high" | "critical";
691
+ category: InjectionCategory;
692
+ }
693
+ /** Categories of injection attacks */
694
+ type InjectionCategory = "instruction_override" | "jailbreak" | "role_manipulation" | "encoding_evasion" | "delimiter_injection" | "context_manipulation" | "indirect_injection";
695
+ /** Default injection patterns - well-tested and low false-positive rate */
696
+ declare const DEFAULT_INJECTION_PATTERNS: InjectionPattern[];
697
+ /** Strict patterns - more aggressive, may have higher false positives */
698
+ declare const STRICT_INJECTION_PATTERNS: InjectionPattern[];
699
+ /** Detailed detection result */
700
+ interface InjectionDetectionResult {
701
+ detected: boolean;
702
+ patterns: Array<{
703
+ name: string;
704
+ category: InjectionCategory;
705
+ severity: InjectionPattern["severity"];
706
+ match: string;
707
+ position: number;
708
+ }>;
709
+ riskScore: number;
710
+ sanitizedInput?: string;
711
+ }
712
+ /**
713
+ * Detect prompt injection patterns in text.
714
+ * Returns detailed results about what was detected.
715
+ *
716
+ * @throws Error if input exceeds MAX_INJECTION_INPUT_LENGTH (100KB)
717
+ */
718
+ declare function detectPromptInjection(text: string, patterns?: InjectionPattern[]): InjectionDetectionResult;
719
+ /**
720
+ * Sanitize text by removing detected injection patterns.
721
+ * Warning: This is a best-effort sanitization, not a security guarantee.
722
+ *
723
+ * Uses a single-pass approach to prevent infinite loops where a replacement
724
+ * could create a new pattern match.
725
+ */
726
+ declare function sanitizeInjection(text: string, patterns?: InjectionPattern[]): string;
727
+ /** Options for prompt injection guardrail */
728
+ interface PromptInjectionGuardrailOptions {
729
+ /** Additional patterns to check (added to defaults) */
730
+ additionalPatterns?: InjectionPattern[];
731
+ /** Replace default patterns entirely */
732
+ replacePatterns?: InjectionPattern[];
733
+ /** Use strict mode with more aggressive detection */
734
+ strictMode?: boolean;
735
+ /** Minimum risk score to block (0-100, default: 50) */
736
+ blockThreshold?: number;
737
+ /** Attempt to sanitize instead of blocking */
738
+ sanitize?: boolean;
739
+ /** Callback when injection is detected */
740
+ onBlocked?: (input: string, result: InjectionDetectionResult) => void;
741
+ /** Categories to ignore (e.g., allow 'role_manipulation' for roleplay apps) */
742
+ ignoreCategories?: InjectionCategory[];
743
+ }
744
+ /**
745
+ * Create a prompt injection detection guardrail.
746
+ *
747
+ * @example
748
+ * ```typescript
749
+ * // Basic usage
750
+ * const guardrail = createPromptInjectionGuardrail();
751
+ *
752
+ * // Strict mode for high-security applications
753
+ * const strictGuardrail = createPromptInjectionGuardrail({
754
+ * strictMode: true,
755
+ * blockThreshold: 25,
756
+ * });
757
+ *
758
+ * // Allow role manipulation for roleplay apps
759
+ * const roleplayGuardrail = createPromptInjectionGuardrail({
760
+ * ignoreCategories: ['role_manipulation'],
761
+ * });
762
+ * ```
763
+ */
764
+ declare function createPromptInjectionGuardrail(options?: PromptInjectionGuardrailOptions): GuardrailFn<InputGuardrailData>;
765
+ /**
766
+ * Mark content as potentially untrusted (from external sources).
767
+ * This wraps the content with markers that injection detection will scrutinize more closely.
768
+ *
769
+ * @example
770
+ * ```typescript
771
+ * const userUpload = await readFile(path);
772
+ * const markedContent = markUntrustedContent(userUpload, 'user-upload');
773
+ * const prompt = `Summarize this document: ${markedContent}`;
774
+ * ```
775
+ */
776
+ declare function markUntrustedContent(content: string, source: string): string;
777
+ /**
778
+ * Create a guardrail that applies stricter checks to marked untrusted content.
779
+ *
780
+ * @example
781
+ * ```typescript
782
+ * const guardrail = createUntrustedContentGuardrail({
783
+ * baseGuardrail: createPromptInjectionGuardrail({ strictMode: true }),
784
+ * });
785
+ * ```
786
+ */
787
+ declare function createUntrustedContentGuardrail(options: {
788
+ /** Guardrail to apply to untrusted sections */
789
+ baseGuardrail?: GuardrailFn<InputGuardrailData>;
790
+ /** Block if untrusted content contains these patterns */
791
+ additionalPatterns?: InjectionPattern[];
792
+ }): GuardrailFn<InputGuardrailData>;
793
+
794
+ /**
795
+ * Approximate Nearest Neighbor (ANN) Index for Semantic Cache
796
+ *
797
+ * Provides pluggable vector search backends for efficient similarity lookups.
798
+ * Includes a brute-force exact search and a VP-tree (Vantage Point Tree) for
799
+ * fast approximate nearest neighbor queries.
800
+ *
801
+ * @example
802
+ * ```typescript
803
+ * import { createSemanticCache } from '@directive-run/ai';
804
+ * import { createVPTreeIndex } from '@directive-run/ai';
805
+ *
806
+ * const index = createVPTreeIndex();
807
+ *
808
+ * const cache = createSemanticCache({
809
+ * embedder: myEmbedder,
810
+ * annIndex: index,
811
+ * });
812
+ * ```
813
+ */
814
+
815
+ /** Search result from an ANN index */
816
+ interface ANNSearchResult {
817
+ /** ID of the matched item */
818
+ id: number;
819
+ /** Similarity score (0-1, higher is more similar) */
820
+ similarity: number;
821
+ }
822
+ /** ANN Index interface - pluggable vector search backend */
823
+ interface ANNIndex {
824
+ /** Add a vector to the index */
825
+ add(id: number, embedding: Embedding): void;
826
+ /** Remove a vector from the index */
827
+ remove(id: number): void;
828
+ /** Search for the k nearest neighbors */
829
+ search(query: Embedding, k: number, threshold?: number): ANNSearchResult[];
830
+ /** Rebuild the index (call after batch additions) */
831
+ rebuild(): void;
832
+ /** Get the number of indexed vectors */
833
+ size(): number;
834
+ /** Clear the index */
835
+ clear(): void;
836
+ /** Check if the index needs to be rebuilt (e.g., after additions/removals) */
837
+ needsRebuild(): boolean;
838
+ }
839
+ /**
840
+ * Create a brute-force exact search index.
841
+ *
842
+ * O(n) search, O(1) add/remove. Best for small collections (< 10,000 vectors).
843
+ *
844
+ * @example
845
+ * ```typescript
846
+ * const index = createBruteForceIndex();
847
+ * index.add(0, [0.1, 0.2, 0.3]);
848
+ * index.add(1, [0.4, 0.5, 0.6]);
849
+ *
850
+ * const results = index.search([0.1, 0.2, 0.3], 1);
851
+ * // [{ id: 0, similarity: 1.0 }]
852
+ * ```
853
+ */
854
+ declare function createBruteForceIndex(): ANNIndex;
855
+ /** VP-Tree index configuration */
856
+ interface VPTreeIndexConfig {
857
+ /** Optional random number generator for deterministic builds. Returns a number in [0, 1). */
858
+ random?: () => number;
859
+ }
860
+ /**
861
+ * Create a VP-Tree (Vantage Point Tree) index for efficient approximate nearest neighbor search.
862
+ *
863
+ * O(log n) search on average, requires rebuild after batch additions.
864
+ * Best for medium collections (1,000 - 100,000 vectors).
865
+ *
866
+ * @example
867
+ * ```typescript
868
+ * const index = createVPTreeIndex();
869
+ *
870
+ * // Add vectors
871
+ * for (let i = 0; i < embeddings.length; i++) {
872
+ * index.add(i, embeddings[i]);
873
+ * }
874
+ *
875
+ * // Build the tree
876
+ * index.rebuild();
877
+ *
878
+ * // Search
879
+ * const results = index.search(queryEmbedding, 5, 0.9);
880
+ * ```
881
+ */
882
+ declare function createVPTreeIndex(vpConfig?: VPTreeIndexConfig): ANNIndex;
883
+
884
+ export { type ANNIndex as A, detectPII as B, detectPromptInjection as C, DEFAULT_INJECTION_PATTERNS as D, type EnhancedPIIGuardrailOptions as E, type FactPIICategory as F, markUntrustedContent as G, redactPII as H, type InjectionDetectionResult as I, sanitizeInjection as J, type PIIType as P, type RedactionStyle as R, STRICT_INJECTION_PATTERNS as S, type VPTreeIndexConfig as V, type ANNSearchResult as a, type DetectedPII as b, type FactPIIErrorMode as c, type FactPIIGuardrailMode as d, type FactPIIGuardrailOptions as e, type FactPIIMatch as f, type PIIDetectionResult as g, type PIIDetector as h, type PromptInjectionGuardrailOptions as i, type RateLimitGuardrail as j, createBruteForceIndex as k, createContentFilterGuardrail as l, createEnhancedPIIGuardrail as m, createFactPIIGuardrail as n, createLengthGuardrail as o, createModerationGuardrail as p, createOutputPIIGuardrail as q, createOutputSchemaGuardrail as r, createOutputTypeGuardrail as s, createPIIGuardrail as t, createPromptInjectionGuardrail as u, createRateLimitGuardrail as v, createToolGuardrail as w, createUntrustedContentGuardrail as x, createVPTreeIndex as y, detectAndRedactPII as z };