From ca7285f46c1ac421b528add90ecb236a899986a6 Mon Sep 17 00:00:00 2001 From: Kaiohz Date: Fri, 24 Apr 2026 21:23:04 +0200 Subject: [PATCH 1/3] feat: add agent configuration form for creating and editing agents from UI - AgentConfigForm: full form with sections (General, System Prompt, Tools & Middleware, Backend, HITL, Memory & Skills, MCP Servers, Subagents, Response Format) - Zod validation schemas for AgentConfig and sub-types - YAML serialization/deserialization (js-yaml) for client-side form-to-API bridge - CreateAgentDialog: Tabs (Form / Upload YAML) for creating agents - AgentConfigViewer: Edit mode + Export YAML button - Reusable editors: StringListEditor, KeyValueEditor, McpServerEditor, SubAgentEditor - shadcn/ui components generated (Dialog, Form, Tabs, Accordion, etc.) - AgentsPage: button change from 'Create Agent (YAML)' to 'Create Agent' - 273/274 tests passing (1 pre-existing chat API failure) - Trivy: 0 new vulnerabilities COM-18 --- bun.lock | 20 +- package.json | 6 +- .../components/agent/AgentConfigForm.tsx | 670 ++++++++++++++++++ .../components/agent/AgentConfigViewer.tsx | 475 +++++++------ .../components/agent/CreateAgentDialog.tsx | 224 +++--- .../components/agent/KeyValueEditor.tsx | 89 +++ .../components/agent/McpServerEditor.tsx | 157 ++++ .../components/agent/StringListEditor.tsx | 80 +++ .../components/agent/SubAgentEditor.tsx | 112 +++ src/application/components/ui/accordion.tsx | 56 ++ src/application/components/ui/badge.tsx | 36 + src/application/components/ui/button.tsx | 56 ++ src/application/components/ui/dialog.tsx | 120 ++++ src/application/components/ui/form.tsx | 176 +++++ src/application/components/ui/input.tsx | 22 + src/application/components/ui/label.tsx | 24 + src/application/components/ui/scroll-area.tsx | 46 ++ src/application/components/ui/select.tsx | 160 +++++ src/application/components/ui/separator.tsx | 31 + src/application/components/ui/switch.tsx | 27 + src/application/components/ui/tabs.tsx | 55 ++ src/application/components/ui/textarea.tsx | 22 + src/application/lib/yaml.ts | 103 +++ src/application/pages/AgentsPage.tsx | 2 +- .../entities/agent/agentConfigSchema.ts | 64 ++ tests/fixtures/external.ts | 40 +- tests/setup.ts | 8 + tests/unit/application/lib/yaml.test.ts | 188 +++++ .../components/agent/AgentConfigForm.test.tsx | 167 +++++ .../agent/AgentConfigViewer.test.tsx | 104 +-- .../agent/CreateAgentDialog.test.tsx | 123 +--- .../components/agent/KeyValueEditor.test.tsx | 100 +++ .../components/agent/McpServerEditor.test.tsx | 82 +++ .../agent/StringListEditor.test.tsx | 100 +++ .../components/agent/SubAgentEditor.test.tsx | 75 ++ .../domain/entities/agentConfigSchema.test.ts | 250 +++++++ tests/unit/pages/AgentsPage.test.tsx | 21 +- 37 files changed, 3614 insertions(+), 477 deletions(-) create mode 100644 src/application/components/agent/AgentConfigForm.tsx create mode 100644 src/application/components/agent/KeyValueEditor.tsx create mode 100644 src/application/components/agent/McpServerEditor.tsx create mode 100644 src/application/components/agent/StringListEditor.tsx create mode 100644 src/application/components/agent/SubAgentEditor.tsx create mode 100644 src/application/components/ui/accordion.tsx create mode 100644 src/application/components/ui/badge.tsx create mode 100644 src/application/components/ui/button.tsx create mode 100644 src/application/components/ui/dialog.tsx create mode 100644 src/application/components/ui/form.tsx create mode 100644 src/application/components/ui/input.tsx create mode 100644 src/application/components/ui/label.tsx create mode 100644 src/application/components/ui/scroll-area.tsx create mode 100644 src/application/components/ui/select.tsx create mode 100644 src/application/components/ui/separator.tsx create mode 100644 src/application/components/ui/switch.tsx create mode 100644 src/application/components/ui/tabs.tsx create mode 100644 src/application/components/ui/textarea.tsx create mode 100644 src/application/lib/yaml.ts create mode 100644 src/domain/entities/agent/agentConfigSchema.ts create mode 100644 tests/unit/application/lib/yaml.test.ts create mode 100644 tests/unit/components/agent/AgentConfigForm.test.tsx create mode 100644 tests/unit/components/agent/KeyValueEditor.test.tsx create mode 100644 tests/unit/components/agent/McpServerEditor.test.tsx create mode 100644 tests/unit/components/agent/StringListEditor.test.tsx create mode 100644 tests/unit/components/agent/SubAgentEditor.test.tsx create mode 100644 tests/unit/domain/entities/agentConfigSchema.test.ts diff --git a/bun.lock b/bun.lock index a2ec038..1d78c36 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "dependencies": { "@hookform/resolvers": "^5.2.2", "@microsoft/fetch-event-source": "^2.0.1", + "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", @@ -16,6 +17,7 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", @@ -26,10 +28,11 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "framer-motion": "^12.38.0", + "js-yaml": "^4.1.1", "lucide-react": "^1.7.0", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-hook-form": "^7.72.1", + "react-hook-form": "^7.73.1", "react-markdown": "^10.1.0", "react-router-dom": "^7.14.0", "remark-gfm": "^4.0.1", @@ -45,6 +48,7 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", + "@types/js-yaml": "^4.0.9", "@types/node": "^25.5.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", @@ -182,12 +186,16 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], @@ -232,6 +240,8 @@ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], @@ -388,6 +398,8 @@ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], @@ -456,6 +468,8 @@ "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], @@ -700,6 +714,8 @@ "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "jsdom": ["jsdom@25.0.1", "", { "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.12", "parse5": "^7.1.2", "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^2.11.2" }, "optionalPeers": ["canvas"] }, "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], @@ -912,7 +928,7 @@ "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], - "react-hook-form": ["react-hook-form@7.72.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig=="], + "react-hook-form": ["react-hook-form@7.73.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-VAfVYOPcx3piiEVQy95vyFmBwbVUsP/AUIN+mpFG8h11yshDd444nn0VyfaGWSRnhOLVgiDu7HIuBtAIzxn9dA=="], "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], diff --git a/package.json b/package.json index 0c427c3..ad3ec99 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "@hookform/resolvers": "^5.2.2", "@microsoft/fetch-event-source": "^2.0.1", + "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", @@ -29,6 +30,7 @@ "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", @@ -39,10 +41,11 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "framer-motion": "^12.38.0", + "js-yaml": "^4.1.1", "lucide-react": "^1.7.0", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-hook-form": "^7.72.1", + "react-hook-form": "^7.73.1", "react-markdown": "^10.1.0", "react-router-dom": "^7.14.0", "remark-gfm": "^4.0.1", @@ -58,6 +61,7 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", + "@types/js-yaml": "^4.0.9", "@types/node": "^25.5.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", diff --git a/src/application/components/agent/AgentConfigForm.tsx b/src/application/components/agent/AgentConfigForm.tsx new file mode 100644 index 0000000..ae73c5f --- /dev/null +++ b/src/application/components/agent/AgentConfigForm.tsx @@ -0,0 +1,670 @@ +import { useState } from "react"; +import { useForm, useFieldArray, type SubmitHandler } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import type { AgentConfig } from "@/domain/entities/agent/agentConfig"; +import { + BackendType, + MiddlewareType, +} from "@/domain/entities/agent/agentConfig"; +import { McpTransportType } from "@/domain/entities/agent/mcpServerConfig"; +import { + agentConfigSchema, + type AgentConfigFormData, +} from "@/domain/entities/agent/agentConfigSchema"; +import { Button } from "@/application/components/ui/button"; +import { Input } from "@/application/components/ui/input"; +import { Label } from "@/application/components/ui/label"; +import { Switch } from "@/application/components/ui/switch"; +import { Textarea } from "@/application/components/ui/textarea"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/application/components/ui/accordion"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/application/components/ui/select"; +import { ScrollArea } from "@/application/components/ui/scroll-area"; +import { Separator } from "@/application/components/ui/separator"; +import StringListEditor from "./StringListEditor"; +import McpServerEditor from "./McpServerEditor"; +import SubAgentEditor from "./SubAgentEditor"; + +interface AgentConfigFormProps { + mode: "create" | "edit"; + initialData?: AgentConfig; + onSubmit: (data: AgentConfigFormData) => void; + onCancel: () => void; + isPending?: boolean; +} + +const MIDDLEWARE_OPTIONS = [ + { value: MiddlewareType.TODO_LIST, label: "todo_list" }, + { value: MiddlewareType.FILESYSTEM, label: "filesystem" }, + { value: MiddlewareType.SUB_AGENT, label: "sub_agent" }, +]; + +const BACKEND_OPTIONS = [ + { value: BackendType.STATE, label: "state" }, + { value: BackendType.STORE, label: "store" }, + { value: BackendType.FILESYSTEM, label: "filesystem" }, + { value: BackendType.COMPOSITE, label: "composite" }, +]; + +function getDefaultValues( + mode: "create" | "edit", + initialData?: AgentConfig, +): AgentConfigFormData { + if (mode === "edit" && initialData) { + return { + name: initialData.name, + model: initialData.model, + system_prompt: initialData.system_prompt ?? "", + system_prompt_file: initialData.system_prompt_file ?? "", + tools: initialData.tools, + middleware: initialData.middleware, + backend: initialData.backend, + hitl: initialData.hitl, + memory: initialData.memory, + skills: initialData.skills, + subagents: initialData.subagents, + mcp_servers: initialData.mcp_servers, + response_format: initialData.response_format ?? undefined, + debug: initialData.debug, + }; + } + return { + name: "", + model: "", + system_prompt: "", + system_prompt_file: "", + tools: [], + middleware: [], + backend: { type: BackendType.STATE }, + hitl: { rules: {} }, + memory: [], + skills: [], + subagents: [], + mcp_servers: [], + response_format: undefined, + debug: false, + }; +} + +export default function AgentConfigForm({ + mode, + initialData, + onSubmit, + onCancel, + isPending = false, +}: Readonly) { + const form = useForm({ + resolver: zodResolver(agentConfigSchema), + defaultValues: getDefaultValues(mode, initialData), + }); + + const { + register, + handleSubmit, + control, + setValue, + getValues, + watch, + formState: { errors }, + } = form; + + const mcpServersArray = useFieldArray({ control, name: "mcp_servers" }); + const subagentsArray = useFieldArray({ control, name: "subagents" }); + + const debugValue = watch("debug"); + const backendType = watch("backend.type"); + + const handleFormSubmit: SubmitHandler = (data) => { + const cleaned: AgentConfigFormData = { + ...data, + system_prompt: data.system_prompt || undefined, + system_prompt_file: data.system_prompt_file || undefined, + }; + onSubmit(cleaned); + }; + + function addMcpServer() { + mcpServersArray.append({ + name: "", + transport: McpTransportType.STDIO, + command: "", + args: [], + url: "", + headers: {}, + env: {}, + auth_token: "", + }); + } + + function addSubagent() { + subagentsArray.append({ + name: "", + description: "", + instructions: "", + model: "", + tools: [], + skills: [], + mcp_servers: [], + }); + } + + function toggleMiddleware(mw: MiddlewareType, isChecked: boolean) { + const current = getValues("middleware") as MiddlewareType[]; + if (isChecked) { + setValue("middleware", [...current, mw]); + } else { + setValue( + "middleware", + current.filter((m) => m !== mw), + ); + } + } + + return ( +
+ +
+ + {/* General */} + + + General + + +
+ + + {errors.name && ( + + )} +
+
+ + + {errors.model && ( + + )} +
+
+ + setValue("debug", v)} + /> +
+
+
+ + {/* System Prompt */} + + + System Prompt + + +
+ +