mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-05 23:01:15 +02:00
feat(agent): share Web UI compatible drawing builders between MCP and copilot
The text drawing tool was MCP-only; the copilot needs the same capability and its area-annotation helper carried the same Web UI compat bugs. Split into three layers with a single implementation: - gns3server/utils/drawings.py: pure SVG builders (text, rectangle, ellipse) with full input validation (ValueError) — the Web UI format knowledge lives in core utils next to picture.py - api_handlers.py: shared REST handlers (create_text_drawing_handler moved here, create_rectangle/ellipse_drawing_handler added) — the layer both consumers already share - thin shells: MCP tools (drawing_create_text unchanged, plus new drawing_create_rectangle/_ellipse) and three LangChain tools (tools_v2/gns3_create_drawing.py) registered in both copilot mode lists Shape builder details: - rectangle: solid fill or "none", stroke block emitted only when a stroke is given, rx corner radius, dasharray validated against the Web UI's Qt remap table (QtDasharrayFixer rewrites those patterns) - ellipse: bounding-box API; cx/cy derived as offsets inside the drawing box (the Web UI treats them as box-relative, not canvas coordinates); odd sizes round down and the actual size is returned gns3_drawing_utils now delegates to the shared builders, fixing the px-blind sizing, the ignored text-anchor/x/y centering and the missing XML escaping in the area-annotation path.
This commit is contained in:
13 files changed
+1003
-265
No files matched your search
@@ -40,6 +40,7 @@ Main modules:
|
||||
- vpcs_tools_netmiko: VPCS device configuration tool using Netmiko
|
||||
- gns3_create_node: GNS3 node creation tool
|
||||
- gns3_create_link: GNS3 link creation tool
|
||||
- gns3_create_drawing: GNS3 canvas drawing tools (text, rectangle, ellipse)
|
||||
- gns3_start_node: GNS3 node startup tool (immediate return, no boot wait)
|
||||
- gns3_wait: Wait timer tool (pair with node start to let devices boot)
|
||||
- gns3_get_node_temp: GNS3 template retrieval tool
|
||||
@@ -54,6 +55,11 @@ Author: Yue Guobin (岳国宾)
|
||||
# Import main tool classes
|
||||
from .config_tools_nornir import ExecuteMultipleDeviceConfigCommands
|
||||
from .display_tools_nornir import ExecuteMultipleDeviceCommands
|
||||
from .gns3_create_drawing import (
|
||||
GNS3CreateEllipseDrawingTool,
|
||||
GNS3CreateRectangleDrawingTool,
|
||||
GNS3CreateTextDrawingTool,
|
||||
)
|
||||
from .gns3_create_link import GNS3LinkTool
|
||||
from .gns3_create_node import GNS3CreateNodeTool
|
||||
from .gns3_get_node_temp import GNS3TemplateTool
|
||||
@@ -81,7 +87,10 @@ __url__ = "https://github.com/yueguobin/gns3-copilot"
|
||||
__all__ = [
|
||||
"ExecuteMultipleDeviceCommands",
|
||||
"ExecuteMultipleDeviceConfigCommands",
|
||||
"GNS3CreateEllipseDrawingTool",
|
||||
"GNS3CreateNodeTool",
|
||||
"GNS3CreateRectangleDrawingTool",
|
||||
"GNS3CreateTextDrawingTool",
|
||||
"GNS3LinkTool",
|
||||
"GNS3PacketFilterTool",
|
||||
"GNS3StartNodeQuickTool",
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# GNS3-Copilot - AI-powered Network Lab Assistant for GNS3
|
||||
#
|
||||
# This file is part of GNS3-Copilot project.
|
||||
#
|
||||
# GNS3-Copilot is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# GNS3-Copilot is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
# for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with GNS3-Copilot. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Copyright (C) 2025 Yue Guobin (岳国宾)
|
||||
# Author: Yue Guobin (岳国宾)
|
||||
#
|
||||
# Project Home: https://github.com/yueguobin/gns3-copilot
|
||||
#
|
||||
|
||||
"""
|
||||
GNS3 canvas drawing tools for text, rectangle and ellipse annotations.
|
||||
|
||||
Thin LangChain wrappers over the shared REST handlers in
|
||||
``gns3_client.api_handlers``; the Web UI compatible SVG is assembled and
|
||||
validated by ``gns3server.utils.drawings`` (single implementation shared
|
||||
with the MCP drawing tools).
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import BaseTool
|
||||
from langchain_core.callbacks import CallbackManagerForToolRun
|
||||
|
||||
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import (
|
||||
build_gns3_ctx,
|
||||
create_ellipse_drawing_handler,
|
||||
create_rectangle_drawing_handler,
|
||||
create_text_drawing_handler,
|
||||
)
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _GNS3CreateDrawingToolBase(BaseTool):
|
||||
"""Shared plumbing: parse JSON input, build ctx, dispatch to a handler."""
|
||||
|
||||
def _dispatch(self, input_data: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
def _run(
|
||||
self,
|
||||
tool_input: str,
|
||||
run_manager: CallbackManagerForToolRun | None = None,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
input_data = json.loads(tool_input)
|
||||
|
||||
gns3_ctx = build_gns3_ctx()
|
||||
if gns3_ctx is None:
|
||||
logger.error("Failed to create GNS3 connector")
|
||||
return {"error": "Failed to connect to GNS3 server. Please check your configuration."}
|
||||
|
||||
return self._dispatch(input_data, gns3_ctx)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("Invalid JSON input: %s", e)
|
||||
return {"error": f"Invalid JSON input: {e}"}
|
||||
except Exception as e:
|
||||
logger.exception("Drawing creation failed")
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
class GNS3CreateTextDrawingTool(_GNS3CreateDrawingToolBase):
|
||||
"""
|
||||
A LangChain tool to add a text label to the GNS3 project canvas.
|
||||
|
||||
**Input**:
|
||||
A JSON object with project_id, x, y (top-left corner on the canvas)
|
||||
and text; optional font_size (1-200, default 13), color (#RRGGBB,
|
||||
default #000000), bold (default false), font_family (default
|
||||
monospace) and z (default 1, keeps text above links).
|
||||
Newlines in text start additional lines; XML characters are escaped
|
||||
automatically; the computed width/height are returned.
|
||||
|
||||
**Output**:
|
||||
A dictionary with the created drawing, its drawing_id and the
|
||||
computed width/height/line_height, or an "error" key on failure.
|
||||
"""
|
||||
|
||||
name: str = "create_gns3_text_drawing"
|
||||
description: str = """
|
||||
Adds a text label to the GNS3 project canvas.
|
||||
Input: JSON with project_id, x, y (top-left corner), text and optional
|
||||
font_size, color, bold, font_family, z.
|
||||
Returns: the created drawing with its computed width and height.
|
||||
"""
|
||||
|
||||
def _dispatch(self, input_data: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
return create_text_drawing_handler(input_data, gns3_ctx)
|
||||
|
||||
|
||||
class GNS3CreateRectangleDrawingTool(_GNS3CreateDrawingToolBase):
|
||||
"""
|
||||
A LangChain tool to add a rectangle to the GNS3 project canvas.
|
||||
|
||||
**Input**:
|
||||
A JSON object with project_id, x, y (top-left corner), width and
|
||||
height (1-2000); optional fill (#RRGGBB or "none", default #FFFFFF),
|
||||
fill_opacity (0-1, default 1.0), stroke (#RRGGBB, no border by
|
||||
default), stroke_width (1-20), dashed (bool), dasharray (like "4,2",
|
||||
requires stroke) and rx (corner radius, default 0). z defaults to 0
|
||||
so shapes stay behind nodes and text.
|
||||
|
||||
**Output**:
|
||||
A dictionary with the created drawing, or an "error" key on failure.
|
||||
"""
|
||||
|
||||
name: str = "create_gns3_rectangle_drawing"
|
||||
description: str = """
|
||||
Adds a rectangle to the GNS3 project canvas.
|
||||
Input: JSON with project_id, x, y, width, height and optional fill,
|
||||
fill_opacity, stroke, stroke_width, dashed, dasharray, rx, z.
|
||||
Returns: the created drawing with its width and height.
|
||||
"""
|
||||
|
||||
def _dispatch(self, input_data: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
return create_rectangle_drawing_handler(input_data, gns3_ctx)
|
||||
|
||||
|
||||
class GNS3CreateEllipseDrawingTool(_GNS3CreateDrawingToolBase):
|
||||
"""
|
||||
A LangChain tool to add an ellipse (or circle) to the GNS3 canvas.
|
||||
|
||||
**Input**:
|
||||
A JSON object with project_id, x, y (bounding box top-left corner),
|
||||
width and height (2-2000, equal values give a circle); the same
|
||||
optional style fields as the rectangle tool. cx/cy are derived from
|
||||
the bounding box; for odd width/height the drawing is 1 px smaller
|
||||
than requested and the actual size is returned.
|
||||
|
||||
**Output**:
|
||||
A dictionary with the created drawing, or an "error" key on failure.
|
||||
"""
|
||||
|
||||
name: str = "create_gns3_ellipse_drawing"
|
||||
description: str = """
|
||||
Adds an ellipse or circle to the GNS3 project canvas.
|
||||
Input: JSON with project_id, x, y, width, height (bounding box) and
|
||||
optional fill, fill_opacity, stroke, stroke_width, dashed, dasharray, z.
|
||||
Returns: the created drawing with its actual width and height.
|
||||
"""
|
||||
|
||||
def _dispatch(self, input_data: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]:
|
||||
return create_ellipse_drawing_handler(input_data, gns3_ctx)
|
||||
Reference in new issue
Block a user