import React, { useState, useCallback } from 'react'; import { useApi } from '../hooks/useApi'; import { getGuardrailRules, getGuardrailHistory, createGuardrailRule, updateGuardrailRule, deleteGuardrailRule, type GuardrailRuleData, type GuardrailTriggerData, type CreateGuardrailData, } from '../api/client'; // ─── Constants ────────────────────────────────────────────────── const CONDITION_OPTIONS = [ { value: 'error_rate_threshold', label: 'Error Rate Threshold' }, { value: 'cost_limit', label: 'Cost Limit' }, { value: 'health_score_threshold', label: 'Health Score Threshold' }, { value: 'custom_metric', label: 'Custom Metric' }, ]; const ACTION_OPTIONS = [ { value: 'pause_agent', label: 'Pause Agent' }, { value: 'notify_webhook', label: 'Notify Webhook' }, { value: 'downgrade_model', label: 'Downgrade Model' }, { value: 'agentgate_policy', label: 'AgentGate Policy' }, ]; type Tab = 'rules' | 'history'; function formatTimestamp(ts: string): string { try { return new Date(ts).toLocaleString(); } catch { return ts; } } // ─── Main Page ────────────────────────────────────────────────── export default function Guardrails() { const [tab, setTab] = useState('rules'); const [showCreate, setShowCreate] = useState(false); const rulesQuery = useApi(() => getGuardrailRules(), []); const historyQuery = useApi(() => getGuardrailHistory({ limit: 50 }), []); const rules = rulesQuery.data?.rules ?? []; const triggers = historyQuery.data?.triggers ?? []; const handleCreate = useCallback(async (data: CreateGuardrailData) => { await createGuardrailRule(data); setShowCreate(false); rulesQuery.refetch(); }, [rulesQuery]); const handleToggle = useCallback(async (rule: GuardrailRuleData) => { await updateGuardrailRule(rule.id, { enabled: !rule.enabled }); rulesQuery.refetch(); }, [rulesQuery]); const handleDelete = useCallback(async (id: string) => { if (!confirm('Delete this guardrail rule?')) return; await deleteGuardrailRule(id); rulesQuery.refetch(); historyQuery.refetch(); }, [rulesQuery, historyQuery]); return (

🛡️ Guardrails

{/* Tabs */}
{/* Create Form */} {showCreate && setShowCreate(false)} />} {/* Rules Tab */} {tab === 'rules' && (
{rules.length === 0 &&

No guardrail rules configured yet.

} {rules.map((rule) => ( ))}
)} {/* History Tab */} {tab === 'history' && (
{triggers.length === 0 &&

No triggers recorded yet.

} {triggers.map((t) => ( ))}
Time Rule Value Threshold Result
{formatTimestamp(t.triggeredAt)} {t.ruleId.slice(0, 8)}... {t.conditionValue.toFixed(2)} {t.conditionThreshold} {t.actionResult ?? 'unknown'}
)}
); } // ─── Sub-components ───────────────────────────────────────────── function RuleCard({ rule, onToggle, onDelete }: { rule: GuardrailRuleData; onToggle: (r: GuardrailRuleData) => void; onDelete: (id: string) => void; }) { return (
{rule.name} {rule.dryRun && [DRY RUN]} {rule.description &&

{rule.description}

}
Condition: {rule.conditionType} Action: {rule.actionType} Cooldown: {rule.cooldownMinutes}min {rule.agentId && Agent: {rule.agentId}}
); } function CreateForm({ onSubmit, onCancel }: { onSubmit: (data: CreateGuardrailData) => void; onCancel: () => void; }) { const [name, setName] = useState(''); const [conditionType, setConditionType] = useState('error_rate_threshold'); const [actionType, setActionType] = useState('pause_agent'); const [threshold, setThreshold] = useState('30'); const [cooldown, setCooldown] = useState('15'); const [dryRun, setDryRun] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const conditionConfig: Record = {}; if (conditionType === 'error_rate_threshold') { conditionConfig.threshold = Number(threshold); conditionConfig.windowMinutes = 5; } else if (conditionType === 'cost_limit') { conditionConfig.maxCostUsd = Number(threshold); conditionConfig.scope = 'daily'; } else if (conditionType === 'health_score_threshold') { conditionConfig.minScore = Number(threshold); } onSubmit({ name, conditionType, conditionConfig, actionType, actionConfig: {}, cooldownMinutes: Number(cooldown), dryRun, }); }; return (

New Guardrail Rule

); } // ─── Styles ───────────────────────────────────────────────────── const btnStyle: React.CSSProperties = { padding: '8px 16px', background: '#3b82f6', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', }; const smallBtnStyle: React.CSSProperties = { padding: '4px 10px', background: 'transparent', border: '1px solid #d1d5db', borderRadius: '4px', cursor: 'pointer', fontSize: '13px', }; const tabStyle: React.CSSProperties = { padding: '8px 16px', background: 'transparent', border: '1px solid #d1d5db', borderRadius: '6px', cursor: 'pointer', fontSize: '14px', }; const tabActiveStyle: React.CSSProperties = { ...tabStyle, background: '#3b82f6', color: 'white', borderColor: '#3b82f6', }; const cardStyle: React.CSSProperties = { padding: '16px', border: '1px solid #e2e8f0', borderRadius: '8px', marginBottom: '12px', }; const thStyle: React.CSSProperties = { padding: '8px 12px', fontSize: '13px', color: '#64748b' }; const tdStyle: React.CSSProperties = { padding: '8px 12px', fontSize: '13px' }; const inputStyle: React.CSSProperties = { width: '100%', padding: '6px 10px', border: '1px solid #d1d5db', borderRadius: '4px', fontSize: '14px', marginTop: '4px', };