diff --git a/packages/core/src/agent/guardrail-provider.spec.ts b/packages/core/src/agent/guardrail-provider.spec.ts new file mode 100644 index 000000000..6c11c0e2c --- /dev/null +++ b/packages/core/src/agent/guardrail-provider.spec.ts @@ -0,0 +1,413 @@ +import { describe, expect, it, vi } from "vitest"; +import { + type GuardrailProvider, + type GuardrailProviderContext, + type GuardrailProviderDecision, + createGuardrailsFromProvider, +} from "./guardrail-provider"; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** Minimal stub for the `agent` property in guardrail args. */ +const stubAgent = { name: "test-agent" } as any; + +/** Minimal operation context stub. */ +const stubOc = { + logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }, +} as any; + +function makeInputArgs(text: string) { + return { + input: text, + inputText: text, + originalInput: text, + originalInputText: text, + agent: stubAgent, + context: stubOc, + operation: "generateText", + } as any; +} + +function makeOutputArgs(text: string) { + return { + output: text, + outputText: text, + originalOutput: text, + originalOutputText: text, + agent: stubAgent, + context: stubOc, + operation: "generateText", + usage: undefined, + finishReason: null, + warnings: null, + } as any; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("createGuardrailsFromProvider", () => { + it("returns empty arrays when provider implements neither direction", () => { + const provider: GuardrailProvider = { name: "Empty Provider" }; + const { inputGuardrails, outputGuardrails } = createGuardrailsFromProvider(provider); + expect(inputGuardrails).toHaveLength(0); + expect(outputGuardrails).toHaveLength(0); + }); + + it("creates an input guardrail when evaluateInput is implemented", () => { + const provider: GuardrailProvider = { + name: "Input Only", + evaluateInput: () => ({ pass: true }), + }; + const { inputGuardrails, outputGuardrails } = createGuardrailsFromProvider(provider); + expect(inputGuardrails).toHaveLength(1); + expect(outputGuardrails).toHaveLength(0); + }); + + it("creates an output guardrail when evaluateOutput is implemented", () => { + const provider: GuardrailProvider = { + name: "Output Only", + evaluateOutput: () => ({ pass: true }), + }; + const { inputGuardrails, outputGuardrails } = createGuardrailsFromProvider(provider); + expect(inputGuardrails).toHaveLength(0); + expect(outputGuardrails).toHaveLength(1); + }); + + it("creates both guardrails when provider implements both directions", () => { + const provider: GuardrailProvider = { + name: "Full Provider", + evaluateInput: () => ({ pass: true }), + evaluateOutput: () => ({ pass: true }), + }; + const { inputGuardrails, outputGuardrails } = createGuardrailsFromProvider(provider); + expect(inputGuardrails).toHaveLength(1); + expect(outputGuardrails).toHaveLength(1); + }); + + it("sets guardrail metadata from provider properties", () => { + const provider: GuardrailProvider = { + name: "AIP Identity", + description: "Cryptographic agent identity verification", + severity: "critical", + tags: ["identity", "security"], + evaluateInput: () => ({ pass: true }), + }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const guardrail = inputGuardrails[0] as any; + expect(guardrail.name).toBe("AIP Identity (input)"); + expect(guardrail.id).toBe("aip-identity-input"); + expect(guardrail.description).toBe("Cryptographic agent identity verification"); + expect(guardrail.severity).toBe("critical"); + expect(guardrail.tags).toEqual(["identity", "security"]); + }); + + it("allows overriding metadata via options", () => { + const provider: GuardrailProvider = { + name: "My Provider", + severity: "info", + tags: ["base"], + evaluateInput: () => ({ pass: true }), + }; + const { inputGuardrails } = createGuardrailsFromProvider(provider, { + id: "custom-id", + severity: "warning", + tags: ["extra"], + }); + const guardrail = inputGuardrails[0] as any; + expect(guardrail.id).toBe("custom-id-input"); + expect(guardrail.severity).toBe("warning"); + expect(guardrail.tags).toEqual(["base", "extra"]); + }); + + describe("input guardrail handler", () => { + it("passes content through when provider returns pass: true", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ pass: true }); + const provider: GuardrailProvider = { name: "Allow", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(true); + expect(result.action).toBe("allow"); + }); + + it("passes content through when provider returns undefined", async () => { + const evaluateInput = vi.fn().mockResolvedValue(undefined); + const provider: GuardrailProvider = { name: "NoOp", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(true); + }); + + it("blocks content when provider returns pass: false", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: false, + message: "Identity verification failed", + }); + const provider: GuardrailProvider = { name: "Block", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + expect(result.message).toBe("Identity verification failed"); + }); + + it("modifies content when provider returns action: modify", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: true, + action: "modify", + modifiedContent: "sanitized input", + message: "Content redacted", + }); + const provider: GuardrailProvider = { name: "Modify", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("sensitive data")); + expect(result.pass).toBe(true); + expect(result.action).toBe("modify"); + expect(result.modifiedInput).toBe("sanitized input"); + expect(result.message).toBe("Content redacted"); + }); + + it("passes the correct context to the provider", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ pass: true }); + const provider: GuardrailProvider = { name: "CtxCheck", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + await inputGuardrails[0].handler(makeInputArgs("test input")); + expect(evaluateInput).toHaveBeenCalledWith("test input", { + agentName: "test-agent", + operation: "generateText", + direction: "input", + }); + }); + + it("preserves metadata from provider decision", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: true, + metadata: { trustScore: 0.95, did: "did:aip:abc123" }, + }); + const provider: GuardrailProvider = { name: "Meta", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.metadata).toEqual({ trustScore: 0.95, did: "did:aip:abc123" }); + }); + + it("works with synchronous evaluateInput", async () => { + const provider: GuardrailProvider = { + name: "Sync", + evaluateInput: () => ({ pass: true, message: "sync ok" }), + }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(true); + expect(result.message).toBe("sync ok"); + }); + }); + + describe("output guardrail handler", () => { + it("passes output through when provider returns pass: true", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ pass: true }); + const provider: GuardrailProvider = { name: "AllowOut", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + const result = await outputGuardrails[0].handler(makeOutputArgs("model response")); + expect(result.pass).toBe(true); + expect(result.action).toBe("allow"); + }); + + it("blocks output when provider returns pass: false", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ + pass: false, + message: "Output contains PII", + }); + const provider: GuardrailProvider = { name: "BlockOut", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + const result = await outputGuardrails[0].handler(makeOutputArgs("name: John Doe")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + expect(result.message).toBe("Output contains PII"); + }); + + it("modifies output when provider returns action: modify", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ + pass: true, + action: "modify", + modifiedContent: "[redacted]", + }); + const provider: GuardrailProvider = { name: "RedactOut", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + const result = await outputGuardrails[0].handler(makeOutputArgs("secret data")); + expect(result.pass).toBe(true); + expect(result.action).toBe("modify"); + expect(result.modifiedOutput).toBe("[redacted]"); + }); + + it("passes the correct context to the provider", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ pass: true }); + const provider: GuardrailProvider = { name: "OutCtx", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + await outputGuardrails[0].handler(makeOutputArgs("response text")); + expect(evaluateOutput).toHaveBeenCalledWith("response text", { + agentName: "test-agent", + operation: "generateText", + direction: "output", + }); + }); + + it("handles empty outputText gracefully", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ pass: true }); + const provider: GuardrailProvider = { name: "EmptyOut", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + const args = makeOutputArgs(""); + args.outputText = undefined; + await outputGuardrails[0].handler(args); + expect(evaluateOutput).toHaveBeenCalledWith("", expect.any(Object)); + }); + }); + + describe("malformed modify decisions (fail-closed)", () => { + it("blocks input when action is modify but modifiedContent is missing", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: true, + action: "modify", + // no modifiedContent + message: "tried to modify", + }); + const provider: GuardrailProvider = { name: "BadModify", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + expect(result.message).toContain("BadModify"); + expect(result.message).toContain("modifiedContent"); + }); + + it("blocks output when action is modify but modifiedContent is missing", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ + pass: true, + action: "modify", + // no modifiedContent + }); + const provider: GuardrailProvider = { name: "BadModifyOut", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + const result = await outputGuardrails[0].handler(makeOutputArgs("response")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + expect(result.message).toContain("BadModifyOut"); + }); + + it("blocks when provider returns explicit action: block even with pass: true (input)", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: true, + action: "block", + message: "Explicit block overrides pass", + }); + const provider: GuardrailProvider = { name: "AuthBlock", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + expect(result.message).toBe("Explicit block overrides pass"); + }); + + it("blocks when provider returns action: block without pass field (input)", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + action: "block", + message: "No pass field", + }); + const provider: GuardrailProvider = { name: "NoPassBlock", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + }); + + it("allows when provider returns explicit action: allow with pass: false (action authoritative)", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: false, + action: "allow", + message: "Explicit allow overrides pass", + }); + const provider: GuardrailProvider = { name: "AuthAllow", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(true); + expect(result.action).toBe("allow"); + }); + + it("blocks when provider returns explicit action: block even with pass: true (output)", async () => { + const evaluateOutput = vi.fn().mockResolvedValue({ + pass: true, + action: "block", + message: "Explicit block on output", + }); + const provider: GuardrailProvider = { name: "AuthBlockOut", evaluateOutput }; + const { outputGuardrails } = createGuardrailsFromProvider(provider); + const result = await outputGuardrails[0].handler(makeOutputArgs("response")); + expect(result.pass).toBe(false); + expect(result.action).toBe("block"); + expect(result.message).toBe("Explicit block on output"); + }); + + it("provides a default message when modify lacks both modifiedContent and message", async () => { + const evaluateInput = vi.fn().mockResolvedValue({ + pass: true, + action: "modify", + }); + const provider: GuardrailProvider = { name: "NoMsg", evaluateInput }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const result = await inputGuardrails[0].handler(makeInputArgs("hello")); + expect(result.pass).toBe(false); + expect(result.message).toContain("NoMsg"); + expect(result.message).toContain("modify"); + }); + }); + + describe("collision-safe IDs", () => { + it("generates a fallback ID when provider name produces an empty slug", () => { + const provider: GuardrailProvider = { + name: "---", + evaluateInput: () => ({ pass: true }), + }; + const { inputGuardrails } = createGuardrailsFromProvider(provider); + const guardrail = inputGuardrails[0] as any; + expect(guardrail.id).toBeTruthy(); + expect(guardrail.id).not.toBe("-input"); + }); + + it("uses explicit id option over generated slug", () => { + const provider: GuardrailProvider = { + name: "Some Provider", + evaluateInput: () => ({ pass: true }), + }; + const { inputGuardrails } = createGuardrailsFromProvider(provider, { id: "explicit" }); + const guardrail = inputGuardrails[0] as any; + expect(guardrail.id).toBe("explicit-input"); + }); + }); + + describe("multiple providers", () => { + it("can combine guardrails from multiple providers", () => { + const providerA: GuardrailProvider = { + name: "Provider A", + evaluateInput: () => ({ pass: true }), + }; + const providerB: GuardrailProvider = { + name: "Provider B", + evaluateInput: () => ({ pass: true }), + evaluateOutput: () => ({ pass: true }), + }; + + const a = createGuardrailsFromProvider(providerA); + const b = createGuardrailsFromProvider(providerB); + + const combined = { + inputGuardrails: [...a.inputGuardrails, ...b.inputGuardrails], + outputGuardrails: [...a.outputGuardrails, ...b.outputGuardrails], + }; + + expect(combined.inputGuardrails).toHaveLength(2); + expect(combined.outputGuardrails).toHaveLength(1); + }); + }); +}); diff --git a/packages/core/src/agent/guardrail-provider.ts b/packages/core/src/agent/guardrail-provider.ts new file mode 100644 index 000000000..196c3b995 --- /dev/null +++ b/packages/core/src/agent/guardrail-provider.ts @@ -0,0 +1,371 @@ +/** + * GuardrailProvider — A pluggable interface for external guardrail implementations. + * + * External packages (e.g., AIP, APort) implement this interface to provide + * identity verification, trust scoring, content filtering, or any other + * guardrail logic. VoltAgent agents can then use any provider without + * coupling to a specific implementation. + * + * @example + * ```typescript + * import { Agent, createGuardrailsFromProvider } from "@voltagent/core"; + * import { AIPGuardrailProvider } from "@aip/voltagent-provider"; + * + * const provider = new AIPGuardrailProvider({ trustThreshold: 0.7 }); + * const { inputGuardrails, outputGuardrails } = createGuardrailsFromProvider(provider); + * + * const agent = new Agent({ + * name: "my-agent", + * inputGuardrails, + * outputGuardrails, + * }); + * ``` + */ + +import type { + GuardrailAction, + GuardrailSeverity, + InputGuardrail, + InputGuardrailArgs, + InputGuardrailResult, + OutputGuardrail, + OutputGuardrailArgs, + OutputGuardrailResult, +} from "./types"; + +// --------------------------------------------------------------------------- +// Provider types +// --------------------------------------------------------------------------- + +/** + * Context passed to a guardrail provider for each evaluation. + * Keeps the provider decoupled from VoltAgent internals while giving it + * the information it needs. + */ +export interface GuardrailProviderContext { + /** The name of the agent being guarded. */ + agentName: string; + /** The operation being performed (e.g., "generateText", "streamText"). */ + operation: string; + /** Direction of the guardrail check. */ + direction: "input" | "output"; +} + +/** + * The decision returned by a guardrail provider after evaluation. + */ +export interface GuardrailProviderDecision { + /** Whether the content passes the guardrail check. */ + pass: boolean; + /** + * The action to take. + * - `"allow"` — let the content through (default when pass is true) + * - `"modify"` — replace the content with `modifiedContent` + * - `"block"` — reject the content (default when pass is false) + */ + action?: GuardrailAction; + /** Human-readable reason for the decision. */ + message?: string; + /** + * Replacement content when action is `"modify"`. + * For input guardrails this replaces the user input. + * For output guardrails this replaces the model output. + */ + modifiedContent?: unknown; + /** Arbitrary metadata attached to the guardrail span for observability. */ + metadata?: Record; +} + +/** + * Abstract interface that external guardrail packages implement. + * + * At minimum a provider must implement {@link evaluateInput} or + * {@link evaluateOutput} (or both). Methods that are not implemented + * are skipped — no guardrail is created for that direction. + * + * The optional {@link name}, {@link description}, {@link severity}, and + * {@link tags} properties control how the guardrail appears in + * observability traces. + */ +export interface GuardrailProvider { + /** Display name for this provider (used in traces and logs). */ + readonly name: string; + + /** Optional human-readable description. */ + readonly description?: string; + + /** Severity level used when the guardrail blocks content. */ + readonly severity?: GuardrailSeverity; + + /** Tags for filtering and categorization in traces. */ + readonly tags?: string[]; + + /** + * Evaluate input content before it reaches the model. + * Return `undefined` or `{ pass: true }` to allow the input through. + */ + evaluateInput?( + content: string, + context: GuardrailProviderContext, + ): Promise | GuardrailProviderDecision | undefined; + + /** + * Evaluate output content before it is returned to the caller. + * Return `undefined` or `{ pass: true }` to allow the output through. + */ + evaluateOutput?( + content: string, + context: GuardrailProviderContext, + ): Promise | GuardrailProviderDecision | undefined; +} + +// --------------------------------------------------------------------------- +// Factory +// --------------------------------------------------------------------------- + +/** + * Options for {@link createGuardrailsFromProvider}. + */ +export interface CreateGuardrailsFromProviderOptions { + /** + * Override the provider's default severity for the generated guardrails. + */ + severity?: GuardrailSeverity; + /** + * Additional tags merged with the provider's own tags. + */ + tags?: string[]; + /** + * Optional unique id for the generated guardrail definitions. + * Defaults to a slug derived from the provider name. + */ + id?: string; +} + +/** + * Convert a {@link GuardrailProvider} into VoltAgent-native guardrail arrays + * that can be passed directly to an Agent constructor or per-call options. + * + * Only directions that the provider implements are included. If the provider + * implements neither `evaluateInput` nor `evaluateOutput`, both arrays will + * be empty. + * + * @example + * ```typescript + * const { inputGuardrails, outputGuardrails } = createGuardrailsFromProvider(myProvider); + * + * const agent = new Agent({ + * name: "guarded-agent", + * inputGuardrails, + * outputGuardrails, + * }); + * ``` + */ +export function createGuardrailsFromProvider( + provider: GuardrailProvider, + options?: CreateGuardrailsFromProviderOptions, +): { + inputGuardrails: InputGuardrail[]; + outputGuardrails: OutputGuardrail[]; +} { + const baseSlug = slugify(provider.name); + const id = options?.id ?? (baseSlug || `provider-${nextProviderId()}`); + const severity = options?.severity ?? provider.severity; + const tags = mergeTags(provider.tags, options?.tags); + + const inputGuardrails: InputGuardrail[] = []; + const outputGuardrails: OutputGuardrail[] = []; + + if (provider.evaluateInput) { + const evaluate = provider.evaluateInput.bind(provider); + + const inputGuardrail: InputGuardrail = { + id: `${id}-input`, + name: `${provider.name} (input)`, + description: provider.description, + severity, + tags, + handler: async (args: InputGuardrailArgs): Promise => { + const context: GuardrailProviderContext = { + agentName: args.agent.name, + operation: args.operation, + direction: "input", + }; + + const decision = await evaluate(args.inputText, context); + + // No decision — allow by default. + if (!decision) { + return { pass: true, action: "allow" }; + } + + // Explicit action is authoritative when present. + if (decision.action !== undefined) { + if (decision.action === "block") { + return { + pass: false, + action: "block", + message: decision.message, + metadata: decision.metadata, + }; + } + if (decision.action === "modify") { + if (decision.modifiedContent === undefined) { + // Modify without content — fail closed. + return { + pass: false, + action: "block", + message: `Provider "${provider.name}" returned action "modify" without modifiedContent — blocking as a safety precaution.`, + metadata: decision.metadata, + }; + } + return { + pass: true, + action: "modify", + message: decision.message, + metadata: decision.metadata, + modifiedInput: decision.modifiedContent as InputGuardrailResult["modifiedInput"], + }; + } + // action === "allow" — trust the provider. + return { + pass: true, + action: "allow", + message: decision.message, + metadata: decision.metadata, + }; + } + + // No explicit action — fall back to pass boolean. + if (decision.pass === false) { + return { + pass: false, + action: "block", + message: decision.message, + metadata: decision.metadata, + }; + } + + return { + pass: true, + action: "allow", + message: decision.message, + metadata: decision.metadata, + }; + }, + }; + + inputGuardrails.push(inputGuardrail); + } + + if (provider.evaluateOutput) { + const evaluate = provider.evaluateOutput.bind(provider); + + const outputGuardrail: OutputGuardrail = { + id: `${id}-output`, + name: `${provider.name} (output)`, + description: provider.description, + severity, + tags, + handler: async (args: OutputGuardrailArgs): Promise => { + const outputText = args.outputText ?? ""; + const context: GuardrailProviderContext = { + agentName: args.agent.name, + operation: args.operation, + direction: "output", + }; + + const decision = await evaluate(outputText, context); + + // No decision — allow by default. + if (!decision) { + return { pass: true, action: "allow" }; + } + + // Explicit action is authoritative when present. + if (decision.action !== undefined) { + if (decision.action === "block") { + return { + pass: false, + action: "block", + message: decision.message, + metadata: decision.metadata, + }; + } + if (decision.action === "modify") { + if (decision.modifiedContent === undefined) { + // Modify without content — fail closed. + return { + pass: false, + action: "block", + message: `Provider "${provider.name}" returned action "modify" without modifiedContent — blocking as a safety precaution.`, + metadata: decision.metadata, + }; + } + return { + pass: true, + action: "modify", + message: decision.message, + metadata: decision.metadata, + modifiedOutput: decision.modifiedContent, + }; + } + // action === "allow" — trust the provider. + return { + pass: true, + action: "allow", + message: decision.message, + metadata: decision.metadata, + }; + } + + // No explicit action — fall back to pass boolean. + if (decision.pass === false) { + return { + pass: false, + action: "block", + message: decision.message, + metadata: decision.metadata, + }; + } + + return { + pass: true, + action: "allow", + message: decision.message, + metadata: decision.metadata, + }; + }, + }; + + outputGuardrails.push(outputGuardrail); + } + + return { inputGuardrails, outputGuardrails }; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +let _providerIdCounter = 0; +function nextProviderId(): string { + return String(++_providerIdCounter); +} + +function slugify(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); +} + +function mergeTags( + providerTags?: string[], + optionTags?: string[], +): string[] | undefined { + if (!providerTags?.length && !optionTags?.length) { + return undefined; + } + return [...(providerTags ?? []), ...(optionTags ?? [])]; +} diff --git a/packages/core/src/agent/index.ts b/packages/core/src/agent/index.ts index 6fbadb28f..4e0a2acd4 100644 --- a/packages/core/src/agent/index.ts +++ b/packages/core/src/agent/index.ts @@ -46,4 +46,11 @@ export { createDefaultSafetyGuardrails, } from "./guardrails/defaults"; export { createInputGuardrail, createOutputGuardrail } from "./guardrail"; +export { createGuardrailsFromProvider } from "./guardrail-provider"; +export type { + GuardrailProvider, + GuardrailProviderContext, + GuardrailProviderDecision, + CreateGuardrailsFromProviderOptions, +} from "./guardrail-provider"; export { createInputMiddleware, createOutputMiddleware } from "./middleware"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 42425f7a1..e15809311 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -87,6 +87,13 @@ export { createDefaultSafetyGuardrails, } from "./agent/guardrails/defaults"; export { createInputGuardrail, createOutputGuardrail } from "./agent/guardrail"; +export { createGuardrailsFromProvider } from "./agent/guardrail-provider"; +export type { + GuardrailProvider, + GuardrailProviderContext, + GuardrailProviderDecision, + CreateGuardrailsFromProviderOptions, +} from "./agent/guardrail-provider"; export { createInputMiddleware, createOutputMiddleware } from "./agent/middleware"; export type { CreateInputGuardrailOptions,