diff --git a/packages/components/agent-builder/README.md b/packages/components/agent-builder/README.md
new file mode 100644
index 00000000..407502cc
--- /dev/null
+++ b/packages/components/agent-builder/README.md
@@ -0,0 +1,87 @@
+# @openassistant/agent-builder
+
+> Visual workflow builder for composing OpenAssistant tools into agents with React Flow.
+
+## Features
+
+- π¦ Automatically imports every tool that ships with OpenAssistant (plots, geoda, duckdb, osm, places, β¦) and makes them available as drag-and-drop nodes.
+- π§± Provides custom metadata nodes (name, description, result, error) so you can describe the agent contract right on the canvas.
+- πΈοΈ Uses [React Flow](https://reactflow.dev/) to let you draw DAGs that represent the execution graph for the experimental AI SDK v5 `Agent` class.
+- π§Ύ Generates a rich JSON schema (tools, metadata, adjacency, topological order, zod-powered parameter schemas) with a single `Create Agent` click.
+- π¨ Modern Tailwind-based UI with search, grouping, and real-time schema preview.
+
+## Installation
+
+```bash
+yarn add @openassistant/agent-builder
+# or
+npm install @openassistant/agent-builder
+```
+
+This package ships as an ES module + CommonJS bundle and expects `react`, `react-dom`, and `reactflow` as peer dependencies.
+
+## Quick start
+
+```tsx
+import { AgentBuilder, defaultToolRegistry } from '@openassistant/agent-builder';
+import '@openassistant/agent-builder/dist/index.css';
+
+export function BuilderExample() {
+ return (
+
+
{
+ // Persist the schema or hydrate an Agent instance.
+ console.log(schema);
+ }}
+ />
+
+ );
+}
+```
+
+By default the builder loads every OpenAssistant tool it can find. You can limit or extend the catalog by passing your own `tools` prop (array of `AgentBuilderTool`).
+
+## JSON schema
+
+Click **Create Agent** to produce a schema shaped like:
+
+```json
+{
+ "agent": {
+ "name": "Spatial Analyst Agent",
+ "description": "Runs Moran scatterplot after DuckDB query",
+ "result": "GeoJSON feature collection",
+ "error": "Surface upstream DuckDB errors"
+ },
+ "workflow": {
+ "order": ["meta_agentName_ab1", "tool_duckdb_1", "tool_moran_2"],
+ "edges": [{ "id": "e1", "source": "tool_duckdb_1", "target": "tool_moran_2" }],
+ "adjacency": { "tool_duckdb_1": ["tool_moran_2"] }
+ },
+ "tools": [
+ {
+ "nodeId": "tool_duckdb_1",
+ "name": "duckdbQuery",
+ "description": "Run SQL queries against DuckDB",
+ "parametersSchema": { "...": "zod-based JSON schema" }
+ }
+ ],
+ "customNodes": [
+ { "nodeId": "meta_agentName_ab1", "type": "agentName", "value": "Spatial Analyst Agent" }
+ ]
+}
+```
+
+You can feed this JSON directly into downstream orchestration layers to instantiate the AI SDK Agent or to persist/share templates.
+
+## Customization tips
+
+- `customNodes`: provide your own metadata node definitions (label, description, placeholder).
+- `initialNodes` / `initialEdges`: hydrate the canvas from a previously saved schema.
+- Use `buildAgentSchema(nodes, edges)` if you need to generate the schema outside of the built-in button.
+
+## License
+
+MIT β see the root repository for details.
diff --git a/packages/components/agent-builder/esbuild.config.mjs b/packages/components/agent-builder/esbuild.config.mjs
new file mode 100644
index 00000000..0c8936d5
--- /dev/null
+++ b/packages/components/agent-builder/esbuild.config.mjs
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import {
+ createBaseConfig,
+ buildFormat,
+ createWatchMode,
+} from '../../../esbuild.config.mjs';
+import tailwindPlugin from 'esbuild-plugin-tailwindcss';
+
+const isStart = process.argv.includes('--start');
+const isWatch = process.argv.includes('--watch');
+
+const baseConfig = createBaseConfig({
+ entryPoints: ['src/index.ts'],
+ external: [
+ 'react',
+ 'react-dom',
+ 'reactflow',
+ 'clsx',
+ 'tailwindcss',
+ 'zod',
+ 'zod-to-json-schema',
+ '@openassistant/utils',
+ '@openassistant/duckdb',
+ '@openassistant/geoda',
+ '@openassistant/h3',
+ '@openassistant/map',
+ '@openassistant/osm',
+ '@openassistant/places',
+ '@openassistant/plots',
+ ],
+ loader: {
+ '.js': 'jsx',
+ '.ts': 'tsx',
+ '.css': 'css',
+ '.svg': 'file',
+ '.png': 'file',
+ },
+ jsx: 'automatic',
+ plugins: [
+ tailwindPlugin({
+ config: './tailwind.config.js',
+ }),
+ ],
+ define: {
+ 'process.env.NODE_ENV': isStart ? '"development"' : '"production"',
+ },
+ mainFields: ['module', 'main'],
+ resolveExtensions: ['.js', '.jsx', '.ts', '.tsx'],
+ nodePaths: ['node_modules'],
+});
+
+if (isWatch) {
+ const esmConfig = {
+ ...baseConfig,
+ format: 'esm',
+ outfile: 'dist/index.esm.js',
+ };
+ const cjsConfig = {
+ ...baseConfig,
+ format: 'cjs',
+ outfile: 'dist/index.cjs.js',
+ platform: 'node',
+ target: ['es2017'],
+ };
+
+ await createWatchMode(esmConfig);
+ await createWatchMode(cjsConfig);
+} else {
+ Promise.all([
+ buildFormat(baseConfig, 'esm', 'dist/index.esm.js'),
+ buildFormat(baseConfig, 'cjs', 'dist/index.cjs.js'),
+ ]).catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
+}
diff --git a/packages/components/agent-builder/package.json b/packages/components/agent-builder/package.json
new file mode 100644
index 00000000..0bb0febc
--- /dev/null
+++ b/packages/components/agent-builder/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "@openassistant/agent-builder",
+ "version": "1.0.0-alpha.0",
+ "author": "Xun Li",
+ "description": "Visual agent workflow builder for OpenAssistant tools",
+ "main": "./dist/index.cjs.js",
+ "types": "./dist/index.d.ts",
+ "module": "./dist/index.esm.js",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.esm.js",
+ "require": "./dist/index.cjs.js"
+ },
+ "./dist/index.css": "./dist/index.esm.css",
+ "./dist/index.cjs.js": "./dist/index.cjs.js",
+ "./dist/index.esm.js": "./dist/index.esm.js"
+ },
+ "scripts": {
+ "build": "node ../../../node_modules/typescript/bin/tsc -p tsconfig.json && node esbuild.config.mjs",
+ "watch": "node esbuild.config.mjs --watch",
+ "prepublishOnly": "yarn build",
+ "lint": "eslint src --ext .js,.jsx,.ts,.tsx"
+ },
+ "keywords": [
+ "agent",
+ "workflow",
+ "react-flow",
+ "openassistant"
+ ],
+ "license": "MIT",
+ "files": [
+ "dist",
+ "src",
+ "README.md",
+ "package.json"
+ ],
+ "dependencies": {
+ "@openassistant/duckdb": "workspace:*",
+ "@openassistant/geoda": "workspace:*",
+ "@openassistant/h3": "workspace:*",
+ "@openassistant/map": "workspace:*",
+ "@openassistant/osm": "workspace:*",
+ "@openassistant/places": "workspace:*",
+ "@openassistant/plots": "workspace:*",
+ "@openassistant/utils": "workspace:*",
+ "clsx": "^2.1.1",
+ "reactflow": "^11.10.4",
+ "tailwindcss": "^3.4.17",
+ "zod": "^3.25.0",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.2",
+ "react-dom": ">=18.2"
+ },
+ "devDependencies": {
+ "esbuild-plugin-tailwindcss": "^1.2.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "gitHead": "232930873ca397af1dbaa234a00c5d27dba29a26"
+}
diff --git a/packages/components/agent-builder/src/AgentBuilder.tsx b/packages/components/agent-builder/src/AgentBuilder.tsx
new file mode 100644
index 00000000..bd895167
--- /dev/null
+++ b/packages/components/agent-builder/src/AgentBuilder.tsx
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import {
+ useCallback,
+ useMemo,
+ useRef,
+ useState,
+ type DragEvent,
+ type MouseEvent,
+} from 'react';
+import ReactFlow, {
+ addEdge,
+ Background,
+ Connection,
+ Controls,
+ MarkerType,
+ MiniMap,
+ type Edge,
+ type ReactFlowInstance,
+ type XYPosition,
+ useEdgesState,
+ useNodesState,
+} from 'reactflow';
+import 'reactflow/dist/style.css';
+import './index.css';
+import clsx from 'clsx';
+import { generateId } from '@openassistant/utils';
+import { ToolNode } from './components/ToolNode';
+import { MetaNode } from './components/MetaNode';
+import { AgentBuilderProvider, createNodeDataUpdater } from './context/AgentBuilderContext';
+import { buildAgentSchema } from './utils/schema';
+import { defaultToolRegistry } from './utils/tool-registry';
+import { DEFAULT_CUSTOM_NODES } from './constants';
+import type {
+ AgentBuilderNode,
+ AgentBuilderNodeData,
+ AgentBuilderProps,
+ AgentBuilderSchema,
+ AgentBuilderTool,
+ AgentMetaNodeType,
+ CustomNodeDefinition,
+ DraggedNodeBlueprint,
+ ToolNodeData,
+ MetaNodeData,
+} from './types';
+
+const nodeTypes = {
+ toolNode: ToolNode,
+ metaNode: MetaNode,
+} as const;
+
+const APPLICATION_MIME = 'application/reactflow';
+
+const normalizeTools = (tools: AgentBuilderTool[]) => {
+ return tools.map((tool) => ({
+ ...tool,
+ label: tool.tool.name,
+ }));
+};
+
+const createNodeFromBlueprint = (
+ blueprint: DraggedNodeBlueprint,
+ position: XYPosition,
+ tools: AgentBuilderTool[],
+ customNodes: CustomNodeDefinition[],
+ currentNodes: AgentBuilderNode[]
+): AgentBuilderNode | null => {
+ if (blueprint.kind === 'tool') {
+ const tool = tools.find((item) => item.id === blueprint.toolId);
+ if (!tool) {
+ return null;
+ }
+ return {
+ id: `tool_${tool.id}_${generateId()}`,
+ type: 'toolNode',
+ position,
+ data: {
+ label: tool.tool.name,
+ description: tool.shortDescription ?? tool.tool.description,
+ category: tool.category,
+ tool: tool.tool,
+ } satisfies ToolNodeData,
+ };
+ }
+
+ const definition = customNodes.find(
+ (node) => node.type === blueprint.metaType
+ );
+
+ if (!definition) {
+ return null;
+ }
+
+ const alreadyExists = currentNodes.some(
+ (node) =>
+ node.type === 'metaNode' &&
+ (node.data as MetaNodeData).type === definition.type
+ );
+
+ if (alreadyExists) {
+ return null;
+ }
+
+ return {
+ id: `meta_${definition.type}_${generateId()}`,
+ type: 'metaNode',
+ position,
+ data: {
+ label: definition.label,
+ description: definition.description,
+ placeholder: definition.placeholder,
+ type: definition.type,
+ value: '',
+ } satisfies MetaNodeData,
+ };
+};
+
+const useGroupedTools = (tools: AgentBuilderTool[], search: string) => {
+ return useMemo(() => {
+ const lowered = search.toLowerCase();
+ const filtered = tools.filter((tool) => {
+ if (!lowered) {
+ return true;
+ }
+ return (
+ tool.tool.name.toLowerCase().includes(lowered) ||
+ tool.tool.description.toLowerCase().includes(lowered)
+ );
+ });
+ const groups = filtered.reduce>(
+ (acc, tool) => {
+ if (!acc[tool.category]) {
+ acc[tool.category] = [];
+ }
+ acc[tool.category].push(tool);
+ return acc;
+ },
+ {}
+ );
+
+ return Object.entries(groups)
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(([category, groupedTools]) => ({
+ category,
+ tools: groupedTools.sort((a, b) =>
+ a.tool.name.localeCompare(b.tool.name)
+ ),
+ }));
+ }, [tools, search]);
+};
+
+export const AgentBuilder = ({
+ tools = defaultToolRegistry,
+ customNodes = DEFAULT_CUSTOM_NODES,
+ initialNodes = [],
+ initialEdges = [],
+ onCreateAgent,
+}: AgentBuilderProps) => {
+ const normalizedTools = useMemo(() => normalizeTools(tools), [tools]);
+ const [nodes, setNodes, onNodesChange] =
+ useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+ const [search, setSearch] = useState('');
+ const [schemaPreview, setSchemaPreview] = useState(
+ null
+ );
+ const [errorMessage, setErrorMessage] = useState(null);
+ const reactFlowWrapper = useRef(null);
+ const [reactFlowInstance, setReactFlowInstance] =
+ useState(null);
+ const groupedTools = useGroupedTools(normalizedTools, search);
+
+ const onConnect = useCallback(
+ (connection: Connection | Edge) => {
+ setEdges((current) =>
+ addEdge(
+ {
+ ...connection,
+ animated: true,
+ style: { stroke: '#6366f1', strokeWidth: 2 },
+ },
+ current
+ )
+ );
+ },
+ [setEdges]
+ );
+
+ const onDragOver = useCallback((event: DragEvent) => {
+ event.preventDefault();
+ event.dataTransfer.dropEffect = 'move';
+ }, []);
+
+ const onDrop = useCallback(
+ (event: DragEvent) => {
+ event.preventDefault();
+ if (!reactFlowWrapper.current || !reactFlowInstance) {
+ return;
+ }
+ const raw = event.dataTransfer.getData(APPLICATION_MIME);
+ if (!raw) {
+ return;
+ }
+
+ let blueprint: DraggedNodeBlueprint | null = null;
+ try {
+ blueprint = JSON.parse(raw) as DraggedNodeBlueprint;
+ } catch {
+ return;
+ }
+
+ const bounds = reactFlowWrapper.current.getBoundingClientRect();
+ const position = reactFlowInstance.project({
+ x: event.clientX - bounds.left,
+ y: event.clientY - bounds.top,
+ });
+ const newNode = createNodeFromBlueprint(
+ blueprint,
+ position,
+ normalizedTools,
+ customNodes,
+ nodes
+ );
+
+ if (!newNode) {
+ setErrorMessage(
+ blueprint.kind === 'meta'
+ ? 'This custom node already exists.'
+ : 'Unable to add this node.'
+ );
+ return;
+ }
+
+ setErrorMessage(null);
+ setNodes((current) => current.concat(newNode));
+ },
+ [
+ customNodes,
+ normalizedTools,
+ nodes,
+ reactFlowInstance,
+ setNodes,
+ setErrorMessage,
+ ]
+ );
+
+ const handleDragStart =
+ (payload: DraggedNodeBlueprint) => (event: DragEvent) => {
+ event.dataTransfer.setData(APPLICATION_MIME, JSON.stringify(payload));
+ event.dataTransfer.effectAllowed = 'move';
+ };
+
+ const handleCreateAgent = useCallback(() => {
+ try {
+ const schema = buildAgentSchema(nodes, edges);
+ setSchemaPreview(schema);
+ setErrorMessage(null);
+ onCreateAgent?.(schema);
+ } catch (error) {
+ setSchemaPreview(null);
+ setErrorMessage(
+ error instanceof Error ? error.message : 'Unable to build agent schema.'
+ );
+ }
+ }, [edges, nodes, onCreateAgent]);
+
+ const updateNodeData = useMemo(
+ () => createNodeDataUpdater(setNodes),
+ [setNodes]
+ );
+
+ const isMetaNodeUsed = useCallback(
+ (type: AgentMetaNodeType) => {
+ return nodes.some(
+ (node) =>
+ node.type === 'metaNode' && (node.data as MetaNodeData).type === type
+ );
+ },
+ [nodes]
+ );
+
+ const handleNodeShortcut = (
+ payload: DraggedNodeBlueprint,
+ event: MouseEvent
+ ) => {
+ event.preventDefault();
+ if (!reactFlowInstance) {
+ return;
+ }
+ const position = reactFlowInstance.project({ x: 120, y: 80 });
+ const newNode = createNodeFromBlueprint(
+ payload,
+ position,
+ normalizedTools,
+ customNodes,
+ nodes
+ );
+ if (!newNode) {
+ setErrorMessage(
+ payload.kind === 'meta'
+ ? 'This custom node already exists.'
+ : 'Unable to add this node.'
+ );
+ return;
+ }
+ setErrorMessage(null);
+ setNodes((current) => current.concat(newNode));
+ };
+
+ const nodeShortcutHandler =
+ (payload: DraggedNodeBlueprint) => (event: MouseEvent) =>
+ handleNodeShortcut(payload, event);
+
+ return (
+
+
+
+
+
+
+ Agent Builder
+
+
+ Orchestrate OpenAssistant Tools
+
+
+ Drag tools & custom nodes to describe how the agent should
+ behave. Connect nodes to express execution order.
+
+
+
+
+
+ Search tools
+ setSearch(event.target.value)}
+ placeholder="Bubble chart, Moran scatter..."
+ className="mt-2 w-full rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-white placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/40"
+ />
+
+
+
+
+
+ {groupedTools.map(({ category, tools: categoryTools }) => (
+
+
+ {category}
+
+
+ {categoryTools.map((tool) => (
+
+ {tool.tool.name}
+
+ {tool.shortDescription}
+
+
+ ))}
+
+
+ ))}
+
+
+
+ Custom Nodes
+
+
+ {customNodes.map((node) => {
+ const disabled = isMetaNodeUsed(node.type);
+ return (
+
+ {node.label}
+ {node.description}
+
+ );
+ })}
+
+
+
+
+
+
+
+ Create Agent
+
+ {errorMessage && (
+
{errorMessage}
+ )}
+ {schemaPreview && (
+
+
+ {JSON.stringify(schemaPreview, null, 2)}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ node.type === 'metaNode' ? '#fbbf24' : '#6366f1'
+ }
+ nodeColor={(node) =>
+ node.type === 'metaNode' ? '#fef3c7' : '#eef2ff'
+ }
+ />
+
+
+
+
+
+
+ );
+};
diff --git a/packages/components/agent-builder/src/components/MetaNode.tsx b/packages/components/agent-builder/src/components/MetaNode.tsx
new file mode 100644
index 00000000..a1478102
--- /dev/null
+++ b/packages/components/agent-builder/src/components/MetaNode.tsx
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import type { NodeProps } from 'reactflow';
+import { Handle, Position } from 'reactflow';
+import type { MetaNodeData } from '../types';
+import { useAgentBuilder } from '../context/AgentBuilderContext';
+
+export const MetaNode = ({ id, data }: NodeProps) => {
+ const { updateNodeData } = useAgentBuilder();
+
+ return (
+
+
+
{data.label}
+
{data.description}
+
+
+ );
+};
diff --git a/packages/components/agent-builder/src/components/ToolNode.tsx b/packages/components/agent-builder/src/components/ToolNode.tsx
new file mode 100644
index 00000000..77c1f6aa
--- /dev/null
+++ b/packages/components/agent-builder/src/components/ToolNode.tsx
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import type { NodeProps } from 'reactflow';
+import { Handle, Position } from 'reactflow';
+import type { ToolNodeData } from '../types';
+
+export const ToolNode = ({ data }: NodeProps) => {
+ return (
+
+
+
{data.label}
+
+ {data.category}
+
+
+
{data.description}
+
+
+
+
+ );
+};
diff --git a/packages/components/agent-builder/src/constants.ts b/packages/components/agent-builder/src/constants.ts
new file mode 100644
index 00000000..946b79f7
--- /dev/null
+++ b/packages/components/agent-builder/src/constants.ts
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import type { CustomNodeDefinition } from './types';
+
+export const DEFAULT_CUSTOM_NODES: CustomNodeDefinition[] = [
+ {
+ type: 'agentName',
+ label: 'Agent Name',
+ description: 'Give the agent an identity so LLMs can reference it.',
+ placeholder: 'e.g. Spatial Planning Copilot',
+ },
+ {
+ type: 'agentDescription',
+ label: 'Agent Description',
+ description: 'Summarize what the workflow does and which tools it needs.',
+ placeholder: 'Describe the orchestration and expectations...',
+ },
+ {
+ type: 'agentResult',
+ label: 'Result Blueprint',
+ description: 'Clarify what output the agent should return to the caller.',
+ placeholder: 'Structured JSON object, markdown table, etc.',
+ },
+ {
+ type: 'agentError',
+ label: 'Error Guidance',
+ description: 'Document possible failures and how they should be surfaced.',
+ placeholder: 'List potential errors or fallback responses.',
+ },
+];
diff --git a/packages/components/agent-builder/src/context/AgentBuilderContext.tsx b/packages/components/agent-builder/src/context/AgentBuilderContext.tsx
new file mode 100644
index 00000000..4983130b
--- /dev/null
+++ b/packages/components/agent-builder/src/context/AgentBuilderContext.tsx
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import {
+ createContext,
+ useContext,
+ type PropsWithChildren,
+ type Dispatch,
+ type SetStateAction,
+} from 'react';
+import type { AgentBuilderNode } from '../types';
+
+type AgentBuilderContextValue = {
+ updateNodeData: (
+ nodeId: string,
+ updater: (data: AgentBuilderNode['data']) => AgentBuilderNode['data']
+ ) => void;
+};
+
+const AgentBuilderContext = createContext(null);
+
+type AgentBuilderProviderProps = PropsWithChildren<{
+ value: AgentBuilderContextValue;
+}>;
+
+export const AgentBuilderProvider = ({
+ children,
+ value,
+}: AgentBuilderProviderProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAgentBuilder = () => {
+ const ctx = useContext(AgentBuilderContext);
+ if (!ctx) {
+ throw new Error(
+ 'useAgentBuilder must be used within an AgentBuilderProvider.'
+ );
+ }
+ return ctx;
+};
+
+export const createNodeDataUpdater =
+ (
+ setNodes: Dispatch>
+ ) =>
+ (
+ nodeId: string,
+ updater: (data: AgentBuilderNode['data']) => AgentBuilderNode['data']
+ ) => {
+ setNodes((nodes) =>
+ nodes.map((node) => {
+ if (node.id !== nodeId) {
+ return node;
+ }
+ return {
+ ...node,
+ data: updater(node.data),
+ };
+ })
+ );
+ };
diff --git a/packages/components/agent-builder/src/index.css b/packages/components/agent-builder/src/index.css
new file mode 100644
index 00000000..6547119e
--- /dev/null
+++ b/packages/components/agent-builder/src/index.css
@@ -0,0 +1,21 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ color-scheme: dark;
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
+ sans-serif;
+}
+
+.agent-builder-grid {
+ background: radial-gradient(circle at top, #312e81 0%, #020617 60%);
+}
+
+.react-flow__edge-path {
+ stroke-linecap: round;
+}
+
+.react-flow__toolbar {
+ border-radius: 1rem;
+}
diff --git a/packages/components/agent-builder/src/index.ts b/packages/components/agent-builder/src/index.ts
new file mode 100644
index 00000000..91d1ec98
--- /dev/null
+++ b/packages/components/agent-builder/src/index.ts
@@ -0,0 +1,13 @@
+export { AgentBuilder } from './AgentBuilder';
+export type {
+ AgentBuilderProps,
+ AgentBuilderSchema,
+ AgentBuilderNode,
+ AgentBuilderEdge,
+ AgentBuilderTool,
+ CustomNodeDefinition,
+} from './types';
+export { DEFAULT_CUSTOM_NODES } from './constants';
+export { defaultToolRegistry } from './utils/tool-registry';
+export { buildAgentSchema } from './utils/schema';
+import './index.css';
diff --git a/packages/components/agent-builder/src/types.ts b/packages/components/agent-builder/src/types.ts
new file mode 100644
index 00000000..c476ce5f
--- /dev/null
+++ b/packages/components/agent-builder/src/types.ts
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import type { OpenAssistantTool } from '@openassistant/utils';
+import type { Node, Edge } from 'reactflow';
+import type { JsonSchema7Type } from 'zod-to-json-schema';
+
+export type AgentMetaNodeType = 'agentName' | 'agentDescription' | 'agentResult' | 'agentError';
+
+export type CustomNodeDefinition = {
+ type: AgentMetaNodeType;
+ label: string;
+ description: string;
+ placeholder: string;
+};
+
+export type AgentBuilderTool = {
+ id: string;
+ category: string;
+ group?: string;
+ tool: OpenAssistantTool;
+ shortDescription?: string;
+};
+
+export type ToolNodeData = {
+ label: string;
+ description: string;
+ category: string;
+ tool: OpenAssistantTool;
+};
+
+export type MetaNodeData = {
+ label: string;
+ description: string;
+ placeholder: string;
+ type: AgentMetaNodeType;
+ value: string;
+};
+
+export type AgentBuilderNodeData = ToolNodeData | MetaNodeData;
+export type AgentBuilderNode = Node;
+
+export type AgentBuilderEdge = Edge;
+
+export type AgentBuilderSchema = {
+ agent: {
+ name?: string;
+ description?: string;
+ result?: string;
+ error?: string;
+ };
+ workflow: {
+ order: string[];
+ edges: Array>;
+ adjacency: Record;
+ };
+ tools: Array<{
+ nodeId: string;
+ name: string;
+ description: string;
+ parametersSchema?: JsonSchema7Type;
+ }>;
+ customNodes: Array<{
+ nodeId: string;
+ type: AgentMetaNodeType;
+ value: string;
+ }>;
+};
+
+export type AgentBuilderProps = {
+ tools?: AgentBuilderTool[];
+ customNodes?: CustomNodeDefinition[];
+ initialNodes?: AgentBuilderNode[];
+ initialEdges?: AgentBuilderEdge[];
+ onCreateAgent?: (schema: AgentBuilderSchema) => void;
+};
+
+export type DraggedNodeBlueprint =
+ | {
+ kind: 'tool';
+ toolId: string;
+ }
+ | {
+ kind: 'meta';
+ metaType: AgentMetaNodeType;
+ };
diff --git a/packages/components/agent-builder/src/utils/schema.ts b/packages/components/agent-builder/src/utils/schema.ts
new file mode 100644
index 00000000..0d103258
--- /dev/null
+++ b/packages/components/agent-builder/src/utils/schema.ts
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import { zodToJsonSchema } from 'zod-to-json-schema';
+import type {
+ AgentBuilderEdge,
+ AgentBuilderNode,
+ AgentBuilderSchema,
+ AgentMetaNodeType,
+ MetaNodeData,
+ ToolNodeData,
+} from '../types';
+
+const META_KEY_TO_AGENT_FIELD: Record =
+ {
+ agentName: 'name',
+ agentDescription: 'description',
+ agentResult: 'result',
+ agentError: 'error',
+ };
+
+const mapToObject = (map: Map): Record => {
+ const output: Record = {};
+ map.forEach((value, key) => {
+ output[key] = value;
+ });
+ return output;
+};
+
+const buildAdjacency = (
+ nodes: AgentBuilderNode[],
+ edges: AgentBuilderEdge[]
+): Map => {
+ const adjacency = new Map();
+ nodes.forEach((node) => {
+ adjacency.set(node.id, []);
+ });
+
+ edges.forEach((edge) => {
+ const next = adjacency.get(edge.source);
+ if (next) {
+ next.push(edge.target);
+ }
+ });
+
+ return adjacency;
+};
+
+const createTopologicalOrder = (
+ nodes: AgentBuilderNode[],
+ edges: AgentBuilderEdge[]
+): { order: string[]; adjacency: Record } => {
+ const adjacency = buildAdjacency(nodes, edges);
+ const inDegree = new Map();
+ nodes.forEach((node) => {
+ inDegree.set(node.id, 0);
+ });
+
+ edges.forEach((edge) => {
+ inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1);
+ });
+
+ const queue: string[] = [];
+ inDegree.forEach((degree, nodeId) => {
+ if (degree === 0) {
+ queue.push(nodeId);
+ }
+ });
+
+ const order: string[] = [];
+ while (queue.length) {
+ const nodeId = queue.shift() as string;
+ order.push(nodeId);
+ adjacency.get(nodeId)?.forEach((nextId) => {
+ const updated = (inDegree.get(nextId) ?? 0) - 1;
+ inDegree.set(nextId, updated);
+ if (updated === 0) {
+ queue.push(nextId);
+ }
+ });
+ }
+
+ if (order.length !== nodes.length) {
+ throw new Error(
+ 'The workflow contains a cycle. Break the circular dependency to create an agent.'
+ );
+ }
+
+ return { order, adjacency: mapToObject(adjacency) };
+};
+
+const extractAgentMetadata = (metaNodes: AgentBuilderNode[]) => {
+ return metaNodes.reduce>((acc, node) => {
+ if (node.type !== 'metaNode') {
+ return acc;
+ }
+ const { type, value } = node.data as MetaNodeData;
+ acc[type] = value?.trim() ?? '';
+ return acc;
+ }, {} as Record);
+};
+
+const serializeToolNode = (node: AgentBuilderNode) => {
+ const data = node.data as ToolNodeData;
+ return {
+ nodeId: node.id,
+ name: data.tool.name,
+ description: data.tool.description,
+ parametersSchema: data.tool.parameters
+ ? (zodToJsonSchema(data.tool.parameters, {
+ target: 'jsonSchema7',
+ name: `${data.tool.name}Parameters`,
+ }) as AgentBuilderSchema['tools'][number]['parametersSchema'])
+ : undefined,
+ };
+};
+
+const serializeMetaNode = (node: AgentBuilderNode) => {
+ const data = node.data as MetaNodeData;
+ return {
+ nodeId: node.id,
+ type: data.type,
+ value: data.value,
+ };
+};
+
+export const buildAgentSchema = (
+ nodes: AgentBuilderNode[],
+ edges: AgentBuilderEdge[]
+): AgentBuilderSchema => {
+ if (!nodes.length) {
+ throw new Error('Add at least one node to describe the agent.');
+ }
+
+ const { order, adjacency } = createTopologicalOrder(nodes, edges);
+ const metaNodes = nodes.filter((node) => node.type === 'metaNode');
+ const toolNodes = nodes.filter((node) => node.type === 'toolNode');
+ const metadata = extractAgentMetadata(metaNodes);
+
+ const agent: AgentBuilderSchema['agent'] = Object.entries(
+ META_KEY_TO_AGENT_FIELD
+ ).reduce((acc, [metaKey, fieldKey]) => {
+ const key = metaKey as AgentMetaNodeType;
+ acc[fieldKey] = metadata[key];
+ return acc;
+ }, {} as AgentBuilderSchema['agent']);
+
+ return {
+ agent,
+ workflow: {
+ order,
+ edges: edges.map(({ id, source, target }) => ({ id, source, target })),
+ adjacency,
+ },
+ tools: toolNodes.map(serializeToolNode),
+ customNodes: metaNodes.map(serializeMetaNode),
+ };
+};
diff --git a/packages/components/agent-builder/src/utils/tool-registry.ts b/packages/components/agent-builder/src/utils/tool-registry.ts
new file mode 100644
index 00000000..83b00a65
--- /dev/null
+++ b/packages/components/agent-builder/src/utils/tool-registry.ts
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+import type { OpenAssistantTool } from '@openassistant/utils';
+import * as DuckdbTools from '@openassistant/duckdb';
+import * as GeodaTools from '@openassistant/geoda';
+import * as H3Tools from '@openassistant/h3';
+import * as MapTools from '@openassistant/map';
+import * as OsmTools from '@openassistant/osm';
+import * as PlacesTools from '@openassistant/places';
+import * as PlotTools from '@openassistant/plots';
+import type { AgentBuilderTool } from '../types';
+
+type ToolModule = {
+ category: string;
+ module: Record;
+};
+
+const MODULES: ToolModule[] = [
+ { category: 'DuckDB', module: DuckdbTools },
+ { category: 'Spatial Analysis', module: GeodaTools },
+ { category: 'H3', module: H3Tools },
+ { category: 'Mapping', module: MapTools },
+ { category: 'OSM', module: OsmTools },
+ { category: 'Places', module: PlacesTools },
+ { category: 'Visualization', module: PlotTools },
+];
+
+const isOpenAssistantTool = (
+ candidate: unknown
+): candidate is OpenAssistantTool => {
+ return (
+ typeof candidate === 'object' &&
+ candidate !== null &&
+ 'name' in candidate &&
+ 'description' in candidate &&
+ 'parameters' in candidate &&
+ 'execute' in candidate &&
+ typeof (candidate as OpenAssistantTool).execute === 'function'
+ );
+};
+
+export const defaultToolRegistry: AgentBuilderTool[] = (() => {
+ const deduped = new Map();
+
+ MODULES.forEach(({ category, module }) => {
+ Object.values(module)
+ .filter(isOpenAssistantTool)
+ .forEach((tool) => {
+ if (!deduped.has(tool.name)) {
+ deduped.set(tool.name, {
+ id: tool.name,
+ category,
+ tool,
+ shortDescription: tool.description,
+ });
+ }
+ });
+ });
+
+ return Array.from(deduped.values()).sort((a, b) => {
+ if (a.category === b.category) {
+ return a.tool.name.localeCompare(b.tool.name);
+ }
+ return a.category.localeCompare(b.category);
+ });
+})();
diff --git a/packages/components/agent-builder/tailwind.config.js b/packages/components/agent-builder/tailwind.config.js
new file mode 100644
index 00000000..c2dc7068
--- /dev/null
+++ b/packages/components/agent-builder/tailwind.config.js
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: MIT
+// Copyright contributors to the openassistant project
+
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./src/**/*.{ts,tsx}'],
+ theme: {
+ extend: {
+ colors: {
+ slate: {
+ 950: '#020617',
+ },
+ },
+ },
+ },
+ plugins: [],
+};
diff --git a/packages/components/agent-builder/tsconfig.json b/packages/components/agent-builder/tsconfig.json
new file mode 100644
index 00000000..fa942ec9
--- /dev/null
+++ b/packages/components/agent-builder/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "composite": true,
+ "jsx": "react-jsx",
+ "lib": ["dom", "es2020"],
+ "outDir": "dist",
+ "baseUrl": ".",
+ "rootDir": "src",
+ "declaration": true,
+ "declarationMap": false,
+ "declarationDir": "dist",
+ "emitDeclarationOnly": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"],
+ "exclude": [
+ "node_modules",
+ "../../../node_modules",
+ "dist",
+ "**/*.test.ts",
+ "**/*.test.tsx",
+ "**/*.stories.tsx"
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index f425add2..3f40c7eb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6039,6 +6039,30 @@ __metadata:
languageName: node
linkType: hard
+"@openassistant/agent-builder@workspace:packages/components/agent-builder":
+ version: 0.0.0-use.local
+ resolution: "@openassistant/agent-builder@workspace:packages/components/agent-builder"
+ dependencies:
+ "@openassistant/duckdb": "workspace:*"
+ "@openassistant/geoda": "workspace:*"
+ "@openassistant/h3": "workspace:*"
+ "@openassistant/map": "workspace:*"
+ "@openassistant/osm": "workspace:*"
+ "@openassistant/places": "workspace:*"
+ "@openassistant/plots": "workspace:*"
+ "@openassistant/utils": "workspace:*"
+ clsx: "npm:^2.1.1"
+ esbuild-plugin-tailwindcss: "npm:^1.2.1"
+ reactflow: "npm:^11.10.4"
+ tailwindcss: "npm:^3.4.17"
+ zod: "npm:^3.25.0"
+ zod-to-json-schema: "npm:^3.24.1"
+ peerDependencies:
+ react: ">=18.2"
+ react-dom: ">=18.2"
+ languageName: unknown
+ linkType: soft
+
"@openassistant/assistant@workspace:*, @openassistant/assistant@workspace:packages/components/assistant":
version: 0.0.0-use.local
resolution: "@openassistant/assistant@workspace:packages/components/assistant"
@@ -6158,7 +6182,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@openassistant/duckdb@workspace:packages/tools/duckdb":
+"@openassistant/duckdb@workspace:*, @openassistant/duckdb@workspace:packages/tools/duckdb":
version: 0.0.0-use.local
resolution: "@openassistant/duckdb@workspace:packages/tools/duckdb"
dependencies:
@@ -6191,7 +6215,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@openassistant/geoda@workspace:packages/tools/geoda":
+"@openassistant/geoda@workspace:*, @openassistant/geoda@workspace:packages/tools/geoda":
version: 0.0.0-use.local
resolution: "@openassistant/geoda@workspace:packages/tools/geoda"
dependencies:
@@ -6210,7 +6234,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@openassistant/h3@workspace:packages/tools/h3":
+"@openassistant/h3@workspace:*, @openassistant/h3@workspace:packages/tools/h3":
version: 0.0.0-use.local
resolution: "@openassistant/h3@workspace:packages/tools/h3"
dependencies:
@@ -6287,7 +6311,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@openassistant/osm@workspace:packages/tools/osm":
+"@openassistant/osm@workspace:*, @openassistant/osm@workspace:packages/tools/osm":
version: 0.0.0-use.local
resolution: "@openassistant/osm@workspace:packages/tools/osm"
dependencies:
@@ -6297,7 +6321,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@openassistant/places@workspace:packages/tools/places":
+"@openassistant/places@workspace:*, @openassistant/places@workspace:packages/tools/places":
version: 0.0.0-use.local
resolution: "@openassistant/places@workspace:packages/tools/places"
dependencies:
@@ -9520,6 +9544,102 @@ __metadata:
languageName: node
linkType: hard
+"@reactflow/background@npm:11.3.14":
+ version: 11.3.14
+ resolution: "@reactflow/background@npm:11.3.14"
+ dependencies:
+ "@reactflow/core": "npm:11.11.4"
+ classcat: "npm:^5.0.3"
+ zustand: "npm:^4.4.1"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: bf6c5c7eaabc72371ae882407299b130ed56c538a60e460a3aa305c8d0ba5a51b1f3cbf244e0c1768ee85324ce2c8fd23eeaf0ece45b570197dacb6348a1e843
+ languageName: node
+ linkType: hard
+
+"@reactflow/controls@npm:11.2.14":
+ version: 11.2.14
+ resolution: "@reactflow/controls@npm:11.2.14"
+ dependencies:
+ "@reactflow/core": "npm:11.11.4"
+ classcat: "npm:^5.0.3"
+ zustand: "npm:^4.4.1"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: 5ab2f4ee4daf43f22bae18b3d20e9804c8797a32f194b7fd0bf3d90c17f51db0fd09ba34c31408528ae809530dcdf2fde4d44bc044b1aa41ff0eabd972f52505
+ languageName: node
+ linkType: hard
+
+"@reactflow/core@npm:11.11.4":
+ version: 11.11.4
+ resolution: "@reactflow/core@npm:11.11.4"
+ dependencies:
+ "@types/d3": "npm:^7.4.0"
+ "@types/d3-drag": "npm:^3.0.1"
+ "@types/d3-selection": "npm:^3.0.3"
+ "@types/d3-zoom": "npm:^3.0.1"
+ classcat: "npm:^5.0.3"
+ d3-drag: "npm:^3.0.0"
+ d3-selection: "npm:^3.0.0"
+ d3-zoom: "npm:^3.0.0"
+ zustand: "npm:^4.4.1"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: 725a876356f44c0efccb43212beaf2e182481aabfb7b5a139926366c1b97e20cec5ad88cf392a7e0a6be3998422c7719cadfc2ab53ec6ff61fa792a36e899599
+ languageName: node
+ linkType: hard
+
+"@reactflow/minimap@npm:11.7.14":
+ version: 11.7.14
+ resolution: "@reactflow/minimap@npm:11.7.14"
+ dependencies:
+ "@reactflow/core": "npm:11.11.4"
+ "@types/d3-selection": "npm:^3.0.3"
+ "@types/d3-zoom": "npm:^3.0.1"
+ classcat: "npm:^5.0.3"
+ d3-selection: "npm:^3.0.0"
+ d3-zoom: "npm:^3.0.0"
+ zustand: "npm:^4.4.1"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: 3aa9bbfbe2fcbd874b8b41fe3ddb5d221f90f58d17d093c0d5edbfafb540d0dc4cd9785e8802eb18db5bdd730d5705234ab65bd31cfbb71eabf42e49bf2f7cac
+ languageName: node
+ linkType: hard
+
+"@reactflow/node-resizer@npm:2.2.14":
+ version: 2.2.14
+ resolution: "@reactflow/node-resizer@npm:2.2.14"
+ dependencies:
+ "@reactflow/core": "npm:11.11.4"
+ classcat: "npm:^5.0.4"
+ d3-drag: "npm:^3.0.0"
+ d3-selection: "npm:^3.0.0"
+ zustand: "npm:^4.4.1"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: 0440dd32c6364304f92b12a2d8288260d66a825533b99b7705c112362858c676b0a0221adf983bfdbbacbd70388dfcaec69401129e60bfca5c25c9b8302b0aef
+ languageName: node
+ linkType: hard
+
+"@reactflow/node-toolbar@npm:1.3.14":
+ version: 1.3.14
+ resolution: "@reactflow/node-toolbar@npm:1.3.14"
+ dependencies:
+ "@reactflow/core": "npm:11.11.4"
+ classcat: "npm:^5.0.3"
+ zustand: "npm:^4.4.1"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: 159566da042f29c15e506d4c49cd938b28644ad7710b266e35293c268286c48488b92d7c621f25700bfbabff84470c181ec2683a44c04d1749370c09d2e4a12b
+ languageName: node
+ linkType: hard
+
"@reduxjs/toolkit@npm:^1.7.2":
version: 1.9.7
resolution: "@reduxjs/toolkit@npm:1.9.7"
@@ -11148,6 +11268,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/d3-array@npm:*, @types/d3-array@npm:^3.0.3":
+ version: 3.2.2
+ resolution: "@types/d3-array@npm:3.2.2"
+ checksum: 1afebd05b688cafaaea295f765b409789f088b274b8a7ca40a4bc2b79760044a898e06a915f40bbc59cf39eabdd2b5d32e960b136fc025fd05c9a9d4435614c6
+ languageName: node
+ linkType: hard
+
"@types/d3-array@npm:^2.8.0":
version: 2.12.7
resolution: "@types/d3-array@npm:2.12.7"
@@ -11162,14 +11289,16 @@ __metadata:
languageName: node
linkType: hard
-"@types/d3-array@npm:^3.0.3":
- version: 3.2.2
- resolution: "@types/d3-array@npm:3.2.2"
- checksum: 1afebd05b688cafaaea295f765b409789f088b274b8a7ca40a4bc2b79760044a898e06a915f40bbc59cf39eabdd2b5d32e960b136fc025fd05c9a9d4435614c6
+"@types/d3-axis@npm:*":
+ version: 3.0.6
+ resolution: "@types/d3-axis@npm:3.0.6"
+ dependencies:
+ "@types/d3-selection": "npm:*"
+ checksum: 8af56b629a0597ac8ef5051b6ad5390818462d8e588e1b52fb181808b1c0525d12a658730fad757e1ae256d0db170a0e29076acdef21acc98b954608d1c37b84
languageName: node
linkType: hard
-"@types/d3-brush@npm:^3.0.1":
+"@types/d3-brush@npm:*, @types/d3-brush@npm:^3.0.1":
version: 3.0.6
resolution: "@types/d3-brush@npm:3.0.6"
dependencies:
@@ -11178,6 +11307,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/d3-chord@npm:*":
+ version: 3.0.6
+ resolution: "@types/d3-chord@npm:3.0.6"
+ checksum: ca9ba8b00debd24a2b51527b9c3db63eafa5541c08dc721d1c52ca19960c5cec93a7b1acfc0ec072dbca31d134924299755e20a4d1d4ee04b961fc0de841b418
+ languageName: node
+ linkType: hard
+
"@types/d3-color@npm:*":
version: 3.1.3
resolution: "@types/d3-color@npm:3.1.3"
@@ -11185,14 +11321,93 @@ __metadata:
languageName: node
linkType: hard
-"@types/d3-ease@npm:^3.0.0":
+"@types/d3-contour@npm:*":
+ version: 3.0.6
+ resolution: "@types/d3-contour@npm:3.0.6"
+ dependencies:
+ "@types/d3-array": "npm:*"
+ "@types/geojson": "npm:*"
+ checksum: e7b7e3972aa71003c21f2c864116ffb95a9175a62ec56ec656a855e5198a66a0830b2ad7fc26811214cfa8c98cdf4190d7d351913ca0913f799fbcf2a4c99b2d
+ languageName: node
+ linkType: hard
+
+"@types/d3-delaunay@npm:*":
+ version: 6.0.4
+ resolution: "@types/d3-delaunay@npm:6.0.4"
+ checksum: cb8d2c9ed0b39ade3107b9792544a745b2de3811a6bd054813e9dc708b1132fbacd796e54c0602c11b3a14458d14487c5276c1affb7c2b9f25fe55fff88d6d25
+ languageName: node
+ linkType: hard
+
+"@types/d3-dispatch@npm:*":
+ version: 3.0.7
+ resolution: "@types/d3-dispatch@npm:3.0.7"
+ checksum: ce7ab5a7d5c64aacf563797c0c61f3862b9ff687cb35470fe462219f09e402185646f51707339beede616586d92ded6974c3958dbeb15e35a85b1ecfafdf13a8
+ languageName: node
+ linkType: hard
+
+"@types/d3-drag@npm:*, @types/d3-drag@npm:^3.0.1":
+ version: 3.0.7
+ resolution: "@types/d3-drag@npm:3.0.7"
+ dependencies:
+ "@types/d3-selection": "npm:*"
+ checksum: 93aba299c3a8d41ee326c5304ab694ceea135ed115c3b2ccab727a5d9bfc935f7f36d3fc416c013010eb755ac536c52adfcb15c195f241dc61f62650cc95088e
+ languageName: node
+ linkType: hard
+
+"@types/d3-dsv@npm:*":
+ version: 3.0.7
+ resolution: "@types/d3-dsv@npm:3.0.7"
+ checksum: 8507f542135cae472781dff1c3b391eceedad0f2032d24ac4a0814e72e2f6877e4ddcb66f44627069977ee61029dc0a729edf659ed73cbf1040f55a7451f05ef
+ languageName: node
+ linkType: hard
+
+"@types/d3-ease@npm:*, @types/d3-ease@npm:^3.0.0":
version: 3.0.2
resolution: "@types/d3-ease@npm:3.0.2"
checksum: d8f92a8a7a008da71f847a16227fdcb53a8938200ecdf8d831ab6b49aba91e8921769761d3bfa7e7191b28f62783bfd8b0937e66bae39d4dd7fb0b63b50d4a94
languageName: node
linkType: hard
-"@types/d3-interpolate@npm:^3.0.1":
+"@types/d3-fetch@npm:*":
+ version: 3.0.7
+ resolution: "@types/d3-fetch@npm:3.0.7"
+ dependencies:
+ "@types/d3-dsv": "npm:*"
+ checksum: d496475cec7750f75740936e750a0150ca45e924a4f4697ad2c564f3a8f6c4ebc1b1edf8e081936e896532516731dbbaf2efd4890d53274a8eae13f51f821557
+ languageName: node
+ linkType: hard
+
+"@types/d3-force@npm:*":
+ version: 3.0.10
+ resolution: "@types/d3-force@npm:3.0.10"
+ checksum: 9c35abed2af91b94fc72d6b477188626e628ed89a01016437502c1deaf558da934b5d0cc808c2f2979ac853b6302b3d6ef763eddaff3a55552a55c0be710d5ca
+ languageName: node
+ linkType: hard
+
+"@types/d3-format@npm:*":
+ version: 3.0.4
+ resolution: "@types/d3-format@npm:3.0.4"
+ checksum: b937ecd2712d4aa38d5b4f5daab9cc8a576383868be1809e046aec99eeb1f1798c139f2e862dc400a82494c763be46087d154891773417f8eb53c73762ba3eb8
+ languageName: node
+ linkType: hard
+
+"@types/d3-geo@npm:*":
+ version: 3.1.0
+ resolution: "@types/d3-geo@npm:3.1.0"
+ dependencies:
+ "@types/geojson": "npm:*"
+ checksum: e759d98470fe605ff0088247af81c3197cefce72b16eafe8acae606216c3e0a9f908df4e7cd5005ecfe13b8ac8396a51aaa0d282f3ca7d1c3850313a13fac905
+ languageName: node
+ linkType: hard
+
+"@types/d3-hierarchy@npm:*":
+ version: 3.1.7
+ resolution: "@types/d3-hierarchy@npm:3.1.7"
+ checksum: 9ff6cdedf5557ef9e1e7a65ca3c6846c895c84c1184e11ec6fa48565e96ebf5482d8be5cc791a8bc7f7debbd0e62604ee3da3ddca4f9d58bf6c8b4030567c6c6
+ languageName: node
+ linkType: hard
+
+"@types/d3-interpolate@npm:*, @types/d3-interpolate@npm:^3.0.1":
version: 3.0.4
resolution: "@types/d3-interpolate@npm:3.0.4"
dependencies:
@@ -11208,16 +11423,35 @@ __metadata:
languageName: node
linkType: hard
-"@types/d3-scale@npm:^3.2.2":
- version: 3.3.5
- resolution: "@types/d3-scale@npm:3.3.5"
- dependencies:
- "@types/d3-time": "npm:^2"
- checksum: 5e0d95ca15297b05301a329ddf36a90ee6ea6187f4bd8db175647773514d7adedbe05b790ffdf0a88c4fbae2c817300610fe694ad554e8981f434596c123ac26
+"@types/d3-polygon@npm:*":
+ version: 3.0.2
+ resolution: "@types/d3-polygon@npm:3.0.2"
+ checksum: 7cf1eadb54f02dd3617512b558f4c0f3811f8a6a8c887d9886981c3cc251db28b68329b2b0707d9f517231a72060adbb08855227f89bef6ef30caedc0a67cab2
languageName: node
linkType: hard
-"@types/d3-scale@npm:^4.0.2":
+"@types/d3-quadtree@npm:*":
+ version: 3.0.6
+ resolution: "@types/d3-quadtree@npm:3.0.6"
+ checksum: 4c260c9857d496b7f112cf57680c411c1912cc72538a5846c401429e3ed89a097c66410cfd38b394bfb4733ec2cb47d345b4eb5e202cbfb8e78ab044b535be02
+ languageName: node
+ linkType: hard
+
+"@types/d3-random@npm:*":
+ version: 3.0.3
+ resolution: "@types/d3-random@npm:3.0.3"
+ checksum: 2c126dda6846f6c7e02c9123a30b4cdf27f3655d19b78456bbb330fbac27acceeeb987318055d3964dba8e6450377ff737db91d81f27c81ca6f4522c9b994ef2
+ languageName: node
+ linkType: hard
+
+"@types/d3-scale-chromatic@npm:*":
+ version: 3.1.0
+ resolution: "@types/d3-scale-chromatic@npm:3.1.0"
+ checksum: 6b04af931b7cd4aa09f21519970cab44aaae181faf076013ab93ccb0d550ec16f4c8d444c1e9dee1493be4261a8a8bb6f8e6356e6f4c6ba0650011b1e8a38aef
+ languageName: node
+ linkType: hard
+
+"@types/d3-scale@npm:*, @types/d3-scale@npm:^4.0.2":
version: 4.0.9
resolution: "@types/d3-scale@npm:4.0.9"
dependencies:
@@ -11226,14 +11460,23 @@ __metadata:
languageName: node
linkType: hard
-"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.2":
+"@types/d3-scale@npm:^3.2.2":
+ version: 3.3.5
+ resolution: "@types/d3-scale@npm:3.3.5"
+ dependencies:
+ "@types/d3-time": "npm:^2"
+ checksum: 5e0d95ca15297b05301a329ddf36a90ee6ea6187f4bd8db175647773514d7adedbe05b790ffdf0a88c4fbae2c817300610fe694ad554e8981f434596c123ac26
+ languageName: node
+ linkType: hard
+
+"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.2, @types/d3-selection@npm:^3.0.3":
version: 3.0.11
resolution: "@types/d3-selection@npm:3.0.11"
checksum: 2d2d993b9e9553d066566cb22916c632e5911090db99e247bd8c32855a344e6b7c25b674f3c27956c367a6b3b1214b09931ce854788c3be2072003e01f2c75d7
languageName: node
linkType: hard
-"@types/d3-shape@npm:^3.1.0":
+"@types/d3-shape@npm:*, @types/d3-shape@npm:^3.1.0":
version: 3.1.7
resolution: "@types/d3-shape@npm:3.1.7"
dependencies:
@@ -11242,6 +11485,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/d3-time-format@npm:*":
+ version: 4.0.3
+ resolution: "@types/d3-time-format@npm:4.0.3"
+ checksum: 9dfc1516502ac1c657d6024bdb88b6dc7e21dd7bff88f6187616cf9a0108250f63507a2004901ece4f97cc46602005a2ca2d05c6dbe53e8a0f6899bd60d4ff7a
+ languageName: node
+ linkType: hard
+
"@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0":
version: 3.0.4
resolution: "@types/d3-time@npm:3.0.4"
@@ -11256,13 +11506,70 @@ __metadata:
languageName: node
linkType: hard
-"@types/d3-timer@npm:^3.0.0":
+"@types/d3-timer@npm:*, @types/d3-timer@npm:^3.0.0":
version: 3.0.2
resolution: "@types/d3-timer@npm:3.0.2"
checksum: 1643eebfa5f4ae3eb00b556bbc509444d88078208ec2589ddd8e4a24f230dd4cf2301e9365947e70b1bee33f63aaefab84cd907822aae812b9bc4871b98ab0e1
languageName: node
linkType: hard
+"@types/d3-transition@npm:*":
+ version: 3.0.9
+ resolution: "@types/d3-transition@npm:3.0.9"
+ dependencies:
+ "@types/d3-selection": "npm:*"
+ checksum: dad647c485440f176117e8a45f31aee9427d8d4dfa174eaa2f01e702641db53ad0f752a144b20987c7189723c4f0afe0bf0f16d95b2a91aa28937eee4339c161
+ languageName: node
+ linkType: hard
+
+"@types/d3-zoom@npm:*, @types/d3-zoom@npm:^3.0.1":
+ version: 3.0.8
+ resolution: "@types/d3-zoom@npm:3.0.8"
+ dependencies:
+ "@types/d3-interpolate": "npm:*"
+ "@types/d3-selection": "npm:*"
+ checksum: cc6ba975cf4f55f94933413954d81b87feb1ee8b8cee8f2202cf526f218dcb3ba240cbeb04ed80522416201c4a7394b37de3eb695d840a36d190dfb2d3e62cb5
+ languageName: node
+ linkType: hard
+
+"@types/d3@npm:^7.4.0":
+ version: 7.4.3
+ resolution: "@types/d3@npm:7.4.3"
+ dependencies:
+ "@types/d3-array": "npm:*"
+ "@types/d3-axis": "npm:*"
+ "@types/d3-brush": "npm:*"
+ "@types/d3-chord": "npm:*"
+ "@types/d3-color": "npm:*"
+ "@types/d3-contour": "npm:*"
+ "@types/d3-delaunay": "npm:*"
+ "@types/d3-dispatch": "npm:*"
+ "@types/d3-drag": "npm:*"
+ "@types/d3-dsv": "npm:*"
+ "@types/d3-ease": "npm:*"
+ "@types/d3-fetch": "npm:*"
+ "@types/d3-force": "npm:*"
+ "@types/d3-format": "npm:*"
+ "@types/d3-geo": "npm:*"
+ "@types/d3-hierarchy": "npm:*"
+ "@types/d3-interpolate": "npm:*"
+ "@types/d3-path": "npm:*"
+ "@types/d3-polygon": "npm:*"
+ "@types/d3-quadtree": "npm:*"
+ "@types/d3-random": "npm:*"
+ "@types/d3-scale": "npm:*"
+ "@types/d3-scale-chromatic": "npm:*"
+ "@types/d3-selection": "npm:*"
+ "@types/d3-shape": "npm:*"
+ "@types/d3-time": "npm:*"
+ "@types/d3-time-format": "npm:*"
+ "@types/d3-timer": "npm:*"
+ "@types/d3-transition": "npm:*"
+ "@types/d3-zoom": "npm:*"
+ checksum: 12234aa093c8661546168becdd8956e892b276f525d96f65a7b32fed886fc6a569fe5a1171bff26fef2a5663960635f460c9504a6f2d242ba281a2b6c8c6465c
+ languageName: node
+ linkType: hard
+
"@types/debug@npm:^4.0.0":
version: 4.1.12
resolution: "@types/debug@npm:4.1.12"
@@ -13537,6 +13844,13 @@ __metadata:
languageName: node
linkType: hard
+"classcat@npm:^5.0.3, classcat@npm:^5.0.4":
+ version: 5.0.5
+ resolution: "classcat@npm:5.0.5"
+ checksum: 19bdeb99b8923b47f9df978b6ef2c5a4cc3bcaa8fb6be16244e31fad619b291b366429747331903ac2ea27560ffd6066d14089a99c95535ce0f1e897525fa63d
+ languageName: node
+ linkType: hard
+
"classnames@npm:*, classnames@npm:^2.2.1":
version: 2.5.1
resolution: "classnames@npm:2.5.1"
@@ -14236,6 +14550,16 @@ __metadata:
languageName: node
linkType: hard
+"d3-drag@npm:2 - 3, d3-drag@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "d3-drag@npm:3.0.0"
+ dependencies:
+ d3-dispatch: "npm:1 - 3"
+ d3-selection: "npm:3"
+ checksum: 80bc689935e5a46ee92b2d7f71e1c792279382affed9fbcf46034bff3ff7d3f50cf61a874da4bdf331037292b9e7dca5c6401a605d4bb699fdcb4e0c87e176ec
+ languageName: node
+ linkType: hard
+
"d3-dsv@npm:^1.2.0":
version: 1.2.0
resolution: "d3-dsv@npm:1.2.0"
@@ -14306,7 +14630,7 @@ __metadata:
languageName: node
linkType: hard
-"d3-ease@npm:^3.0.1":
+"d3-ease@npm:1 - 3, d3-ease@npm:^3.0.1":
version: 3.0.1
resolution: "d3-ease@npm:3.0.1"
checksum: 985d46e868494e9e6806fedd20bad712a50dcf98f357bf604a843a9f6bc17714a657c83dd762f183173dcde983a3570fa679b2bc40017d40b24163cdc4167796
@@ -14530,6 +14854,13 @@ __metadata:
languageName: node
linkType: hard
+"d3-selection@npm:2 - 3, d3-selection@npm:3, d3-selection@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "d3-selection@npm:3.0.0"
+ checksum: 0e5acfd305b31628b7be5009ba7303d84bb34817a88ed4dde9c8bd9c23528573fc5272f89fc04e5be03d2cbf5441a248d7274aaf55a8ef3dad46e16333d72298
+ languageName: node
+ linkType: hard
+
"d3-selection@npm:2, d3-selection@npm:^2.0.0":
version: 2.0.0
resolution: "d3-selection@npm:2.0.0"
@@ -14636,6 +14967,21 @@ __metadata:
languageName: node
linkType: hard
+"d3-transition@npm:2 - 3":
+ version: 3.0.1
+ resolution: "d3-transition@npm:3.0.1"
+ dependencies:
+ d3-color: "npm:1 - 3"
+ d3-dispatch: "npm:1 - 3"
+ d3-ease: "npm:1 - 3"
+ d3-interpolate: "npm:1 - 3"
+ d3-timer: "npm:1 - 3"
+ peerDependencies:
+ d3-selection: 2 - 3
+ checksum: 02571636acb82f5532117928a87fe25de68f088c38ab4a8b16e495f0f2d08a3fd2937eaebdefdfcf7f1461545524927d2632d795839b88d2e4c71e387aaaffac
+ languageName: node
+ linkType: hard
+
"d3-voronoi@npm:^1.1.2":
version: 1.1.4
resolution: "d3-voronoi@npm:1.1.4"
@@ -14643,6 +14989,19 @@ __metadata:
languageName: node
linkType: hard
+"d3-zoom@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "d3-zoom@npm:3.0.0"
+ dependencies:
+ d3-dispatch: "npm:1 - 3"
+ d3-drag: "npm:2 - 3"
+ d3-interpolate: "npm:1 - 3"
+ d3-selection: "npm:2 - 3"
+ d3-transition: "npm:2 - 3"
+ checksum: 0e6e5c14e33c4ecdff311a900dd037dea407734f2dd2818988ed6eae342c1799e8605824523678bd404f81e37824cc588f62dbde46912444c89acc7888036c6b
+ languageName: node
+ linkType: hard
+
"damerau-levenshtein@npm:^1.0.8":
version: 1.0.8
resolution: "damerau-levenshtein@npm:1.0.8"
@@ -22928,6 +23287,23 @@ __metadata:
languageName: node
linkType: hard
+"reactflow@npm:^11.10.4":
+ version: 11.11.4
+ resolution: "reactflow@npm:11.11.4"
+ dependencies:
+ "@reactflow/background": "npm:11.3.14"
+ "@reactflow/controls": "npm:11.2.14"
+ "@reactflow/core": "npm:11.11.4"
+ "@reactflow/minimap": "npm:11.7.14"
+ "@reactflow/node-resizer": "npm:2.2.14"
+ "@reactflow/node-toolbar": "npm:1.3.14"
+ peerDependencies:
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: d3322b9971d69762ee4afe5eab16c3d9a2cd362923ad2385d3751ba51f0081a91100c7018597ee0b5eee4b3037ae8ccbded0a27c1bac711252197d8eeaebbe40
+ languageName: node
+ linkType: hard
+
"read-cache@npm:^1.0.0":
version: 1.0.0
resolution: "read-cache@npm:1.0.0"
@@ -25844,6 +26220,15 @@ __metadata:
languageName: node
linkType: hard
+"use-sync-external-store@npm:^1.2.2":
+ version: 1.6.0
+ resolution: "use-sync-external-store@npm:1.6.0"
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ checksum: b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e
+ languageName: node
+ linkType: hard
+
"usehooks-ts@npm:^3.1.1":
version: 3.1.1
resolution: "usehooks-ts@npm:3.1.1"
@@ -27288,6 +27673,26 @@ __metadata:
languageName: node
linkType: hard
+"zustand@npm:^4.4.1":
+ version: 4.5.7
+ resolution: "zustand@npm:4.5.7"
+ dependencies:
+ use-sync-external-store: "npm:^1.2.2"
+ peerDependencies:
+ "@types/react": ">=16.8"
+ immer: ">=9.0.6"
+ react: ">=16.8"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ checksum: 21c47ea1c9bb0363b714a7e371a91b9afaeabc5c9c2f522803a0fb412605b1e037c4f975a7377529de8f2857e60d1f4586e7ade18444168ecc492e38779e605d
+ languageName: node
+ linkType: hard
+
"zustand@npm:^5.0.8":
version: 5.0.8
resolution: "zustand@npm:5.0.8"