{"version":3,"file":"guardrail.mjs","names":[],"sources":["../../../../../../../../ai/src/middleware/builtins/guardrail.ts"],"sourcesContent":["import type {\n  AgentMiddleware,\n  MiddlewareTripContext,\n} from \"../../contracts/middleware\";\nimport type { ModelResponse } from \"../../contracts/model.contract\";\nimport { GuardrailViolationError } from \"../../errors\";\nimport { extractUserText } from \"../utils\";\n\n/**\n * Decision returned by a guardrail check function. `ok: true`\n * permits the call; `ok: false` rejects with a human-readable\n * `reason` surfaced on `GuardrailViolationError`.\n */\nexport type GuardrailCheckResult = { ok: true } | { ok: false; reason: string };\n\n/**\n * Synchronous or asynchronous check invoked against the outbound\n * prompt (`inputCheck`) or the inbound response (`outputCheck`).\n * Receives the raw text and the surrounding trip context so\n * consumers can classify, route to an external moderation API, or\n * branch on tripIndex / messages history.\n */\nexport type GuardrailCheck = (\n  text: string,\n  context: MiddlewareTripContext,\n) => GuardrailCheckResult | Promise<GuardrailCheckResult>;\n\n/**\n * Configuration for `guardrail()`.  At least one of `inputCheck` or\n * `outputCheck` must be supplied — a guardrail with no checks is a\n * no-op.\n */\nexport type GuardrailOptions = {\n  /**\n   * Run against the outbound prompt just before the model sees it.\n   * Fires every trip with the concatenated last user-message text.\n   * Rejection aborts the trip with a `GuardrailViolationError` whose\n   * `phase === \"input\"`.\n   */\n  inputCheck?: GuardrailCheck;\n  /**\n   * Run against the model's response text after the trip completes.\n   * Fires every trip with `response.content`. Rejection aborts with\n   * a `GuardrailViolationError` whose `phase === \"output\"`.\n   *\n   * Output checks run BEFORE any tool dispatch — a rejected response\n   * means the tool calls it requested are never invoked.\n   */\n  outputCheck?: GuardrailCheck;\n  /**\n   * Override the middleware name — surfaces on\n   * `GuardrailViolationError.guardrail` so operators can tell two\n   * guardrails apart in logs. Default `\"guardrail\"`.\n   */\n  name?: string;\n};\n\n/**\n * Pre/post content guardrail for an agent run.\n *\n * **Role.** Inspects outbound prompts and inbound responses against\n * caller-supplied policies, aborting the trip with a typed\n * `GuardrailViolationError` when either trips a check. Consumers\n * distinguish `\"input\"` vs `\"output\"` violations off `error.phase`\n * — the two failure modes have very different product responses\n * (block the user vs re-prompt the model).\n *\n * **Scope.** Per-trip. Fires on every round-trip the agent makes,\n * including repair attempts and tool-follow-up trips. Input checks\n * evaluate the last user-role message; output checks evaluate the\n * raw model response text before any tool call is dispatched.\n *\n * **Composition.** A single middleware instance can carry both\n * `inputCheck` and `outputCheck`, or two separate instances can be\n * registered (useful when input and output policies come from\n * different teams / services). Registration order determines which\n * guardrail's violation surfaces first — the short-circuit throws\n * from the innermost offending hook, and outer guardrails never run\n * after an abort.\n *\n * **Not a sanitizer.** The guardrail either passes a trip unchanged\n * or aborts it. Mutating the prompt / response in-place is out of\n * scope — use a bespoke middleware for content rewriting.\n *\n * @example\n * const policy = guardrail({\n *   inputCheck: async (text) =>\n *     text.includes(\"SSN\") ? { ok: false, reason: \"pii\" } : { ok: true },\n *   outputCheck: async (text) =>\n *     text.length > 10_000 ? { ok: false, reason: \"too-long\" } : { ok: true },\n * });\n *\n * const myAgent = agent({ model, middleware: [policy] });\n */\nexport function guardrail(options: GuardrailOptions): AgentMiddleware {\n  const name = options.name ?? \"guardrail\";\n  const { inputCheck, outputCheck } = options;\n\n  return {\n    name,\n    trip: {\n      async before(context) {\n        if (!inputCheck) {\n          return;\n        }\n\n        const prompt = extractUserText(context.messages);\n\n        if (!prompt) {\n          return;\n        }\n\n        const verdict = await inputCheck(prompt, context);\n\n        if (!verdict.ok) {\n          throw new GuardrailViolationError(\n            `guardrail \"${name}\" rejected input — ${verdict.reason}`,\n            { phase: \"input\", reason: verdict.reason, guardrail: name },\n          );\n        }\n      },\n      async after(context, response: ModelResponse) {\n        if (!outputCheck) {\n          return;\n        }\n\n        if (!response.content) {\n          return;\n        }\n\n        const verdict = await outputCheck(response.content, context);\n\n        if (!verdict.ok) {\n          throw new GuardrailViolationError(\n            `guardrail \"${name}\" rejected output — ${verdict.reason}`,\n            { phase: \"output\", reason: verdict.reason, guardrail: name },\n          );\n        }\n      },\n    },\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FA,SAAgB,UAAU,SAA4C;CACpE,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,EAAE,YAAY,gBAAgB;CAEpC,OAAO;EACL;EACA,MAAM;GACJ,MAAM,OAAO,SAAS;IACpB,IAAI,CAAC,YACH;IAGF,MAAM,SAAS,gBAAgB,QAAQ,QAAQ;IAE/C,IAAI,CAAC,QACH;IAGF,MAAM,UAAU,MAAM,WAAW,QAAQ,OAAO;IAEhD,IAAI,CAAC,QAAQ,IACX,MAAM,IAAI,wBACR,cAAc,KAAK,qBAAqB,QAAQ,UAChD;KAAE,OAAO;KAAS,QAAQ,QAAQ;KAAQ,WAAW;IAAK,CAC5D;GAEJ;GACA,MAAM,MAAM,SAAS,UAAyB;IAC5C,IAAI,CAAC,aACH;IAGF,IAAI,CAAC,SAAS,SACZ;IAGF,MAAM,UAAU,MAAM,YAAY,SAAS,SAAS,OAAO;IAE3D,IAAI,CAAC,QAAQ,IACX,MAAM,IAAI,wBACR,cAAc,KAAK,sBAAsB,QAAQ,UACjD;KAAE,OAAO;KAAU,QAAQ,QAAQ;KAAQ,WAAW;IAAK,CAC7D;GAEJ;EACF;CACF;AACF"}