"use client"; /* Agent configuration + the middleware-stack signature. Controlled component. */ import { useState } from "react"; import { Icon } from "../icons"; import { Field, Modal, Segmented, Tile, Toggle } from "../primitives"; import { FieldsForm, MW_FIELDS, MultiSelectChips } from "./ConfigForm"; import { MIDDLEWARE_CATALOG, MW_META } from "@/lib/data"; import { useModels } from "@/lib/models"; import type { Agent, ComponentT, McpClientT, Tool, ToolSet } from "@/lib/api"; type Cfg = Record; type MW = { type: string; enabled?: boolean; config?: Record }; export function AgentConfig({ config, onChange, tools = [], toolSets = [], agents = [], folders = [], kinds = [], mcpServers = [], components = [] }: { config: Cfg; onChange: (c: Cfg) => void; tools?: Tool[]; toolSets?: ToolSet[]; agents?: Agent[]; folders?: string[]; kinds?: string[]; mcpServers?: McpClientT[]; components?: ComponentT[] }) { const set = (patch: Cfg) => onChange({ ...config, ...patch }); const MODELS = useModels(); const flavor = config.flavor || "agent"; const selectedTools: string[] = config.tools || []; const selectedComponents: string[] = config.components || []; const mwCount = (config.middleware || []).filter((m: MW) => m.enabled !== false).length; const toggleTool = (id: string) => set({ tools: selectedTools.includes(id) ? selectedTools.filter((t) => t !== id) : [...selectedTools, id] }); const selectedToolSets: string[] = config.toolsets || []; const toggleToolSet = (id: string) => set({ toolsets: selectedToolSets.includes(id) ? selectedToolSets.filter((s) => s !== id) : [...selectedToolSets, id] }); // Tools are picked BY tool set (accordion), not from a flat list. const [openSets, setOpenSets] = useState>(new Set()); const toggleOpen = (id: string) => setOpenSets((prev) => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n; }); const toolById = new Map(tools.map((t) => [t.id, t])); const groupedIds = new Set(toolSets.flatMap((s) => s.tool_ids)); const ungrouped = tools.filter((t) => !groupedIds.has(t.id)); // Effective count = DISTINCT TOOL NAMES bound to the model: individually-picked tools ∪ every // tool of a whole-set grant, then collapsed BY NAME. The backend binds one function per name // (a tool shared across sets — or two records that share a name — reaches the model once), so // the badge matches what the model actually receives rather than counting the same name twice. const grantedIds = new Set([ ...selectedTools, ...toolSets.filter((s) => selectedToolSets.includes(s.id)).flatMap((s) => s.tool_ids), ]); const grantedCount = new Set([...grantedIds].map((id) => toolById.get(id)?.name ?? id)).size; const toggleComponent = (id: string) => set({ components: selectedComponents.includes(id) ? selectedComponents.filter((c) => c !== id) : [...selectedComponents, id] }); // Built-in knowledge access - compiles to agent-callable RAG / Q&A tools (see // tools/builtin.py build_knowledge_capability_tools). Each capability is independent. const knowledge = config.knowledge || {}; const setKnowledge = (key: "rag" | "qa", patch: Cfg) => set({ knowledge: { ...knowledge, [key]: { ...(knowledge[key] || {}), ...patch } } }); // Bind this node to a saved agent (from the Agents tab). When bound, the saved agent's // config drives the node LIVE - the backend resolves `agent_ref` at compile time, so // editing the agent once updates every node that uses it. A snapshot of its config is // also copied in so the canvas card + validation stay populated. "None" detaches and // keeps the current fields for inline editing. const boundId: string = config.agent_ref || ""; const boundAgent = boundId ? agents.find((a) => a.id === boundId) : undefined; const bindTo = (id: string) => { if (!id) { const { agent_ref: _drop, ...rest } = config; onChange(rest); return; } const a = agents.find((x) => x.id === id); if (!a) return; const { name: _name, ...cfg } = a.config || {}; onChange({ flavor: "agent", ...cfg, agent_ref: id }); }; return (
{agents.length > 0 && (
)} {boundId && (
{boundAgent ? ( <>
{boundAgent.name}
{boundAgent.config?.model || "-"} · {(boundAgent.config?.tools || []).length} tools · {(boundAgent.config?.middleware || []).length} middleware
This node mirrors the saved agent. Edit its model, instructions, tools, and middleware in the Agents tab - changes apply everywhere it's used.
) : (
The saved agent this node referenced wasn’t found (it may have been deleted). Pick another above, or detach to configure inline.
)}
)} {!boundId && ( <>
set({ flavor: v })} />