import { getErrorMessage } from '@agentkitai/agentlens-core'; import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { getAgents, getGuardrailStatus, createGuardrailRule, updateGuardrailRule, type CreateGuardrailData, } from '../api/client'; // ─── Constants ────────────────────────────────────────────────── const CONDITION_TYPES = [ { value: 'error_rate_threshold', label: 'Error Rate Threshold', category: 'operational' }, { value: 'cost_limit', label: 'Cost Limit', category: 'operational' }, { value: 'health_score_threshold', label: 'Health Score Threshold', category: 'operational' }, { value: 'custom_metric', label: 'Custom Metric', category: 'operational' }, { value: 'pii_detection', label: '🔒 PII Detection', category: 'content' }, { value: 'secrets_detection', label: '🔑 Secrets Detection', category: 'content' }, { value: 'content_regex', label: '📝 Content Regex', category: 'content' }, { value: 'toxicity', label: '⚠️ Toxicity', category: 'content' }, { value: 'prompt_injection', label: '🛡️ Prompt Injection', category: 'content' }, ] as const; const ACTION_TYPES = [ { value: 'pause_agent', label: 'Pause Agent' }, { value: 'notify_webhook', label: 'Notify Webhook' }, { value: 'downgrade_model', label: 'Downgrade Model' }, { value: 'agentgate_policy', label: 'AgentGate Policy' }, { value: 'block', label: '🚫 Block' }, { value: 'redact', label: '██ Redact' }, { value: 'log_and_continue', label: '📋 Log & Continue' }, { value: 'alert', label: '🔔 Alert' }, ] as const; const DIRECTION_OPTIONS = [ { value: 'both', label: 'Both (Input & Output)' }, { value: 'input', label: 'Input Only' }, { value: 'output', label: 'Output Only' }, ] as const; function isContentCondition(type: string): boolean { return ['pii_detection', 'secrets_detection', 'content_regex', 'toxicity', 'prompt_injection'].includes(type); } const OPERATORS = ['gt', 'gte', 'lt', 'lte', 'eq'] as const; // ─── Condition Config Fields ──────────────────────────────────── function ConditionConfigFields({ type, config, onChange }: { type: string; config: Record; onChange: (config: Record) => void; }) { const set = (key: string, value: unknown) => onChange({ ...config, [key]: value }); switch (type) { case 'error_rate_threshold': return (
); case 'cost_limit': return (
); case 'health_score_threshold': return (
); case 'custom_metric': return (
); case 'pii_detection': return (
); case 'secrets_detection': return (
); case 'content_regex': return (
); case 'toxicity': return (
); case 'prompt_injection': return (
); default: return null; } } // ─── Action Config Fields ─────────────────────────────────────── function ActionConfigFields({ type, config, onChange }: { type: string; config: Record; onChange: (config: Record) => void; }) { const set = (key: string, value: unknown) => onChange({ ...config, [key]: value }); switch (type) { case 'pause_agent': return (
); case 'notify_webhook': return (
); case 'downgrade_model': return (
); case 'agentgate_policy': return (
); default: return null; } } // ─── Main Form ────────────────────────────────────────────────── export default function GuardrailForm() { const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); const isEdit = Boolean(id); const [name, setName] = useState(''); const [agentId, setAgentId] = useState(''); const [enabled, setEnabled] = useState(true); const [dryRun, setDryRun] = useState(false); const [conditionType, setConditionType] = useState('error_rate_threshold'); const [conditionConfig, setConditionConfig] = useState>({}); const [actionType, setActionType] = useState('pause_agent'); const [actionConfig, setActionConfig] = useState>({}); const [cooldownMinutes, setCooldownMinutes] = useState(15); const [direction, setDirection] = useState<'input' | 'output' | 'both'>('both'); const [toolNamesStr, setToolNamesStr] = useState(''); const [priority, setPriority] = useState(0); const [agents, setAgents] = useState<{ id: string; name: string }[]>([]); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); // Load agents for dropdown useEffect(() => { getAgents().then(a => setAgents(a)).catch(() => {}); }, []); // Load existing rule for editing useEffect(() => { if (!id) return; getGuardrailStatus(id).then(({ rule }) => { setName(rule.name); setAgentId(rule.agentId ?? ''); setEnabled(rule.enabled); setDryRun(rule.dryRun); setConditionType(rule.conditionType); setConditionConfig(rule.conditionConfig); setActionType(rule.actionType); setActionConfig(rule.actionConfig); setCooldownMinutes(rule.cooldownMinutes); setDirection(rule.direction ?? 'both'); setToolNamesStr((rule.toolNames ?? []).join(', ')); setPriority(rule.priority ?? 0); }).catch(err => setError(`Failed to load rule: ${err}`)); }, [id]); const handleSubmit = useCallback(async (e: React.FormEvent) => { e.preventDefault(); setSaving(true); setError(''); const trimmedName = name.trim(); if (!trimmedName) { setError('Name is required'); setSaving(false); return; } const isContent = isContentCondition(conditionType); const toolNames = toolNamesStr.split(',').map(s => s.trim()).filter(Boolean); const data: CreateGuardrailData = { name: trimmedName, conditionType, conditionConfig, actionType, actionConfig, cooldownMinutes, enabled, dryRun, ...(agentId ? { agentId } : {}), ...(isContent ? { direction } : {}), ...(isContent && toolNames.length > 0 ? { toolNames } : {}), ...(isContent ? { priority } : {}), }; try { if (isEdit && id) { await updateGuardrailRule(id, data); } else { await createGuardrailRule(data); } navigate('/guardrails'); } catch (err: unknown) { setError(getErrorMessage(err) ?? 'Failed to save'); } finally { setSaving(false); } }, [name, agentId, enabled, dryRun, conditionType, conditionConfig, actionType, actionConfig, cooldownMinutes, direction, toolNamesStr, priority, isEdit, id, navigate]); return (

{isEdit ? '✏️ Edit Guardrail Rule' : '🛡️ Create Guardrail Rule'}

{error &&
{error}
}
{/* Basic fields */}

Basic

{/* Condition */}

Condition

{/* Content Rule Options — only shown for content condition types */} {isContentCondition(conditionType) && (

Content Rule Options

)} {/* Action */}

Action

{/* Submit */}
); } // ─── Tailwind class constants ──────────────────────────────────── const sectionClass = 'p-4 border border-slate-200 rounded-lg mb-4'; const sectionTitleClass = 'mb-3 text-base text-slate-700 font-medium'; const fieldGroupClass = 'grid grid-cols-2 gap-3'; const inputClass = 'w-full px-2.5 py-1.5 border border-gray-300 rounded text-sm mt-1'; const btnClass = 'px-5 py-2.5 bg-blue-500 text-white border-none rounded-md cursor-pointer text-sm font-semibold hover:bg-blue-600 transition-colors'; const cancelBtnClass = 'px-5 py-2.5 bg-transparent border border-gray-300 rounded-md cursor-pointer text-sm hover:bg-gray-50 transition-colors'; export { GuardrailForm };