import { agentNativePath } from "@agent-native/core/client/api-path"; import { useOrgRole } from "@agent-native/core/client/org"; import { IconExternalLink, IconTrash } from "@tabler/icons-react"; import { useRef, useState, type FormEvent } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "./ui/alert-dialog"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; export interface ConnectedAgent { id: string; name: string; description: string; url: string; color: string; source: "builtin" | "custom" | "workspace"; resourceId?: string; path?: string; scope?: "shared" | "personal"; } type AgentFormErrors = Partial>; function slugifyAgentName(value: string): string { return value .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); } function validateAgentForm(name: string, url: string): AgentFormErrors { const errors: AgentFormErrors = {}; const trimmedName = name.trim(); const trimmedUrl = url.trim(); if (!trimmedName) { errors.name = "Agent name is required."; } else if (!slugifyAgentName(trimmedName)) { errors.name = "Agent name must include at least one letter or number."; } if (!trimmedUrl) { errors.url = "Agent endpoint URL is required."; } else { try { const parsed = new URL(trimmedUrl); if (parsed.protocol !== "https:" && parsed.protocol !== "http:") { errors.url = "Use an http:// or https:// endpoint URL."; } else if (!parsed.hostname) { errors.url = "Enter a complete endpoint URL with a host."; } else if (parsed.username || parsed.password) { errors.url = "Do not include credentials in the endpoint URL."; } } catch { errors.url = "Enter a valid endpoint URL, such as https://app.example.com."; } } return errors; } export function AgentsPanel({ agents, onRefresh, }: { agents: ConnectedAgent[]; onRefresh: () => void; }) { const { org, isLoading: orgLoading, error: orgError } = useOrgRole(); const canManageSharedAgents = !orgLoading && !orgError && (!org?.orgId || org.role === "owner" || org.role === "admin"); const [name, setName] = useState(""); const [url, setUrl] = useState(""); const [description, setDescription] = useState(""); const [saving, setSaving] = useState(false); const [errors, setErrors] = useState({}); const nameRef = useRef(null); const customAgents = agents.filter((agent) => agent.source === "custom"); const workspaceAgents = agents.filter( (agent) => agent.source === "workspace", ); const builtinAgents = agents.filter((agent) => agent.source === "builtin"); const handleAdd = async (event?: FormEvent) => { event?.preventDefault(); const trimmedName = name.trim(); const trimmedUrl = url.trim(); const nextErrors = validateAgentForm(trimmedName, trimmedUrl); if (Object.keys(nextErrors).length > 0) { setErrors(nextErrors); return; } const id = slugifyAgentName(trimmedName); const agentJson = JSON.stringify( { id, name: trimmedName, description: description.trim() || undefined, url: trimmedUrl, color: "#6B7280", }, null, 2, ); setSaving(true); try { const res = await fetch(agentNativePath("/_agent-native/resources"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: `remote-agents/${id}.json`, content: agentJson, shared: true, }), }); if (res.ok) { setName(""); setUrl(""); setDescription(""); setErrors({}); onRefresh(); nameRef.current?.focus(); } else { setErrors({ form: `Could not add agent. Request failed with ${res.status}.`, }); } } catch (error) { setErrors({ form: error instanceof Error ? error.message : "Could not add agent. Please try again.", }); } finally { setSaving(false); } }; const handleDelete = async (resourceId?: string) => { if (!resourceId) return; const res = await fetch( agentNativePath(`/_agent-native/resources/${resourceId}`), { method: "DELETE", }, ); if (res.ok) onRefresh(); }; return (
Available by default
{builtinAgents.map((agent) => (
{agent.name}
))} {builtinAgents.length === 0 && (
No default agents detected.
)}
Added in this workspace
{workspaceAgents.map((agent) => (
{agent.name}
{agent.description ? (
{agent.description}
) : null}
))} {customAgents.map((agent) => (
{agent.name}
{agent.description ? (
{agent.description}
) : null}
{agent.url} · {agent.scope || "shared"}
{canManageSharedAgents && ( Remove this agent? “{agent.name}” will be removed from the workspace. Any jobs or chats that delegate to it will stop working. Cancel handleDelete(agent.resourceId)} > Remove )}
))} {workspaceAgents.length === 0 && customAgents.length === 0 && (
No extra agents added yet.
)}
Add external agent
{canManageSharedAgents ? (

Add another A2A-compatible app by saving its agent endpoint here.

) : !orgLoading ? (

Only workspace owners and admins can add shared agents.

) : null}
{ setName(event.target.value); setErrors((current) => ({ ...current, name: undefined })); }} placeholder="Name" aria-invalid={Boolean(errors.name)} aria-describedby={ errors.name ? "external-agent-name-error" : undefined } /> {errors.name ? (

{errors.name}

) : null}
{ setUrl(event.target.value); setErrors((current) => ({ ...current, url: undefined })); }} placeholder="https://app.example.com" aria-invalid={Boolean(errors.url)} aria-describedby={ errors.url ? "external-agent-url-error" : undefined } /> {errors.url ? (

{errors.url}

) : null}
setDescription(event.target.value)} placeholder="Description (optional)" /> {errors.form ? (

{errors.form}

) : null}
); }