From 755556c1465281bbefea3a01f5f0559467b9e4b1 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:06:25 +0000 Subject: [PATCH 01/42] fix(typing): resolve mypy errors in agent.web_wireshark.manage_wireshark --- gns3server/agent/web_wireshark/manage_wireshark.py | 5 +---- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/gns3server/agent/web_wireshark/manage_wireshark.py b/gns3server/agent/web_wireshark/manage_wireshark.py index 559eaadd9..5203a98d9 100755 --- a/gns3server/agent/web_wireshark/manage_wireshark.py +++ b/gns3server/agent/web_wireshark/manage_wireshark.py @@ -81,10 +81,7 @@ from gns3server.utils.uuid_validator import validate_uuid if __name__ == "__main__": sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) -try: - from gns3server.agent.web_wireshark.manager import WebWiresharkManager -except ImportError: - from manager import WebWiresharkManager +from gns3server.agent.web_wireshark.manager import WebWiresharkManager logger = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..40c6db04a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -318,7 +318,6 @@ module = [ "gns3server.agent.mcp", # 34 "gns3server.agent.mcp.appliances", # 1 "gns3server.agent.web_wireshark.docker_client", # 5 - "gns3server.agent.web_wireshark.manage_wireshark", # 1 "gns3server.agent.web_wireshark.manager", # 9 "gns3server.agent.web_wireshark.stats", # 4 "gns3server.api.routes.compute.atm_switch_nodes", # 9 From 81d77bb6891a811e7c31e5598d4cca2fed6ee970 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:20:52 +0000 Subject: [PATCH 02/42] fix(typing): resolve mypy errors in agent.web_wireshark.stats --- gns3server/agent/web_wireshark/stats.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/agent/web_wireshark/stats.py b/gns3server/agent/web_wireshark/stats.py index 78dc7b0c0..de9b66091 100644 --- a/gns3server/agent/web_wireshark/stats.py +++ b/gns3server/agent/web_wireshark/stats.py @@ -7,7 +7,7 @@ about Web Wireshark containers and sessions. import logging import subprocess -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional logger = logging.getLogger(__name__) @@ -30,7 +30,7 @@ async def collect_webwireshark_stats(projects: List) -> Dict: """ from .manager import WebWiresharkManager - stats = {"total_containers": 0, "running_containers": 0, "active_sessions": 0, "containers": []} + stats: Dict[str, Any] = {"total_containers": 0, "running_containers": 0, "active_sessions": 0, "containers": []} # Create a single manager instance and reuse it manager = WebWiresharkManager() diff --git a/pyproject.toml b/pyproject.toml index 40c6db04a..e2446fda0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -319,7 +319,6 @@ module = [ "gns3server.agent.mcp.appliances", # 1 "gns3server.agent.web_wireshark.docker_client", # 5 "gns3server.agent.web_wireshark.manager", # 9 - "gns3server.agent.web_wireshark.stats", # 4 "gns3server.api.routes.compute.atm_switch_nodes", # 9 "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 From 7f2d1bc78983317158c09e79d4845e86835d9c7b Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 06:10:36 +0000 Subject: [PATCH 03/42] fix(typing): resolve mypy errors in agent.web_wireshark.manager --- gns3server/agent/web_wireshark/manager.py | 16 ++++++++-------- pyproject.toml | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gns3server/agent/web_wireshark/manager.py b/gns3server/agent/web_wireshark/manager.py index e17cf65c0..16f057d75 100644 --- a/gns3server/agent/web_wireshark/manager.py +++ b/gns3server/agent/web_wireshark/manager.py @@ -84,7 +84,7 @@ class WebWiresharkManager: logger.warning(f"Container {container_id[:12]} health check failed: {e}") return False - async def _exec_in_container(self, container_id: str, command: str, timeout: int = None) -> tuple: + async def _exec_in_container(self, container_id: str, command: str, timeout: Optional[int] = None) -> tuple: """Execute command in container with timeout. Args: @@ -197,7 +197,7 @@ class WebWiresharkManager: stdout, _ = await proc.communicate() # Build parent->children mapping - children_map = {} + children_map: dict[int, list[int]] = {} for line in stdout.decode().strip().split("\n"): parts = line.split(None, 2) if len(parts) < 3: @@ -326,7 +326,7 @@ class WebWiresharkManager: return # Build parent->children mapping and collect process info - children_map = {} # ppid -> [pid] + children_map: dict[int, list[int]] = {} # ppid -> [pid] process_info = {} # pid -> (ppid, command) for line in stdout.decode().strip().split("\n"): @@ -480,7 +480,7 @@ class WebWiresharkManager: logger.debug(f"Cannot get URL from Config: {e}") return None - async def _get_container_gateway_ip(self, container_id: str = None) -> Optional[str]: + async def _get_container_gateway_ip(self, container_id: Optional[str] = None) -> Optional[str]: """Get the Docker bridge gateway IP for container to access host. Args: @@ -519,7 +519,7 @@ class WebWiresharkManager: return None - async def get_container_ip(self, container_name: str, container_id: str = None) -> Optional[str]: + async def get_container_ip(self, container_name: str, container_id: Optional[str] = None) -> Optional[str]: """Get the container IP address in the wireshark network. Args: @@ -626,7 +626,7 @@ class WebWiresharkManager: project_id: str, image: str = "gns3/web-wireshark:latest", memory: str = "2g", - memory_swap: str = None, + memory_swap: Optional[str] = None, cpus: float = 1.0, pids_limit: int = 1000, ) -> str: @@ -935,10 +935,10 @@ class WebWiresharkManager: project_id: str, link_id: str, jwt_token: str, - capture_stream_url: str = None, + capture_stream_url: Optional[str] = None, image: str = "gns3/web-wireshark:latest", memory: str = "2g", - memory_swap: str = None, + memory_swap: Optional[str] = None, cpus: float = 1.0, pids_limit: int = 1000, ): diff --git a/pyproject.toml b/pyproject.toml index e2446fda0..2611beb26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -318,7 +318,6 @@ module = [ "gns3server.agent.mcp", # 34 "gns3server.agent.mcp.appliances", # 1 "gns3server.agent.web_wireshark.docker_client", # 5 - "gns3server.agent.web_wireshark.manager", # 9 "gns3server.api.routes.compute.atm_switch_nodes", # 9 "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 From 890093de01f2401d4c98aa5f5e510276971994e6 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:01:12 +0000 Subject: [PATCH 04/42] fix(typing): resolve mypy errors in agent.gns3_copilot.gns3_client.connector_factory --- gns3server/agent/gns3_copilot/gns3_client/connector_factory.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/agent/gns3_copilot/gns3_client/connector_factory.py b/gns3server/agent/gns3_copilot/gns3_client/connector_factory.py index c57dc8fee..32a4e2e45 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/connector_factory.py +++ b/gns3server/agent/gns3_copilot/gns3_client/connector_factory.py @@ -347,7 +347,7 @@ def get_gns3_server_host() -> str: host = get_gns3_server_host() print(f"GNS3 server host: {host}") """ - url = _detect_url_for_api() + url = _detect_url_for_api() or DEFAULT_GNS3_URL # Extract host from URL # URL format: protocol://host:port diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..46137effe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -300,7 +300,6 @@ module = [ "gns3server.agent.gns3_copilot.agent_service", # 8 "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 - "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 "gns3server.agent.gns3_copilot.skills.loader", # 1 "gns3server.agent.gns3_copilot.skills.manager", # 4 From 97f71269d361139b6f39ee20964b153aedd96c5a Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:05:01 +0000 Subject: [PATCH 05/42] fix(typing): resolve mypy errors in agent.gns3_copilot.gns3_client.project_inventory --- .../agent/gns3_copilot/gns3_client/project_inventory.py | 6 +++--- pyproject.toml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gns3server/agent/gns3_copilot/gns3_client/project_inventory.py b/gns3server/agent/gns3_copilot/gns3_client/project_inventory.py index b1f958767..aeefd304a 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/project_inventory.py +++ b/gns3server/agent/gns3_copilot/gns3_client/project_inventory.py @@ -49,7 +49,7 @@ def build_nodes_inventory(nodes: list[dict[str, Any]], server_host: str | None) """ inventory: dict[str, Any] = {} for n in nodes: - inventory[n.get("name")] = { + inventory[n.get("name") or ""] = { "server": server_host, "name": n.get("name"), "node_id": n.get("node_id"), @@ -68,7 +68,7 @@ def build_nodes_inventory(nodes: list[dict[str, Any]], server_host: str | None) return inventory -def build_links_summary(nodes: list[dict[str, Any]], links: list[dict[str, Any]]) -> list[dict[str, str]]: +def build_links_summary(nodes: list[dict[str, Any]], links: list[dict[str, Any]]) -> list[dict[str, Any]]: """ Build a human/LLM-friendly link list resolving node and port names. @@ -76,7 +76,7 @@ def build_links_summary(nodes: list[dict[str, Any]], links: list[dict[str, Any]] Links whose endpoints cannot be resolved are skipped, mirroring the original Project.links_summary behavior. """ - summary: list[dict[str, str]] = [] + summary: list[dict[str, Any]] = [] for link in links: if not link.get("nodes"): continue diff --git a/pyproject.toml b/pyproject.toml index 46137effe..7b0ff58a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -300,7 +300,6 @@ module = [ "gns3server.agent.gns3_copilot.agent_service", # 8 "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 - "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 "gns3server.agent.gns3_copilot.skills.loader", # 1 "gns3server.agent.gns3_copilot.skills.manager", # 4 "gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir", # 6 From e8bff79370fbad37954761083f8e6b4803be7c73 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:26:26 +0000 Subject: [PATCH 06/42] fix(typing): resolve mypy errors in agent.gns3_copilot.tools_v2.config_tools_nornir --- gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py b/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py index db215c6bf..ed15f0589 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py +++ b/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py @@ -78,7 +78,7 @@ try: # so we use importlib to get the actual module import importlib - sd = importlib.import_module("netmiko.ssh_dispatcher") + sd: Any = importlib.import_module("netmiko.ssh_dispatcher") # Recalculate platforms lists to include custom device types sd.platforms = list(sd.CLASS_MAPPER.keys()) @@ -196,7 +196,7 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool): # Validate input device_configs_list, project_id = self._validate_tool_input(tool_input) - if isinstance(device_configs_list, list) and len(device_configs_list) > 0 and "error" in device_configs_list[0]: + if len(device_configs_list) > 0 and "error" in device_configs_list[0]: return device_configs_list # Filter forbidden commands and store blocked commands info diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..21a5c54ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -304,7 +304,6 @@ module = [ "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 "gns3server.agent.gns3_copilot.skills.loader", # 1 "gns3server.agent.gns3_copilot.skills.manager", # 4 - "gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.display_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.gns3_packet_filter", # 1 "gns3server.agent.gns3_copilot.tools_v2.gns3_start_node", # 3 From c7c7e3b5839e0825aa11262a9cdae4102919e901 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:00:35 +0000 Subject: [PATCH 07/42] fix(typing): resolve mypy errors in agent.gns3_copilot.skills.loader --- gns3server/agent/gns3_copilot/skills/loader.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/agent/gns3_copilot/skills/loader.py b/gns3server/agent/gns3_copilot/skills/loader.py index 655d76263..0295e2520 100644 --- a/gns3server/agent/gns3_copilot/skills/loader.py +++ b/gns3server/agent/gns3_copilot/skills/loader.py @@ -248,7 +248,7 @@ class SkillsLoader: continue # Use device_type from YAML content as the key # Fallback to filename stem if device_type not present - skill_key = skill_data.get("device_type") if isinstance(skill_data, dict) else None + skill_key = skill_data.get("device_type") if not skill_key: skill_key = yaml_file.stem logger.warning(f"No device_type in {yaml_file}, using filename '{skill_key}' as key") diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..0c02c3d5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -302,7 +302,6 @@ module = [ "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 - "gns3server.agent.gns3_copilot.skills.loader", # 1 "gns3server.agent.gns3_copilot.skills.manager", # 4 "gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.display_tools_nornir", # 6 From 35b360ca752dbf6bd53090b892e450926603ccfd Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:26:36 +0000 Subject: [PATCH 08/42] fix(typing): resolve mypy errors in agent.gns3_copilot.tools_v2.display_tools_nornir --- .../agent/gns3_copilot/tools_v2/display_tools_nornir.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py b/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py index 8c4a5281d..a8eb41bc4 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py +++ b/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py @@ -78,7 +78,7 @@ try: # so we use importlib to get the actual module import importlib - sd = importlib.import_module("netmiko.ssh_dispatcher") + sd: Any = importlib.import_module("netmiko.ssh_dispatcher") # Recalculate platforms lists to include custom device types sd.platforms = list(sd.CLASS_MAPPER.keys()) @@ -199,7 +199,7 @@ class ExecuteMultipleDeviceCommands(BaseTool): # Validate input device_configs_list, project_id = self._validate_tool_input(tool_input) - if isinstance(device_configs_list, list) and len(device_configs_list) > 0 and "error" in device_configs_list[0]: + if len(device_configs_list) > 0 and "error" in device_configs_list[0]: return device_configs_list # Filter forbidden commands and store blocked commands info diff --git a/pyproject.toml b/pyproject.toml index 21a5c54ac..49c8063be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -304,7 +304,6 @@ module = [ "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 "gns3server.agent.gns3_copilot.skills.loader", # 1 "gns3server.agent.gns3_copilot.skills.manager", # 4 - "gns3server.agent.gns3_copilot.tools_v2.display_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.gns3_packet_filter", # 1 "gns3server.agent.gns3_copilot.tools_v2.gns3_start_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_stop_node", # 3 From d6ae597f366bdbf1e70ac02fdf1e08e364cf3a43 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:21:57 +0000 Subject: [PATCH 09/42] fix(typing): resolve mypy errors in agent.gns3_copilot.skills.manager --- .../agent/gns3_copilot/skills/manager.py | 18 ++++++++++-------- pyproject.toml | 1 - 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/gns3server/agent/gns3_copilot/skills/manager.py b/gns3server/agent/gns3_copilot/skills/manager.py index 473fb2b09..1a511ec29 100644 --- a/gns3server/agent/gns3_copilot/skills/manager.py +++ b/gns3server/agent/gns3_copilot/skills/manager.py @@ -37,8 +37,10 @@ from typing import Optional, Dict, Any try: import git + + GIT_AVAILABLE = True except ImportError: - git = None + GIT_AVAILABLE = False from gns3server.config import Config from .loader import SkillsLoader @@ -66,7 +68,7 @@ class SkillsManager: - Version tracking """ - def __init__(self, repo_url: str = None, branch: str = "main", auto_update: bool = False): + def __init__(self, repo_url: Optional[str] = None, branch: str = "main", auto_update: bool = False): """ Initialize the skills manager. @@ -90,7 +92,7 @@ class SkillsManager: self._repo: Optional["git.Repo"] = None self._prompt_count = 0 - if git is None: + if not GIT_AVAILABLE: logger.warning("GitPython is not installed. Skills management features will be limited.") def initialize(self) -> bool: @@ -134,7 +136,7 @@ class SkillsManager: - Up to date → nothing - Network error → use existing files, log warning """ - if git is None or self._repo is None: + if not GIT_AVAILABLE or self._repo is None: return # Check for uncommitted changes @@ -173,7 +175,7 @@ class SkillsManager: Returns: True if successful, False otherwise """ - if git is None: + if not GIT_AVAILABLE: logger.error("GitPython is not installed. Cannot clone repository.") return False @@ -347,7 +349,7 @@ class SkillsManager: Returns: Commit hash as string, or empty string if not available """ - if git is None or self._repo is None: + if not GIT_AVAILABLE or self._repo is None: try: self._repo = git.Repo(self.local_path) except Exception: @@ -409,7 +411,7 @@ class SkillsManager: Returns: True if successful, False otherwise """ - if git is None: + if not GIT_AVAILABLE: logger.error("GitPython is not installed. Cannot rollback.") return False @@ -436,7 +438,7 @@ class SkillsManager: Returns: List of commit information dictionaries """ - if git is None: + if not GIT_AVAILABLE: return [] try: diff --git a/pyproject.toml b/pyproject.toml index 0c02c3d5a..bab14b8c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -302,7 +302,6 @@ module = [ "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 - "gns3server.agent.gns3_copilot.skills.manager", # 4 "gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.display_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.gns3_packet_filter", # 1 From 67f68c9676cdaa1d044d2d2c46a30903ad840caa Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:00:48 +0000 Subject: [PATCH 10/42] fix(typing): resolve mypy errors in agent.gns3_copilot.tools_v2.packet_analysis_tool --- gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py | 3 ++- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py b/gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py index b9c35d141..c5f30bf79 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py +++ b/gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py @@ -143,7 +143,8 @@ class PacketAnalysisTool(BaseTool): stderr=subprocess.DEVNULL, text=True, ) - tshark.stdout.close() + if tshark.stdout is not None: + tshark.stdout.close() stdout, _ = grep.communicate(timeout=30) tshark.wait(timeout=5) except Exception as e: diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..8b3471880 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -310,7 +310,6 @@ module = [ "gns3server.agent.gns3_copilot.tools_v2.gns3_start_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_stop_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_suspend_node", # 3 - "gns3server.agent.gns3_copilot.tools_v2.packet_analysis_tool", # 1 "gns3server.agent.gns3_copilot.tools_v2.vpcs_tools_netmiko", # 9 "gns3server.agent.gns3_copilot.utils.custom_netmiko.scripts.list_netmiko_telnet_devices", # 1 "gns3server.agent.gns3_copilot.utils.message_converters", # 2 From d2fd2383836e51e55f825f86e4e845de1b8c3b39 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:00:42 +0000 Subject: [PATCH 11/42] fix(typing): resolve mypy errors in agent.gns3_copilot.tools_v2.gns3_packet_filter --- gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py index d94a0fc1d..be7af824f 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py @@ -286,7 +286,7 @@ class GNS3PacketFilterTool(BaseTool): # Check if output contains "Invalid" indicating syntax error if "Invalid" in result.stdout or "Invalid" in result.stderr: - error_lines = [] + error_lines: list[str] = [] if "Invalid" in result.stderr: error_lines.extend(line for line in result.stderr.split("\n") if "Invalid" in line) if "Invalid" in result.stdout: diff --git a/pyproject.toml b/pyproject.toml index 49c8063be..785afbae0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -304,7 +304,6 @@ module = [ "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 "gns3server.agent.gns3_copilot.skills.loader", # 1 "gns3server.agent.gns3_copilot.skills.manager", # 4 - "gns3server.agent.gns3_copilot.tools_v2.gns3_packet_filter", # 1 "gns3server.agent.gns3_copilot.tools_v2.gns3_start_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_stop_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_suspend_node", # 3 From f8144494f61a22330728a50cc3e04d650ddcea01 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 06:11:09 +0000 Subject: [PATCH 12/42] fix(typing): resolve mypy errors in agent.gns3_copilot.tools_v2.vpcs_tools_netmiko --- .../agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py | 8 ++++---- pyproject.toml | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py b/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py index 3a8789cda..1315730c7 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py +++ b/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py @@ -68,7 +68,7 @@ try: # CRITICAL: Update netmiko.ssh_dispatcher platforms lists import importlib - sd = importlib.import_module("netmiko.ssh_dispatcher") + sd: Any = importlib.import_module("netmiko.ssh_dispatcher") # Recalculate platforms lists to include custom device types sd.platforms = list(sd.CLASS_MAPPER.keys()) @@ -179,7 +179,7 @@ class VPCSCommands(BaseTool): # Validate input device_configs_list, project_id = self._validate_tool_input(tool_input) - if isinstance(device_configs_list, list) and len(device_configs_list) > 0 and "error" in device_configs_list[0]: + if len(device_configs_list) > 0 and "error" in device_configs_list[0]: return device_configs_list # Create a mapping of device names to their commands @@ -386,7 +386,7 @@ class VPCSCommands(BaseTool): def _prepare_device_hosts_data( self, device_configs_list: list[dict[str, Any]], - project_id: str, + project_id: str | None, jwt_token: str | None = None, url: str | None = None, ) -> dict[str, dict[str, Any]]: @@ -415,7 +415,7 @@ class VPCSCommands(BaseTool): device_ports = get_device_ports_from_topology(device_names, project_id=project_id, jwt_token=jwt_token, url=url) # Build Nornir inventory hosts data - hosts_data = {} + hosts_data: dict[str, Any] = {} for device_name in device_names: if device_name not in device_ports: logger.error("Device '%s' not found in topology", device_name) diff --git a/pyproject.toml b/pyproject.toml index 8b3471880..80399d488 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -310,7 +310,6 @@ module = [ "gns3server.agent.gns3_copilot.tools_v2.gns3_start_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_stop_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.gns3_suspend_node", # 3 - "gns3server.agent.gns3_copilot.tools_v2.vpcs_tools_netmiko", # 9 "gns3server.agent.gns3_copilot.utils.custom_netmiko.scripts.list_netmiko_telnet_devices", # 1 "gns3server.agent.gns3_copilot.utils.message_converters", # 2 "gns3server.agent.gns3_copilot.utils.parse_tool_content", # 1 From 5bb7896242f00890e2610690e14403c6e92cbe0d Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:42:04 +0000 Subject: [PATCH 13/42] fix(typing): resolve mypy errors in api.routes.compute.capabilities --- gns3server/api/routes/compute/capabilities.py | 3 ++- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/api/routes/compute/capabilities.py b/gns3server/api/routes/compute/capabilities.py index f472f6231..42154d4e1 100644 --- a/gns3server/api/routes/compute/capabilities.py +++ b/gns3server/api/routes/compute/capabilities.py @@ -39,7 +39,8 @@ def get_capabilities(request: Request) -> dict: node_types.extend(module.node_types()) # record the controller hostname or IP address - request.app.state.controller_host = request.client.host + if request.client: + request.app.state.controller_host = request.client.host return { "version": __version__, diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..fadae6ec9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -322,7 +322,6 @@ module = [ "gns3server.agent.web_wireshark.manager", # 9 "gns3server.agent.web_wireshark.stats", # 4 "gns3server.api.routes.compute.atm_switch_nodes", # 9 - "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 "gns3server.api.routes.compute.compute", # 1 "gns3server.api.routes.compute.dependencies.authentication", # 3 From f94a0cae0d8f026f5c4c790ae90350c961b4f088 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:41:45 +0000 Subject: [PATCH 14/42] fix(typing): resolve mypy errors in api.routes.compute.compute --- gns3server/api/routes/compute/compute.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/api/routes/compute/compute.py b/gns3server/api/routes/compute/compute.py index 1a862787c..c98b1a256 100644 --- a/gns3server/api/routes/compute/compute.py +++ b/gns3server/api/routes/compute/compute.py @@ -163,7 +163,7 @@ def compute_statistics() -> dict: @router.get("/qemu/capabilities") async def get_qemu_capabilities() -> dict: - capabilities = {"kvm": []} + capabilities: dict[str, list] = {"kvm": []} kvms = await Qemu.get_kvm_archs() if kvms: capabilities["kvm"] = kvms diff --git a/pyproject.toml b/pyproject.toml index fadae6ec9..5900a66ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -323,7 +323,6 @@ module = [ "gns3server.agent.web_wireshark.stats", # 4 "gns3server.api.routes.compute.atm_switch_nodes", # 9 "gns3server.api.routes.compute.cloud_nodes", # 14 - "gns3server.api.routes.compute.compute", # 1 "gns3server.api.routes.compute.dependencies.authentication", # 3 "gns3server.api.routes.compute.docker_nodes", # 26 "gns3server.api.routes.compute.dynamips_nodes", # 9 From 777258ff68017cdd50fe441595b8e922015ba931 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:58:15 +0000 Subject: [PATCH 15/42] fix(typing): resolve mypy errors in api.routes.controller.api_keys --- gns3server/api/routes/controller/api_keys.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/api/routes/controller/api_keys.py b/gns3server/api/routes/controller/api_keys.py index 235cbe7de..a3bfa4b14 100644 --- a/gns3server/api/routes/controller/api_keys.py +++ b/gns3server/api/routes/controller/api_keys.py @@ -39,7 +39,7 @@ API_KEY_PREFIX = "gns3_" API_KEY_BYTES = 32 -def _generate_api_key(api_key_id: UUID = None) -> tuple[str, str, str, UUID]: +def _generate_api_key(api_key_id: UUID | None = None) -> tuple[str, str, str, UUID]: if api_key_id is None: api_key_id = uuid4() random_bytes = secrets.token_hex(API_KEY_BYTES) diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..b911216f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -340,7 +340,6 @@ module = [ "gns3server.api.routes.compute.vmware_nodes", # 10 "gns3server.api.routes.compute.vpcs_nodes", # 9 "gns3server.api.routes.controller.acl", # 5 - "gns3server.api.routes.controller.api_keys", # 1 "gns3server.api.routes.controller.chat", # 1 "gns3server.api.routes.controller.computes", # 6 "gns3server.api.routes.controller.controller", # 10 From 6526beeb3d3c170f48c471bfb423c5157316e7cf Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:57:51 +0000 Subject: [PATCH 16/42] fix(typing): resolve mypy errors in api.routes.controller.chat --- gns3server/api/routes/controller/chat.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/controller/chat.py b/gns3server/api/routes/controller/chat.py index cc796fe26..e55e722f7 100644 --- a/gns3server/api/routes/controller/chat.py +++ b/gns3server/api/routes/controller/chat.py @@ -23,7 +23,7 @@ Nested under projects: /v3/projects/{project_id}/chat/... import json import logging import uuid -from typing import List, Optional +from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.responses import StreamingResponse @@ -40,7 +40,7 @@ from .dependencies.authentication import get_current_active_user log = logging.getLogger(__name__) -responses = {404: {"model": schemas.ErrorMessage, "description": "Resource not found"}} +responses: dict[int | str, dict[str, Any]] = {404: {"model": schemas.ErrorMessage, "description": "Resource not found"}} router = APIRouter(responses=responses) diff --git a/pyproject.toml b/pyproject.toml index b911216f7..98edf55b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -340,7 +340,6 @@ module = [ "gns3server.api.routes.compute.vmware_nodes", # 10 "gns3server.api.routes.compute.vpcs_nodes", # 9 "gns3server.api.routes.controller.acl", # 5 - "gns3server.api.routes.controller.chat", # 1 "gns3server.api.routes.controller.computes", # 6 "gns3server.api.routes.controller.controller", # 10 "gns3server.api.routes.controller.dependencies.authentication", # 11 From 5fb2c277a9a8980b169706a0ebe22697f3456e97 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:16:05 +0000 Subject: [PATCH 17/42] fix(typing): resolve mypy errors in api.routes.compute.notifications --- gns3server/api/routes/compute/notifications.py | 5 +++-- pyproject.toml | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/compute/notifications.py b/gns3server/api/routes/compute/notifications.py index 2c6ca4ad5..ee533037b 100644 --- a/gns3server/api/routes/compute/notifications.py +++ b/gns3server/api/routes/compute/notifications.py @@ -39,14 +39,15 @@ async def project_ws_notifications(websocket: Union[None, WebSocket] = Depends(w """ if websocket: - log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket") + client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown" + log.info(f"New client {client} has connected to compute WebSocket") try: with NotificationManager.instance().queue() as queue: while True: notification = await queue.get_json(5) await websocket.send_text(notification) except (ConnectionClosed, WebSocketDisconnect): - log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket") + log.info(f"Client {client} has disconnected from compute WebSocket") except WebSocketException as e: log.warning(f"Error while sending to controller event to WebSocket client: {e}") diff --git a/pyproject.toml b/pyproject.toml index 5900a66ff..ec44a2882 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -331,7 +331,6 @@ module = [ "gns3server.api.routes.compute.frame_relay_switch_nodes", # 9 "gns3server.api.routes.compute.iou_nodes", # 15 "gns3server.api.routes.compute.nat_nodes", # 10 - "gns3server.api.routes.compute.notifications", # 4 "gns3server.api.routes.compute.projects", # 7 "gns3server.api.routes.compute.qemu_nodes", # 18 "gns3server.api.routes.compute.virtualbox_nodes", # 20 From bbdd9bf8617d738f80143d033921d807da35717e Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:57:45 +0000 Subject: [PATCH 18/42] fix(typing): resolve mypy errors in api.routes.controller.drawings --- gns3server/api/routes/controller/drawings.py | 6 ++++-- pyproject.toml | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/controller/drawings.py b/gns3server/api/routes/controller/drawings.py index 42e989f0f..6df9ddd91 100644 --- a/gns3server/api/routes/controller/drawings.py +++ b/gns3server/api/routes/controller/drawings.py @@ -20,7 +20,7 @@ API routes for drawings. from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder -from typing import List +from typing import Any, List from uuid import UUID from gns3server.controller import Controller @@ -30,7 +30,9 @@ from gns3server import schemas from .dependencies.database import get_repository from .dependencies.rbac import has_privilege -responses = {404: {"model": schemas.ErrorMessage, "description": "Project or drawing not found"}} +responses: dict[int | str, dict[str, Any]] = { + 404: {"model": schemas.ErrorMessage, "description": "Project or drawing not found"} +} router = APIRouter(responses=responses) diff --git a/pyproject.toml b/pyproject.toml index 98edf55b8..1b09de56e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -344,7 +344,6 @@ module = [ "gns3server.api.routes.controller.controller", # 10 "gns3server.api.routes.controller.dependencies.authentication", # 11 "gns3server.api.routes.controller.dependencies.database", # 2 - "gns3server.api.routes.controller.drawings", # 1 "gns3server.api.routes.controller.groups", # 6 "gns3server.api.routes.controller.images", # 9 "gns3server.api.routes.controller.links", # 27 From 943bd6732cd630b91c36c34d85c859133d69da6e Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:53:39 +0000 Subject: [PATCH 19/42] fix(typing): resolve mypy errors in utils.encryption --- gns3server/utils/encryption.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/utils/encryption.py b/gns3server/utils/encryption.py index 1f95f497b..38719a60e 100644 --- a/gns3server/utils/encryption.py +++ b/gns3server/utils/encryption.py @@ -48,7 +48,7 @@ def init_encryption(secrets_dir: str) -> None: os.makedirs(secrets_dir, exist_ok=True) with open(encryption_key_path, "w", encoding="utf-8") as f: # Use Fernet's base64-encoded key format - f.write(key.decode() if isinstance(key, bytes) else key) + f.write(key.decode()) # Set restrictive permissions (owner read/write only) os.chmod(encryption_key_path, 0o600) except OSError as e: @@ -58,7 +58,7 @@ def init_encryption(secrets_dir: str) -> None: try: with open(encryption_key_path, encoding="utf-8") as f: key_content = f.read().strip() - key_bytes = key_content.encode() if isinstance(key_content, str) else key_content + key_bytes = key_content.encode() _fernet = Fernet(key_bytes) log.debug("Encryption initialized successfully") except OSError as e: diff --git a/pyproject.toml b/pyproject.toml index 5f0789a77..b992dcbb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -413,7 +413,6 @@ module = [ "gns3server.services.computes", # 4 "gns3server.services.templates", # 18 "gns3server.utils.asyncio.aiozipstream", # 8 - "gns3server.utils.encryption", # 2 "gns3server.utils.http_client", # 4 "gns3server.utils.images", # 4 "gns3server.utils.packet_filter_validation", # 16 From ab5572a0c1bbe5ab5c44483696f09f73b6d90e57 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:10:05 +0000 Subject: [PATCH 20/42] fix(typing): resolve mypy errors in utils.images --- gns3server/utils/images.py | 10 +++++----- pyproject.toml | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gns3server/utils/images.py b/gns3server/utils/images.py index c06e22bec..06b4ae9be 100644 --- a/gns3server/utils/images.py +++ b/gns3server/utils/images.py @@ -126,7 +126,7 @@ def get_builtin_disks() -> List[str]: return builtin_disks -async def read_image_info(path: str, expected_image_type: str = None) -> dict: +async def read_image_info(path: str, expected_image_type: str | None = None) -> dict: header_magic_len = 7 try: @@ -155,7 +155,7 @@ async def read_image_info(path: str, expected_image_type: str = None) -> dict: return image_info -async def discover_images(image_type: str, skip_image_paths: list = None) -> List[dict]: +async def discover_images(image_type: str, skip_image_paths: list | None = None) -> List[dict]: """ Scan directories for available images """ @@ -378,9 +378,9 @@ async def write_image( f"File '{image_path}' already exists, please choose a different name or remove the existing image" ) - checksum = checksum.hexdigest() + checksum_hex = checksum.hexdigest() image_dir = os.path.dirname(image_path) - duplicate_image = await images_repo.get_image_by_checksum(checksum, image_dir) + duplicate_image = await images_repo.get_image_by_checksum(checksum_hex, image_dir) if duplicate_image: raise InvalidImageError( f"Image '{duplicate_image.filename}' with the same checksum already exists in '{image_dir}'" @@ -396,5 +396,5 @@ async def write_image( log.warning(f"Could not remove '{tmp_path}'") return await images_repo.add_image( - image_name, image_type, image_size, image_path, checksum, checksum_algorithm="md5" + image_name, image_type, image_size, image_path, checksum_hex, checksum_algorithm="md5" ) diff --git a/pyproject.toml b/pyproject.toml index b992dcbb1..719f116dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -414,7 +414,6 @@ module = [ "gns3server.services.templates", # 18 "gns3server.utils.asyncio.aiozipstream", # 8 "gns3server.utils.http_client", # 4 - "gns3server.utils.images", # 4 "gns3server.utils.packet_filter_validation", # 16 "gns3server.utils.websocket_to_websocket", # 12 "gns3server.utils.zipfile_zstd", # 2 From 94bf56040b38ace8847f444142b3bc67c980de01 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 06:24:56 +0000 Subject: [PATCH 21/42] fix(typing): resolve mypy errors in utils.packet_filter_validation --- gns3server/utils/packet_filter_validation.py | 10 +++++----- pyproject.toml | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gns3server/utils/packet_filter_validation.py b/gns3server/utils/packet_filter_validation.py index cc2634546..1b9481ffd 100644 --- a/gns3server/utils/packet_filter_validation.py +++ b/gns3server/utils/packet_filter_validation.py @@ -15,7 +15,7 @@ class FilterValidationError(Exception): pass -def validate_bpf_syntax(bpf_expression: str) -> Dict[str, Optional[str]]: +def validate_bpf_syntax(bpf_expression: str) -> Dict[str, Any]: """ Validate BPF filter expression syntax using tcpdump. @@ -78,7 +78,7 @@ def validate_filter_parameters(filter_type: str, values: List[Any]) -> None: """ # Define validation rules based on ubridge implementation - VALIDATION_RULES = { + VALIDATION_RULES: Dict[str, Dict[str, Any]] = { "frequency_drop": { "params_count": 1, "ranges": [(-1, 32767)], # min, max @@ -173,11 +173,11 @@ def filter_inactive_filters(filters: Dict[str, List[Any]]) -> Dict[str, List[Any active_filters = {} for filter_type, values in filters.items(): - if not values or (isinstance(values, list) and len(values) == 0): + if not values: continue # Normalize values (strip strings, convert to int) - normalized_values = [] + normalized_values: List[Any] = [] for value in values: if isinstance(value, str): normalized_values.append(value.strip("\n ")) @@ -228,7 +228,7 @@ def validate_all_filters(filters: Dict[str, List[Any]]) -> None: return for filter_type, values in filters.items(): - if not values or (isinstance(values, list) and len(values) == 0): + if not values: continue validate_filter_parameters(filter_type, values) diff --git a/pyproject.toml b/pyproject.toml index 719f116dc..97426df7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -414,7 +414,6 @@ module = [ "gns3server.services.templates", # 18 "gns3server.utils.asyncio.aiozipstream", # 8 "gns3server.utils.http_client", # 4 - "gns3server.utils.packet_filter_validation", # 16 "gns3server.utils.websocket_to_websocket", # 12 "gns3server.utils.zipfile_zstd", # 2 "gns3server.utils.zipfile_zstd._patcher", # 1 From a251ca73acd5f04ed4614578e7ae5879da338f19 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:20:39 +0000 Subject: [PATCH 22/42] fix(typing): resolve mypy errors in agent.gns3_copilot.agent.context_manager --- gns3server/agent/gns3_copilot/agent/context_manager.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/agent/gns3_copilot/agent/context_manager.py b/gns3server/agent/gns3_copilot/agent/context_manager.py index 79c526dbf..5337c3d95 100644 --- a/gns3server/agent/gns3_copilot/agent/context_manager.py +++ b/gns3server/agent/gns3_copilot/agent/context_manager.py @@ -140,7 +140,7 @@ def estimate_tool_tokens(tools: list[Any]) -> int: for tool in tools: try: # Build tool schema in OpenAI format - tool_schema = { + tool_schema: dict[str, Any] = { "type": "function", "function": { "name": tool.name, diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..909ed64d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -295,7 +295,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] # Remove modules from this list one small PR at a time. Never add new ones. module = [ "gns3server.agent", # 2 - "gns3server.agent.gns3_copilot.agent.context_manager", # 5 "gns3server.agent.gns3_copilot.agent.model_factory", # 4 "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 From cd7f5ab8e95b65c004f91d0e99316f776d6cbf83 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:20:33 +0000 Subject: [PATCH 23/42] fix(typing): resolve mypy errors in agent.gns3_copilot.agent.model_factory --- gns3server/agent/gns3_copilot/agent/model_factory.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/agent/gns3_copilot/agent/model_factory.py b/gns3server/agent/gns3_copilot/agent/model_factory.py index cfdc26854..f790646bd 100644 --- a/gns3server/agent/gns3_copilot/agent/model_factory.py +++ b/gns3server/agent/gns3_copilot/agent/model_factory.py @@ -110,7 +110,7 @@ def create_base_model( try: # Prepare parameters for init_chat_model - init_params = { + init_params: dict[str, Any] = { "model": config_vars["model_name"], "model_provider": config_vars["model_provider"], "api_key": config_vars["api_key"], @@ -177,7 +177,7 @@ def create_title_model( try: # Prepare parameters for init_chat_model - init_params = { + init_params: dict[str, Any] = { "model": config_vars["model_name"], "model_provider": config_vars["model_provider"], "api_key": config_vars["api_key"], diff --git a/pyproject.toml b/pyproject.toml index 909ed64d9..ee3fb2bbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -295,7 +295,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] # Remove modules from this list one small PR at a time. Never add new ones. module = [ "gns3server.agent", # 2 - "gns3server.agent.gns3_copilot.agent.model_factory", # 4 "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 From 5cb1b8e3d2ddcf9bfeedd0d820bbdf85e3631702 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:05:22 +0000 Subject: [PATCH 24/42] fix(typing): resolve mypy errors in agent.gns3_copilot.utils.message_converters --- gns3server/agent/gns3_copilot/utils/message_converters.py | 5 +++-- pyproject.toml | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gns3server/agent/gns3_copilot/utils/message_converters.py b/gns3server/agent/gns3_copilot/utils/message_converters.py index 8bf18f668..367b2d956 100644 --- a/gns3server/agent/gns3_copilot/utils/message_converters.py +++ b/gns3server/agent/gns3_copilot/utils/message_converters.py @@ -37,6 +37,7 @@ from typing import Dict from langchain_core.messages import AIMessage from langchain_core.messages import HumanMessage from langchain_core.messages import SystemMessage +from langchain_core.messages import ToolCall from langchain_core.messages import ToolMessage @@ -86,7 +87,7 @@ def convert_langchain_to_openai(lc_message) -> Dict[str, Any]: tool_calls = [] for tc in lc_message.tool_calls: # Convert to dict if it's an object - tc_dict = tc if isinstance(tc, dict) else tc.model_dump() + tc_dict = tc tool_calls.append( { "id": tc_dict.get("id", str(uuid.uuid4())), @@ -139,7 +140,7 @@ def convert_openai_to_langchain(msg: Dict[str, Any]): # Restore tool calls if present if "tool_calls" in msg and msg["tool_calls"]: - tool_calls = [] + tool_calls: list[ToolCall] = [] for tc in msg["tool_calls"]: tool_calls.append( { diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..25a160936 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -312,7 +312,6 @@ module = [ "gns3server.agent.gns3_copilot.tools_v2.packet_analysis_tool", # 1 "gns3server.agent.gns3_copilot.tools_v2.vpcs_tools_netmiko", # 9 "gns3server.agent.gns3_copilot.utils.custom_netmiko.scripts.list_netmiko_telnet_devices", # 1 - "gns3server.agent.gns3_copilot.utils.message_converters", # 2 "gns3server.agent.gns3_copilot.utils.parse_tool_content", # 1 "gns3server.agent.mcp.appliances", # 1 "gns3server.agent.web_wireshark.docker_client", # 5 From 4ae6b5338d8988d1dacdf2e25fe1ca6ba9fc7b55 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:01:01 +0000 Subject: [PATCH 25/42] fix(typing): resolve mypy errors in agent.gns3_copilot.utils.parse_tool_content --- gns3server/agent/gns3_copilot/utils/parse_tool_content.py | 4 +--- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/gns3server/agent/gns3_copilot/utils/parse_tool_content.py b/gns3server/agent/gns3_copilot/utils/parse_tool_content.py index 4ea17912d..350a6bf50 100644 --- a/gns3server/agent/gns3_copilot/utils/parse_tool_content.py +++ b/gns3server/agent/gns3_copilot/utils/parse_tool_content.py @@ -214,9 +214,7 @@ def parse_tool_content( return content # Handle unsupported types - error_msg = ( # type: ignore[unreachable] - f"Content must be str, dict, list, int, float, bool, or None, got {type(content).__name__}" - ) + error_msg = f"Content must be str, dict, list, int, float, bool, or None, got {type(content).__name__}" logger.error(error_msg) if strict_mode: diff --git a/pyproject.toml b/pyproject.toml index 25a160936..3e96ccbdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -312,7 +312,6 @@ module = [ "gns3server.agent.gns3_copilot.tools_v2.packet_analysis_tool", # 1 "gns3server.agent.gns3_copilot.tools_v2.vpcs_tools_netmiko", # 9 "gns3server.agent.gns3_copilot.utils.custom_netmiko.scripts.list_netmiko_telnet_devices", # 1 - "gns3server.agent.gns3_copilot.utils.parse_tool_content", # 1 "gns3server.agent.mcp.appliances", # 1 "gns3server.agent.web_wireshark.docker_client", # 5 "gns3server.agent.web_wireshark.manage_wireshark", # 1 From 094fcf27479b8fc1567df41904f3ee872c942473 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:15:05 +0000 Subject: [PATCH 26/42] fix(typing): resolve mypy errors in schemas.compute.atm_switch_nodes --- gns3server/schemas/compute/atm_switch_nodes.py | 4 ++-- pyproject.toml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/gns3server/schemas/compute/atm_switch_nodes.py b/gns3server/schemas/compute/atm_switch_nodes.py index 9a9db0845..9d126bfe9 100644 --- a/gns3server/schemas/compute/atm_switch_nodes.py +++ b/gns3server/schemas/compute/atm_switch_nodes.py @@ -26,8 +26,8 @@ class ATMSwitchBase(BaseModel): Common ATM switch properties. """ - name: str = None - node_id: UUID = None + name: Optional[str] = None + node_id: Optional[UUID] = None usage: Optional[str] = None mappings: Optional[dict] = None diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..093342909 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -374,13 +374,10 @@ module = [ "gns3server.db.repositories.templates", # 6 "gns3server.db.repositories.users", # 12 "gns3server.db.tasks", # 12 - "gns3server.schemas.compute.atm_switch_nodes", # 5 "gns3server.schemas.compute.cloud_nodes", # 1 "gns3server.schemas.compute.docker_nodes", # 2 - "gns3server.schemas.compute.dynamips_nodes", # 1 "gns3server.schemas.compute.frame_relay_switch_nodes", # 5 "gns3server.schemas.compute.iou_nodes", # 3 - "gns3server.schemas.compute.nat_nodes", # 1 "gns3server.schemas.compute.qemu_nodes", # 1 "gns3server.schemas.compute.virtualbox_nodes", # 2 "gns3server.schemas.compute.vmware_nodes", # 3 From 672ef2e3cec155adb361a7660d91ef75920e3070 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:48:29 +0000 Subject: [PATCH 27/42] fix(typing): resolve mypy errors in schemas.compute.dynamips_nodes --- gns3server/schemas/compute/dynamips_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/schemas/compute/dynamips_nodes.py b/gns3server/schemas/compute/dynamips_nodes.py index d01020a28..6292b400b 100644 --- a/gns3server/schemas/compute/dynamips_nodes.py +++ b/gns3server/schemas/compute/dynamips_nodes.py @@ -124,7 +124,6 @@ class DynamipsBase(BaseModel): node_id: Optional[UUID] = None name: Optional[str] = None dynamips_id: Optional[int] = Field(None, description="Dynamips internal ID") - platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") ram: Optional[int] = Field(None, gt=0, description="Amount of RAM in MB") nvram: Optional[int] = Field(None, gt=0, description="Amount of NVRAM in KB") image: Optional[str] = Field(None, description="Path to the IOS image") @@ -185,11 +184,12 @@ class DynamipsUpdate(DynamipsBase): Properties to update a Dynamips node. """ - pass + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") class Dynamips(DynamipsBase): name: str + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") node_id: UUID project_id: UUID dynamips_id: int From a3c4c954315b8b06961a69d0f908bb208e434d9e Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:47:28 +0000 Subject: [PATCH 28/42] fix(typing): resolve mypy errors in schemas.compute.nat_nodes --- gns3server/schemas/compute/nat_nodes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gns3server/schemas/compute/nat_nodes.py b/gns3server/schemas/compute/nat_nodes.py index 95a703320..e3c569251 100644 --- a/gns3server/schemas/compute/nat_nodes.py +++ b/gns3server/schemas/compute/nat_nodes.py @@ -89,7 +89,7 @@ class NATBase(BaseModel): Common NAT node properties. """ - name: str + name: Optional[str] = None node_id: Optional[UUID] = None usage: Optional[str] = None ports_mapping: Optional[List[Union[EthernetPort, TAPPort, UDPPort]]] = Field( @@ -102,7 +102,7 @@ class NATCreate(NATBase): Properties to create a NAT node. """ - pass + name: str class NATUpdate(NATBase): @@ -110,10 +110,11 @@ class NATUpdate(NATBase): Properties to update a NAT node. """ - name: Optional[str] = None + pass class NAT(NATBase): + name: str project_id: UUID node_id: UUID ports_mapping: List[Union[EthernetPort, TAPPort, UDPPort]] From 10edb109df6e4ebd5e2b0755dcf03270dfccdf73 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:10:38 +0000 Subject: [PATCH 29/42] fix(typing): resolve mypy errors in api.routes.compute.dependencies.authentication --- .../api/routes/compute/dependencies/authentication.py | 9 +++++---- pyproject.toml | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gns3server/api/routes/compute/dependencies/authentication.py b/gns3server/api/routes/compute/dependencies/authentication.py index 14ba07f91..0412a4136 100644 --- a/gns3server/api/routes/compute/dependencies/authentication.py +++ b/gns3server/api/routes/compute/dependencies/authentication.py @@ -91,9 +91,9 @@ async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSock if not separator: raise invalid_user_credentials_exc - username = secrets.compare_digest(username, server_settings.compute_username) - password = secrets.compare_digest(password, server_settings.compute_password.get_secret_value()) - if not (username and password): + username_valid = secrets.compare_digest(username, server_settings.compute_username) + password_valid = secrets.compare_digest(password, server_settings.compute_password.get_secret_value()) + if not (username_valid and password_valid): raise invalid_user_credentials_exc except HTTPException as e: @@ -101,5 +101,6 @@ async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSock websocket_error = {"action": "log.error", "event": {"message": err_msg}} await websocket.send_json(websocket_error) log.error(err_msg) - return await websocket.close(code=1008) + await websocket.close(code=1008) + return None return websocket diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..ce388d188 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -323,7 +323,6 @@ module = [ "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 "gns3server.api.routes.compute.compute", # 1 - "gns3server.api.routes.compute.dependencies.authentication", # 3 "gns3server.api.routes.compute.docker_nodes", # 26 "gns3server.api.routes.compute.dynamips_nodes", # 9 "gns3server.api.routes.compute.ethernet_hub_nodes", # 9 From bb0f29b8e6f352373d2b692de282ce1f8d2190f8 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:43:01 +0000 Subject: [PATCH 30/42] fix(typing): resolve mypy errors in api.routes.compute.docker_nodes --- gns3server/api/routes/compute/docker_nodes.py | 14 ++++++++------ pyproject.toml | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py index 6091cf37b..9dee90dda 100644 --- a/gns3server/api/routes/compute/docker_nodes.py +++ b/gns3server/api/routes/compute/docker_nodes.py @@ -24,14 +24,16 @@ from fastapi import APIRouter, WebSocket, Depends, Body, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID -from typing import Union +from typing import Any, Union from gns3server import schemas from gns3server.compute.docker import Docker from gns3server.compute.docker.docker_vm import DockerVM from .dependencies.authentication import compute_authentication, ws_compute_authentication -responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}} +responses: dict[int | str, dict[str, Any]] = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"} +} router = APIRouter(responses=responses) @@ -53,13 +55,13 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM: responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}}, dependencies=[Depends(compute_authentication)], ) -async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker: +async def create_docker_node(project_id: UUID, node_create: schemas.DockerCreate) -> schemas.Docker: """ Create a new Docker node. """ docker_manager = Docker.instance() - node_data = jsonable_encoder(node_data, exclude_unset=True) + node_data = jsonable_encoder(node_create, exclude_unset=True) container = await docker_manager.create_node( node_data.pop("name"), str(project_id), @@ -124,7 +126,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker: @router.put("/{node_id}", response_model=schemas.Docker, dependencies=[Depends(compute_authentication)]) -async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker: +async def update_docker_node(node_update: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker: """ Update a Docker node. """ @@ -152,7 +154,7 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D ] changed = False - node_data = jsonable_encoder(node_data, exclude_unset=True) + node_data = jsonable_encoder(node_update, exclude_unset=True) for prop in props: # hasattr: startup_config_content only exists on IOLDockerVM if prop in node_data and hasattr(node, prop) and node_data[prop] != getattr(node, prop): diff --git a/pyproject.toml b/pyproject.toml index ce388d188..902af10ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -323,7 +323,6 @@ module = [ "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 "gns3server.api.routes.compute.compute", # 1 - "gns3server.api.routes.compute.docker_nodes", # 26 "gns3server.api.routes.compute.dynamips_nodes", # 9 "gns3server.api.routes.compute.ethernet_hub_nodes", # 9 "gns3server.api.routes.compute.ethernet_switch_nodes", # 16 From 904672559e742eec5ce0bfb42f2c91789a894a12 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:50:51 +0000 Subject: [PATCH 31/42] fix(typing): resolve mypy errors in api.routes.compute.dynamips_nodes --- .../api/routes/compute/dynamips_nodes.py | 25 +++++++++++-------- pyproject.toml | 1 - 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py index ce1c1b42b..ccfcd58fb 100644 --- a/gns3server/api/routes/compute/dynamips_nodes.py +++ b/gns3server/api/routes/compute/dynamips_nodes.py @@ -23,7 +23,7 @@ import os from fastapi import APIRouter, WebSocket, Body, Depends, status, HTTPException from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse -from typing import List, Union +from typing import Any, List, Optional, Union from uuid import UUID from gns3server.compute.dynamips import Dynamips @@ -32,7 +32,9 @@ from gns3server import schemas from .dependencies.authentication import compute_authentication, ws_compute_authentication -responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}} +responses: dict[int | str, dict[str, Any]] = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"} +} router = APIRouter(responses=responses) @@ -64,25 +66,26 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> dynamips_manager = Dynamips.instance() platform = node_data.platform + chassis: Optional[str] if not node_data.chassis and platform in DEFAULT_CHASSIS: chassis = DEFAULT_CHASSIS[platform] else: chassis = node_data.chassis - node_data = jsonable_encoder(node_data, exclude_unset=True) + data = jsonable_encoder(node_data, exclude_unset=True) vm = await dynamips_manager.create_node( - node_data.pop("name"), + data.pop("name"), str(project_id), - node_data.get("node_id"), - dynamips_id=node_data.get("dynamips_id"), + data.get("node_id"), + dynamips_id=data.get("dynamips_id"), platform=platform, - console=node_data.get("console"), - console_type=node_data.get("console_type", "telnet"), - aux=node_data.get("aux"), - aux_type=node_data.pop("aux_type", "none"), + console=data.get("console"), + console_type=data.get("console_type", "telnet"), + aux=data.get("aux"), + aux_type=data.pop("aux_type", "none"), chassis=chassis, node_type="dynamips", ) - await dynamips_manager.update_vm_settings(vm, node_data) + await dynamips_manager.update_vm_settings(vm, data) return vm.asdict() diff --git a/pyproject.toml b/pyproject.toml index 902af10ce..fb001c18a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -323,7 +323,6 @@ module = [ "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 "gns3server.api.routes.compute.compute", # 1 - "gns3server.api.routes.compute.dynamips_nodes", # 9 "gns3server.api.routes.compute.ethernet_hub_nodes", # 9 "gns3server.api.routes.compute.ethernet_switch_nodes", # 16 "gns3server.api.routes.compute.frame_relay_switch_nodes", # 9 From cacbc18ad11b544191817257a32a5d6804a8e90f Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:40:31 +0000 Subject: [PATCH 32/42] fix(typing): resolve mypy errors in api.routes.compute.ethernet_hub_nodes --- .../api/routes/compute/ethernet_hub_nodes.py | 23 +++++++++++-------- pyproject.toml | 1 - 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/gns3server/api/routes/compute/ethernet_hub_nodes.py b/gns3server/api/routes/compute/ethernet_hub_nodes.py index 350105789..150d18e64 100644 --- a/gns3server/api/routes/compute/ethernet_hub_nodes.py +++ b/gns3server/api/routes/compute/ethernet_hub_nodes.py @@ -18,6 +18,7 @@ API routes for Ethernet hub nodes. """ +from typing import Any import os from fastapi import APIRouter, Depends, Body, Path, status, HTTPException @@ -29,7 +30,9 @@ from gns3server.compute.dynamips import Dynamips from gns3server.compute.dynamips.nodes.ethernet_hub import EthernetHub from gns3server import schemas -responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"}} +responses: dict[int | str, dict[str, Any]] = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"} +} router = APIRouter(responses=responses) @@ -57,13 +60,13 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr # Use the Dynamips Ethernet hub to simulate this node dynamips_manager = Dynamips.instance() - node_data = jsonable_encoder(node_data, exclude_unset=True) + data = jsonable_encoder(node_data, exclude_unset=True) node = await dynamips_manager.create_node( - node_data.pop("name"), + data.pop("name"), str(project_id), - node_data.get("node_id"), + data.get("node_id"), node_type="ethernet_hub", - ports=node_data.get("ports_mapping"), + ports=data.get("ports_mapping"), ) return node.asdict() @@ -97,11 +100,11 @@ async def update_ethernet_hub( Update an Ethernet hub. """ - node_data = jsonable_encoder(node_data, exclude_unset=True) - if "name" in node_data and node.name != node_data["name"]: - await node.set_name(node_data["name"]) - if "ports_mapping" in node_data: - node.ports_mapping = node_data["ports_mapping"] + data = jsonable_encoder(node_data, exclude_unset=True) + if "name" in data and node.name != data["name"]: + await node.set_name(data["name"]) + if "ports_mapping" in data: + node.ports_mapping = data["ports_mapping"] node.updated() return node.asdict() diff --git a/pyproject.toml b/pyproject.toml index fb001c18a..1ec09fbe7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -323,7 +323,6 @@ module = [ "gns3server.api.routes.compute.capabilities", # 1 "gns3server.api.routes.compute.cloud_nodes", # 14 "gns3server.api.routes.compute.compute", # 1 - "gns3server.api.routes.compute.ethernet_hub_nodes", # 9 "gns3server.api.routes.compute.ethernet_switch_nodes", # 16 "gns3server.api.routes.compute.frame_relay_switch_nodes", # 9 "gns3server.api.routes.compute.iou_nodes", # 15 From 93419fc33a0ceab75f5288b7fa126c04766fd713 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 06:26:18 +0000 Subject: [PATCH 33/42] fix(typing): resolve mypy errors in api.routes.controller.nodes --- gns3server/api/routes/controller/nodes.py | 47 +++++++++-------------- pyproject.toml | 1 - 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index 0938ab087..8cd7c0ade 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -27,7 +27,7 @@ from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, Request, from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from fastapi.routing import APIRoute -from typing import List, Callable, Optional +from typing import Any, List, Callable, Optional from uuid import UUID from gns3server.controller import Controller @@ -47,7 +47,7 @@ import logging log = logging.getLogger(__name__) -node_locks = {} +node_locks: dict[str, dict[str, Any]] = {} class NodeConcurrency(APIRoute): @@ -84,7 +84,9 @@ class NodeConcurrency(APIRoute): return custom_route_handler -responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}} +responses: dict[int | str, dict[str, Any]] = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"} +} router = APIRouter(route_class=NodeConcurrency, responses=responses) @@ -127,7 +129,7 @@ def _check_node_type(node: Node, *required_types: str) -> None: }, dependencies=[Depends(has_privilege("Node.Allocate"))], ) -async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node: +async def create_node(node_create: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node: """ Create a new node. @@ -135,8 +137,8 @@ async def create_node(node_data: schemas.NodeCreate, project: Project = Depends( """ controller = Controller.instance() - compute = controller.get_compute(str(node_data.compute_id)) - node_data = jsonable_encoder(node_data, exclude_unset=True) + compute = controller.get_compute(str(node_create.compute_id)) + node_data = jsonable_encoder(node_create, exclude_unset=True) node = await project.add_node(compute, node_data.pop("name"), node_data.pop("node_id", None), **node_data) return node.asdict() @@ -274,14 +276,14 @@ async def get_node(node: Node = Depends(dep_node)) -> schemas.Node: response_model_exclude_unset=True, dependencies=[Depends(has_privilege("Node.Modify"))], ) -async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node: +async def update_node(node_update: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node: """ Update a node. Required privilege: Node.Modify """ - node_data = jsonable_encoder(node_data, exclude_unset=True) + node_data = jsonable_encoder(node_update, exclude_unset=True) # Ignore these because we only use them when creating a node node_data.pop("node_id", None) @@ -654,9 +656,8 @@ async def ws_console( return compute = node.compute - log.info( - f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket" - ) + client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown" + log.info(f"New client {client} has connected to controller console WebSocket") compute_host = compute.host try: @@ -690,9 +691,7 @@ async def ws_console( await ws_console_compute.send_bytes(msg["bytes"]) except WebSocketDisconnect: pass - log.info( - f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller console WebSocket" - ) + log.info(f"Client {client} has disconnected from controller console WebSocket") async def ws_send(ws_console_compute): """ @@ -709,10 +708,7 @@ async def ws_console( break except WebSocketDisconnect: # the client disconnected while the compute was still streaming console output - log.info( - f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller" - f" console WebSocket" - ) + log.info(f"Client {client} has disconnected from controller console WebSocket") try: # forward WebSocket data in both directions between the client and the compute console WebSocket @@ -763,9 +759,8 @@ async def vnc_console( return compute = node.compute - log.info( - f"New client {websocket.client.host}:{websocket.client.port} has connected to controller VNC console WebSocket" - ) + client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown" + log.info(f"New client {client} has connected to controller VNC console WebSocket") compute_host = compute.host try: @@ -796,10 +791,7 @@ async def vnc_console( await vnc_console_compute.send_bytes(data) except WebSocketDisconnect: pass - log.info( - f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller" - f" VNC console WebSocket" - ) + log.info(f"Client {client} has disconnected from controller VNC console WebSocket") async def vnc_send(vnc_console_compute): """ @@ -814,10 +806,7 @@ async def vnc_console( break except WebSocketDisconnect: # the client disconnected while the compute was still streaming VNC console output - log.info( - f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller" - f" VNC console WebSocket" - ) + log.info(f"Client {client} has disconnected from controller VNC console WebSocket") try: # forward WebSocket data in both directions between the client and the compute VNC console WebSocket diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..6040d3cfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -347,7 +347,6 @@ module = [ "gns3server.api.routes.controller.groups", # 6 "gns3server.api.routes.controller.images", # 9 "gns3server.api.routes.controller.llm_model_configs", # 32 - "gns3server.api.routes.controller.nodes", # 13 "gns3server.api.routes.controller.pools", # 8 "gns3server.api.routes.controller.privileges", # 1 "gns3server.api.routes.controller.projects", # 16 From 2cf90dfce64dfb0306bcdfbc9f291225b90d7da4 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:58:39 +0000 Subject: [PATCH 34/42] fix(typing): resolve mypy errors in api.routes.controller.privileges --- gns3server/api/routes/controller/privileges.py | 3 ++- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/api/routes/controller/privileges.py b/gns3server/api/routes/controller/privileges.py index a070a6ee6..1f34ec1ff 100644 --- a/gns3server/api/routes/controller/privileges.py +++ b/gns3server/api/routes/controller/privileges.py @@ -16,6 +16,7 @@ API route for privileges """ from typing import List +import gns3server.db.models as models from gns3server.db.repositories.rbac import RbacRepository from .dependencies.database import get_repository from fastapi import APIRouter, Depends @@ -33,7 +34,7 @@ router = APIRouter() ) async def get_privileges( rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)), -) -> List[schemas.Privilege]: +) -> List[models.Privilege]: """ Get all privileges. diff --git a/pyproject.toml b/pyproject.toml index 6040d3cfd..8d12aad82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -348,7 +348,6 @@ module = [ "gns3server.api.routes.controller.images", # 9 "gns3server.api.routes.controller.llm_model_configs", # 32 "gns3server.api.routes.controller.pools", # 8 - "gns3server.api.routes.controller.privileges", # 1 "gns3server.api.routes.controller.projects", # 16 "gns3server.api.routes.controller.roles", # 5 "gns3server.api.routes.controller.snapshots", # 1 From c340b18ba2467b80df4cdf3544cf513afa39262d Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:16:38 +0000 Subject: [PATCH 35/42] fix(typing): resolve mypy errors in api.routes.controller.roles --- gns3server/api/routes/controller/roles.py | 13 +++++++------ pyproject.toml | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gns3server/api/routes/controller/roles.py b/gns3server/api/routes/controller/roles.py index aab6e5a00..3f6e854a1 100644 --- a/gns3server/api/routes/controller/roles.py +++ b/gns3server/api/routes/controller/roles.py @@ -21,7 +21,7 @@ API routes for roles. from fastapi import APIRouter, Depends, status from uuid import UUID -from typing import List +from typing import List, Optional from gns3server import schemas from gns3server.controller.controller_error import ( @@ -31,6 +31,7 @@ from gns3server.controller.controller_error import ( ControllerForbiddenError, ) +import gns3server.db.models as models from gns3server.db.repositories.rbac import RbacRepository from .dependencies.database import get_repository from .dependencies.rbac import has_privilege @@ -43,7 +44,7 @@ router = APIRouter() @router.get("", response_model=List[schemas.Role], dependencies=[Depends(has_privilege("Role.Audit"))]) -async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[schemas.Role]: +async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[models.Role]: """ Get all roles. @@ -61,7 +62,7 @@ async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepos ) async def create_role( role_create: schemas.RoleCreate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)) -) -> schemas.Role: +) -> Optional[models.Role]: """ Create a new role. @@ -78,7 +79,7 @@ async def create_role( async def get_role( role_id: UUID, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)), -) -> schemas.Role: +) -> models.Role: """ Get a role. @@ -94,7 +95,7 @@ async def get_role( @router.put("/{role_id}", response_model=schemas.Role, dependencies=[Depends(has_privilege("Role.Modify"))]) async def update_role( role_id: UUID, role_update: schemas.RoleUpdate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)) -) -> schemas.Role: +) -> Optional[models.Role]: """ Update a role. @@ -142,7 +143,7 @@ async def delete_role( ) async def get_role_privileges( role_id: UUID, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)) -) -> List[schemas.Privilege]: +) -> List[models.Privilege]: """ Get all role privileges. diff --git a/pyproject.toml b/pyproject.toml index 8d12aad82..8eb27c2a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -349,7 +349,6 @@ module = [ "gns3server.api.routes.controller.llm_model_configs", # 32 "gns3server.api.routes.controller.pools", # 8 "gns3server.api.routes.controller.projects", # 16 - "gns3server.api.routes.controller.roles", # 5 "gns3server.api.routes.controller.snapshots", # 1 "gns3server.api.routes.controller.templates", # 10 "gns3server.api.routes.controller.users", # 14 From ebe6dee916c1605e48e2b1ec7d700708ea11383a Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:57:39 +0000 Subject: [PATCH 36/42] fix(typing): resolve mypy errors in api.routes.controller.snapshots --- gns3server/api/routes/controller/snapshots.py | 6 ++++-- pyproject.toml | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/controller/snapshots.py b/gns3server/api/routes/controller/snapshots.py index f85b5d9ef..4ec8c467c 100644 --- a/gns3server/api/routes/controller/snapshots.py +++ b/gns3server/api/routes/controller/snapshots.py @@ -24,7 +24,7 @@ import logging log = logging.getLogger() from fastapi import APIRouter, Depends, status -from typing import List +from typing import Any, List from uuid import UUID from gns3server.controller.project import Project @@ -35,7 +35,9 @@ from gns3server.controller import Controller from .dependencies.database import get_repository from .dependencies.rbac import has_privilege -responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or snapshot"}} +responses: dict[int | str, dict[str, Any]] = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or snapshot"} +} router = APIRouter(responses=responses) diff --git a/pyproject.toml b/pyproject.toml index 8eb27c2a8..8026bab8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -349,7 +349,6 @@ module = [ "gns3server.api.routes.controller.llm_model_configs", # 32 "gns3server.api.routes.controller.pools", # 8 "gns3server.api.routes.controller.projects", # 16 - "gns3server.api.routes.controller.snapshots", # 1 "gns3server.api.routes.controller.templates", # 10 "gns3server.api.routes.controller.users", # 14 "gns3server.api.server", # 2 From d783ccfc0d8ee99ac4941eb8e9503e0402740a79 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 04:48:54 +0000 Subject: [PATCH 37/42] fix(typing): resolve mypy errors in controller.topology --- gns3server/controller/topology.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 5048be4ce..f2390930a 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -53,7 +53,7 @@ GNS3_FILE_FORMAT_REVISION = 10 class DynamipsNodeValidation(DynamipsCreate): - name: Optional[str] = None + name: Optional[str] = None # type: ignore[assignment] def _check_topology_schema(topo, path): diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..d2f066c64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -364,7 +364,6 @@ module = [ "gns3server.controller.appliance", # 2 "gns3server.controller.appliance_manager", # 5 "gns3server.controller.project", # 2 - "gns3server.controller.topology", # 1 "gns3server.db.repositories.api_keys", # 8 "gns3server.db.repositories.computes", # 10 "gns3server.db.repositories.images", # 14 From 07560b0026cb55dfed639180d86cd44bf53ac09f Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:06:09 +0000 Subject: [PATCH 38/42] fix(typing): resolve mypy errors in agent --- gns3server/agent/__init__.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/agent/__init__.py b/gns3server/agent/__init__.py index dee2aaed1..082c6dd4a 100644 --- a/gns3server/agent/__init__.py +++ b/gns3server/agent/__init__.py @@ -57,7 +57,7 @@ except ImportError as e: AI_COPILOT_AVAILABLE = False # Provide stub functions that raise helpful errors - async def get_project_agent_manager(): + async def get_project_agent_manager() -> "ProjectAgentManager": """ Get the global ProjectAgentManager singleton instance. @@ -68,7 +68,7 @@ except ImportError as e: "AI Copilot is not available. Install AI dependencies with: pip install gns3-server[ai-features]" ) - class ProjectAgentManager: + class ProjectAgentManager: # type: ignore[no-redef] """ Stub class for ProjectAgentManager when AI dependencies are not installed. """ diff --git a/pyproject.toml b/pyproject.toml index 258447eb6..87f19d76b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -294,7 +294,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] # Baseline: modules with existing type errors (error count at baseline time). # Remove modules from this list one small PR at a time. Never add new ones. module = [ - "gns3server.agent", # 2 "gns3server.agent.gns3_copilot.agent.context_manager", # 5 "gns3server.agent.gns3_copilot.agent.model_factory", # 4 "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 From 9882eba08812447e35e430174e5716a9c510d205 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 06:11:44 +0000 Subject: [PATCH 39/42] fix(typing): resolve mypy errors in agent.gns3_copilot.chat_sessions_repository --- gns3server/agent/gns3_copilot/chat_sessions_repository.py | 8 +++++--- pyproject.toml | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gns3server/agent/gns3_copilot/chat_sessions_repository.py b/gns3server/agent/gns3_copilot/chat_sessions_repository.py index 4d18d8852..41006f299 100644 --- a/gns3server/agent/gns3_copilot/chat_sessions_repository.py +++ b/gns3server/agent/gns3_copilot/chat_sessions_repository.py @@ -125,7 +125,7 @@ class ChatSessionsRepository: project_id: str, title: str = "New Conversation", copilot_mode: Optional[str] = None, - ) -> ChatSession: + ) -> Optional[ChatSession]: """ Create a new chat session. @@ -158,6 +158,8 @@ class ChatSessionsRepository: session_id = cursor.lastrowid log.info("Created chat session: id=%s, thread_id=%s, copilot_mode=%s", session_id, thread_id, copilot_mode) + if session_id is None: + return None return await self.get_session_by_id(session_id) async def get_session_by_id(self, session_id: int) -> Optional[ChatSession]: @@ -214,7 +216,7 @@ class ChatSessionsRepository: List of ChatSession """ query = "SELECT * FROM chat_sessions" - params = [] + params: List[Any] = [] conditions = [] if user_id: @@ -268,7 +270,7 @@ class ChatSessionsRepository: Updated ChatSession or None """ updates = [] - params = [] + params: List[Any] = [] now = datetime.utcnow().isoformat() diff --git a/pyproject.toml b/pyproject.toml index 87f19d76b..a2e9c2872 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -296,7 +296,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] module = [ "gns3server.agent.gns3_copilot.agent.context_manager", # 5 "gns3server.agent.gns3_copilot.agent.model_factory", # 4 - "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 From bc27c4814fe8cebbc3040ae3732bbebe7370e49e Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 06:37:23 +0000 Subject: [PATCH 40/42] fix(typing): resolve mypy errors in agent.gns3_copilot.gns3_client.api_handlers --- .../agent/gns3_copilot/gns3_client/api_handlers.py | 14 +++++++------- .../gns3_copilot/tools_v2/gns3_create_link.py | 2 ++ .../gns3_copilot/tools_v2/gns3_create_node.py | 2 ++ pyproject.toml | 1 - 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py b/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py index b0934c201..4f7105881 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py +++ b/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py @@ -162,14 +162,14 @@ def build_gns3_ctx(jwt_token: str | None = None, url: str | None = None) -> dict } -def _filter_node_response(node: dict, fields: list[str] = None) -> dict: +def _filter_node_response(node: dict, fields: list[str] | None = None) -> dict: """Filter node response to only include requested fields.""" if not fields: fields = ["node_id", "name", "node_type", "status", "console"] return {k: node[k] for k in fields if k in node} -def _filter_link_response(link: dict, fields: list[str] = None) -> dict: +def _filter_link_response(link: dict, fields: list[str] | None = None) -> dict: """Filter link response to only include requested fields.""" if not fields: fields = LINK_DEFAULT_FIELDS @@ -332,7 +332,7 @@ def suspend_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> di return {"message": f"Node {node_id} suspended", "node_id": node_id} -def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: @@ -401,7 +401,7 @@ def create_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic return _filter_node_response(resp, fields) -def delete_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def delete_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} @@ -706,7 +706,7 @@ def available_filters_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) return conn.http_call("get", url).json() -def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} @@ -762,7 +762,7 @@ def create_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic return _filter_link_response(resp, fields) -def delete_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def delete_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} @@ -809,7 +809,7 @@ def update_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dic # ── Link capture / reset handlers ────────────────────────────────────── -def reset_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def reset_link_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_create_link.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_create_link.py index 8f926586b..ff11c6a44 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_create_link.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_create_link.py @@ -222,6 +222,8 @@ class GNS3LinkTool(BaseTool): }, gns3_ctx, ) + if isinstance(link_resp, list): + raise RuntimeError("Unexpected batch response when creating a single link") if "error" in link_resp: raise RuntimeError(link_resp["error"]) diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_create_node.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_create_node.py index a511de21f..bdbc0bf42 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_create_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_create_node.py @@ -230,6 +230,8 @@ class GNS3CreateNodeTool(BaseTool): }, gns3_ctx, ) + if isinstance(created, list): + raise RuntimeError("Unexpected batch response when creating a single node") if "error" in created: raise RuntimeError(created["error"]) node_info = { diff --git a/pyproject.toml b/pyproject.toml index a2e9c2872..ee860c022 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -296,7 +296,6 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] module = [ "gns3server.agent.gns3_copilot.agent.context_manager", # 5 "gns3server.agent.gns3_copilot.agent.model_factory", # 4 - "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 "gns3server.agent.gns3_copilot.skills.loader", # 1 From 780b7f2ab6b6375067eef65ad37362240aebe351 Mon Sep 17 00:00:00 2001 From: Mark Paronyan Date: Mon, 28 Sep 2026 05:04:32 +0000 Subject: [PATCH 41/42] fix(typing): resolve mypy errors in agent node lifecycle tools --- gns3server/agent/gns3_copilot/gns3_client/api_handlers.py | 6 +++--- gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py | 6 +++++- gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py | 6 +++++- gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py | 6 +++++- pyproject.toml | 3 --- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py b/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py index 4f7105881..7f6ca8c95 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py +++ b/gns3server/agent/gns3_copilot/gns3_client/api_handlers.py @@ -278,7 +278,7 @@ def _batch_lifecycle(project_id, node_ids, action, conn, action_label): return list(pool.map(_act, node_ids)) -def start_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def start_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} @@ -296,7 +296,7 @@ def start_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict return {"message": f"Node {node_id} started", "node_id": node_id} -def stop_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def stop_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} @@ -314,7 +314,7 @@ def stop_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[ return {"message": f"Node {node_id} stopped", "node_id": node_id} -def suspend_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any]: +def suspend_node_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]]: project_id = params.get("project_id") if not project_id: return {"error": "project_id is required"} diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py index 8e98c7480..4086cde1b 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py @@ -136,7 +136,11 @@ class GNS3StartNodeTool(BaseTool): results = [] known_ids = [nid for nid in node_ids if nid in nodes_by_id] start_results = start_node_handler({"project_id": project_id, "node_ids": known_ids}, gns3_ctx) - start_errors = {r["node_id"]: r.get("error") for r in start_results if r.get("status") == "error"} + start_errors = ( + {r["node_id"]: r.get("error") for r in start_results if r.get("status") == "error"} + if isinstance(start_results, list) + else {} + ) # Get immediate status (likely 'starting' or 'stopped') — one call listing = get_nodes_handler({"project_id": project_id}, gns3_ctx) diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py index 3c0d8ef2c..d6965f19b 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py @@ -124,7 +124,11 @@ class GNS3StopNodeTool(BaseTool): results = [] known_ids = [nid for nid in node_ids if nid in nodes_by_id] stop_results = stop_node_handler({"project_id": project_id, "node_ids": known_ids}, gns3_ctx) - stop_errors = {r["node_id"]: r.get("error") for r in stop_results if r.get("status") == "error"} + stop_errors = ( + {r["node_id"]: r.get("error") for r in stop_results if r.get("status") == "error"} + if isinstance(stop_results, list) + else {} + ) # Get updated status — one call listing = get_nodes_handler({"project_id": project_id}, gns3_ctx) diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py index 74f9ba883..47a0cc4b1 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py @@ -130,7 +130,11 @@ class GNS3SuspendNodeTool(BaseTool): results = [] known_ids = [nid for nid in node_ids if nid in nodes_by_id] suspend_results = suspend_node_handler({"project_id": project_id, "node_ids": known_ids}, gns3_ctx) - suspend_errors = {r["node_id"]: r.get("error") for r in suspend_results if r.get("status") == "error"} + suspend_errors = ( + {r["node_id"]: r.get("error") for r in suspend_results if r.get("status") == "error"} + if isinstance(suspend_results, list) + else {} + ) # Get updated status — one call listing = get_nodes_handler({"project_id": project_id}, gns3_ctx) diff --git a/pyproject.toml b/pyproject.toml index ee860c022..9ccaef978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -303,9 +303,6 @@ module = [ "gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.display_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.gns3_packet_filter", # 1 - "gns3server.agent.gns3_copilot.tools_v2.gns3_start_node", # 3 - "gns3server.agent.gns3_copilot.tools_v2.gns3_stop_node", # 3 - "gns3server.agent.gns3_copilot.tools_v2.gns3_suspend_node", # 3 "gns3server.agent.gns3_copilot.tools_v2.packet_analysis_tool", # 1 "gns3server.agent.gns3_copilot.tools_v2.vpcs_tools_netmiko", # 9 "gns3server.agent.gns3_copilot.utils.custom_netmiko.scripts.list_netmiko_telnet_devices", # 1 From a51af4a32eedecd8a803ab423f4a97581595db53 Mon Sep 17 00:00:00 2001 From: Jeremy Grossmann Date: Tue, 29 Sep 2026 12:35:33 +0200 Subject: [PATCH 42/42] Fix module list after merging Removed unused modules from the project configuration. --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6677635c2..1bb396c59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -297,10 +297,6 @@ module = [ "gns3server.agent", # 2 "gns3server.agent.gns3_copilot.chat_sessions_repository", # 8 "gns3server.agent.gns3_copilot.gns3_client.api_handlers", # 10 - "gns3server.agent.gns3_copilot.gns3_client.connector_factory", # 1 - "gns3server.agent.gns3_copilot.gns3_client.project_inventory", # 2 - "gns3server.agent.gns3_copilot.skills.loader", # 1 - "gns3server.agent.gns3_copilot.skills.manager", # 4 "gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.display_tools_nornir", # 6 "gns3server.agent.gns3_copilot.tools_v2.gns3_packet_filter", # 1