diff --git a/gns3server/agent/__init__.py b/gns3server/agent/__init__.py index 082c6dd4a..bf6ecac3e 100644 --- a/gns3server/agent/__init__.py +++ b/gns3server/agent/__init__.py @@ -103,6 +103,6 @@ except ImportError: __all__ = [ "AI_COPILOT_AVAILABLE", "MCP_AVAILABLE", - "get_project_agent_manager", "ProjectAgentManager", + "get_project_agent_manager", ] diff --git a/gns3server/agent/gns3_copilot/agent/context_manager.py b/gns3server/agent/gns3_copilot/agent/context_manager.py index 5337c3d95..a17338c83 100644 --- a/gns3server/agent/gns3_copilot/agent/context_manager.py +++ b/gns3server/agent/gns3_copilot/agent/context_manager.py @@ -391,10 +391,10 @@ def create_pre_model_hook( max_tokens_for_trim = max_input_tokens - tool_tokens # Validate budget and provide actionable warnings - if system_tokens + tool_tokens > max_input_tokens: # noqa: E501 + if system_tokens + tool_tokens > max_input_tokens: logger.error( "System prompt (%d tokens) + tools (%d tokens) EXCEED input " - "budget (%d tokens). This will likely cause LLM call failures. " # noqa: E501 + "budget (%d tokens). This will likely cause LLM call failures. " "Recommendations: 1) Reduce system prompt length, 2) Reduce " "number of tools, 3) Use a model with larger context window, " "or 4) Switch to 'conservative' strategy.", @@ -524,7 +524,7 @@ def _inject_topology_into_system( if filtered_count > 0: logger.debug("Filtered out %d existing SystemMessage(s)", filtered_count) - return [SystemMessage(content=formatted_prompt)] + non_system_messages + return [SystemMessage(content=formatted_prompt), *non_system_messages] # ============================================================================ @@ -556,7 +556,7 @@ def prepare_context_messages( else: formatted_prompt = system_prompt.replace("{{topology_info}}", "(No topology information available)") - return [SystemMessage(content=formatted_prompt)] + state_messages + return [SystemMessage(content=formatted_prompt), *state_messages] # ============================================================================ diff --git a/gns3server/agent/gns3_copilot/agent/gns3_copilot.py b/gns3server/agent/gns3_copilot/agent/gns3_copilot.py index a9616eb8b..ac784cab0 100644 --- a/gns3server/agent/gns3_copilot/agent/gns3_copilot.py +++ b/gns3server/agent/gns3_copilot/agent/gns3_copilot.py @@ -527,7 +527,7 @@ def tool_node(state: dict, config: RunnableConfig | None = None): ) except Exception as e: logger.error("Tool %s failed: %s", tool_name, e, exc_info=True) - observation = f"Error: {str(e)}" + observation = f"Error: {e!s}" # Serialize observation to JSON string if it's not already a string # This ensures ToolMessage.content is always JSON format, not Python diff --git a/gns3server/agent/gns3_copilot/gns3_client/__init__.py b/gns3server/agent/gns3_copilot/gns3_client/__init__.py index 100ed500c..16fb366c5 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/__init__.py +++ b/gns3server/agent/gns3_copilot/gns3_client/__init__.py @@ -71,16 +71,16 @@ __description__ = "AI-powered network automation assistant for GNS3" __url__ = "https://github.com/yueguobin/gns3-copilot" __all__ = [ + "GNS3ProjectInfoTool", + "GNS3TopologyTool", "Gns3Connector", "build_gns3_ctx", - "GNS3TopologyTool", - "GNS3ProjectInfoTool", + "get_current_jwt_token", + "get_current_llm_config", "get_gns3_connector", "get_gns3_connector_with_llm_config", "get_gns3_server_host", "get_llm_config", "set_current_jwt_token", - "get_current_jwt_token", "set_current_llm_config", - "get_current_llm_config", ] diff --git a/gns3server/agent/gns3_copilot/gns3_client/connector.py b/gns3server/agent/gns3_copilot/gns3_client/connector.py index 40ce1c5e5..21f7fc58d 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/connector.py +++ b/gns3server/agent/gns3_copilot/gns3_client/connector.py @@ -175,7 +175,7 @@ class Gns3Connector: else: raise HTTPError(f"v3 API authentication failed: {response.status_code} - {response.text}") except Exception as e: - raise HTTPError(f"v3 API authentication error: {str(e)}") from e + raise HTTPError(f"v3 API authentication error: {e!s}") from e def _is_token_expired(self) -> bool: """ @@ -273,7 +273,7 @@ class Gns3Connector: except Exception: # If JSON parsing fails, return error with original text return HTTPError( - f"Original Error: {str(e)}. GNS3 response text: {response.text}", + f"Original Error: {e!s}. GNS3 response text: {response.text}", response=response, ) return e diff --git a/gns3server/agent/gns3_copilot/gns3_client/context_helpers.py b/gns3server/agent/gns3_copilot/gns3_client/context_helpers.py index 5070dcb9a..b65cf88cd 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/context_helpers.py +++ b/gns3server/agent/gns3_copilot/gns3_client/context_helpers.py @@ -124,8 +124,8 @@ def get_current_llm_config() -> Optional[dict]: __all__ = [ - "set_current_jwt_token", "get_current_jwt_token", - "set_current_llm_config", "get_current_llm_config", + "set_current_jwt_token", + "set_current_llm_config", ] diff --git a/gns3server/agent/gns3_copilot/gns3_client/gns3_project_info.py b/gns3server/agent/gns3_copilot/gns3_client/gns3_project_info.py index 9b699e51f..3ed793580 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/gns3_project_info.py +++ b/gns3server/agent/gns3_copilot/gns3_client/gns3_project_info.py @@ -143,7 +143,7 @@ class GNS3ProjectInfoTool(BaseTool): except Exception as e: logger.error("Error retrieving GNS3 project info: %s", str(e)) - return {"error": f"Failed to retrieve project info: {str(e)}"} + return {"error": f"Failed to retrieve project info: {e!s}"} if __name__ == "__main__": diff --git a/gns3server/agent/gns3_copilot/gns3_client/gns3_topology_reader.py b/gns3server/agent/gns3_copilot/gns3_client/gns3_topology_reader.py index ce5449079..208d7074f 100644 --- a/gns3server/agent/gns3_copilot/gns3_client/gns3_topology_reader.py +++ b/gns3server/agent/gns3_copilot/gns3_client/gns3_topology_reader.py @@ -145,7 +145,7 @@ class GNS3TopologyTool(BaseTool): except Exception as e: logger.error("Error retrieving GNS3 topology: %s", str(e)) - return {"error": f"Failed to retrieve topology: {str(e)}"} + return {"error": f"Failed to retrieve topology: {e!s}"} def _clean_nodes_ports(self, data: dict) -> dict: """ diff --git a/gns3server/agent/gns3_copilot/skills/__init__.py b/gns3server/agent/gns3_copilot/skills/__init__.py index c23c9d8b8..6dd522dca 100644 --- a/gns3server/agent/gns3_copilot/skills/__init__.py +++ b/gns3server/agent/gns3_copilot/skills/__init__.py @@ -55,18 +55,18 @@ from .manager import SkillsManager from .loader import SkillsLoader __all__ = [ - "SKILLS_REGISTRY", "INJECTION_SKILLS_REGISTRY", - "get_skill", - "get_injection_skill", + "SKILLS_REGISTRY", "DeviceSkillsTool", "InjectionSkillsTool", "PacketAnalysisSkillsTool", - "SkillsManager", "SkillsLoader", - "set_skills_manager", + "SkillsManager", + "get_injection_skill", + "get_skill", "get_skills_manager", + "get_skills_repository_info", "reload_injection_skills", "reload_skills_repository", - "get_skills_repository_info", + "set_skills_manager", ] diff --git a/gns3server/agent/gns3_copilot/skills/loader.py b/gns3server/agent/gns3_copilot/skills/loader.py index 0295e2520..aaad14e51 100644 --- a/gns3server/agent/gns3_copilot/skills/loader.py +++ b/gns3server/agent/gns3_copilot/skills/loader.py @@ -278,7 +278,7 @@ class SkillsLoader: return "" try: - with open(prompt_file, "r", encoding="utf-8") as f: + with open(prompt_file, encoding="utf-8") as f: content = f.read() logger.debug(f"Loaded prompt: {prompt_name} from {prompt_file}") return content @@ -302,7 +302,7 @@ class SkillsLoader: try: commands = [] - with open(config_file, "r", encoding="utf-8") as f: + with open(config_file, encoding="utf-8") as f: for line in f: line = line.strip() if not line or line.startswith("#"): @@ -364,7 +364,7 @@ class SkillsLoader: Returns: Parsed YAML content as dictionary, or empty dict if file is empty """ - with open(file_path, "r", encoding="utf-8") as f: + with open(file_path, encoding="utf-8") as f: data = yaml.safe_load(f) if not isinstance(data, dict): logger.warning(f"YAML file {file_path} is empty or has invalid format") diff --git a/gns3server/agent/gns3_copilot/skills/manager.py b/gns3server/agent/gns3_copilot/skills/manager.py index 1a511ec29..3195b4611 100644 --- a/gns3server/agent/gns3_copilot/skills/manager.py +++ b/gns3server/agent/gns3_copilot/skills/manager.py @@ -89,7 +89,7 @@ class SkillsManager: self.branch = branch self.auto_update = auto_update self.loader = SkillsLoader(str(local_path)) - self._repo: Optional["git.Repo"] = None + self._repo: Optional[git.Repo] = None self._prompt_count = 0 if not GIT_AVAILABLE: diff --git a/gns3server/agent/gns3_copilot/skills/registry.py b/gns3server/agent/gns3_copilot/skills/registry.py index d9e2b5083..aa67cd51b 100644 --- a/gns3server/agent/gns3_copilot/skills/registry.py +++ b/gns3server/agent/gns3_copilot/skills/registry.py @@ -239,7 +239,7 @@ def reload_injection_skills() -> dict[str, Any]: logger.error(f"Error during skills reload: {e}") return { "success": False, - "message": f"Error: {str(e)}", + "message": f"Error: {e!s}", "skill_count": len(INJECTION_SKILLS_REGISTRY), "version": "", } @@ -278,7 +278,7 @@ def reload_prompts() -> dict[str, Any]: manager = get_skills_manager() return { "success": False, - "message": f"Error: {str(e)}", + "message": f"Error: {e!s}", "prompt_count": manager.get_prompt_count() if manager else 0, "version": "", } @@ -314,7 +314,7 @@ def reload_forbidden_commands() -> dict[str, Any]: } except Exception as e: logger.error(f"Error during forbidden commands reload: {e}") - return {"success": False, "message": f"Error: {str(e)}", "command_count": 0, "version": ""} + return {"success": False, "message": f"Error: {e!s}", "command_count": 0, "version": ""} def get_prompt(prompt_name: str) -> str: diff --git a/gns3server/agent/gns3_copilot/tools_v2/__init__.py b/gns3server/agent/gns3_copilot/tools_v2/__init__.py index 18f122727..aacf4423f 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/__init__.py +++ b/gns3server/agent/gns3_copilot/tools_v2/__init__.py @@ -80,17 +80,17 @@ __url__ = "https://github.com/yueguobin/gns3-copilot" # Export main tool classes __all__ = [ - "ExecuteMultipleDeviceConfigCommands", "ExecuteMultipleDeviceCommands", + "ExecuteMultipleDeviceConfigCommands", "GNS3CreateNodeTool", "GNS3LinkTool", "GNS3PacketFilterTool", - "GNS3StartNodeTool", "GNS3StartNodeQuickTool", + "GNS3StartNodeTool", "GNS3StopNodeTool", "GNS3SuspendNodeTool", - "GNS3UpdateNodeNameTool", "GNS3TemplateTool", + "GNS3UpdateNodeNameTool", "GNS3WaitTool", "PacketAnalysisTool", ] 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 ed15f0589..250fb559d 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py +++ b/gns3server/agent/gns3_copilot/tools_v2/config_tools_nornir.py @@ -57,7 +57,7 @@ from gns3server.agent.gns3_copilot.utils.command_filter import ( # Import custom Netmiko device types for GNS3 emulation # This registers gns3_huawei_telnet_ce and other custom device types # NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered -from gns3server.agent.gns3_copilot.utils import custom_netmiko # noqa: F401 +from gns3server.agent.gns3_copilot.utils import custom_netmiko # Explicitly register custom device types to ensure they are available # This is a safety measure in case the auto-registration on import doesn't work @@ -255,7 +255,7 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool): except Exception as e: # Overall execution failed logger.error("Error executing configurations on all devices: %s", e) - return [{"status": "failed", "error": f"Execution error: {str(e)}"}] + return [{"status": "failed", "error": f"Execution error: {e!s}"}] logger.info( "Multiple device configuration execution completed. Results: %s", @@ -285,7 +285,7 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool): ) return Result( host=task.host, - result=f"Configuration failed (ReadTimeout): {str(e)}", + result=f"Configuration failed (ReadTimeout): {e!s}", failed=True, ) @@ -305,7 +305,7 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool): ) return Result( host=task.host, - result=f"Configuration failed (Unhandled Exception): {str(e)}", + result=f"Configuration failed (Unhandled Exception): {e!s}", failed=True, ) 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 a8eb41bc4..ab31e9978 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py +++ b/gns3server/agent/gns3_copilot/tools_v2/display_tools_nornir.py @@ -57,7 +57,7 @@ from gns3server.agent.gns3_copilot.utils.command_filter import ( # Import custom Netmiko device types for GNS3 emulation # This registers gns3_huawei_telnet_ce and other custom device types # NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered -from gns3server.agent.gns3_copilot.utils import custom_netmiko # noqa: F401 +from gns3server.agent.gns3_copilot.utils import custom_netmiko # Explicitly register custom device types to ensure they are available # This is a safety measure in case the auto-registration on import doesn't work @@ -253,7 +253,7 @@ class ExecuteMultipleDeviceCommands(BaseTool): except Exception as e: # Overall execution failed logger.error("Error executing display on all devices: %s", e) - return [{"status": "failed", "error": f"Execution error: {str(e)}"}] + return [{"status": "failed", "error": f"Execution error: {e!s}"}] logger.debug( "Multiple device display execution completed. Results: %s", @@ -288,7 +288,7 @@ class ExecuteMultipleDeviceCommands(BaseTool): ) return Result( host=task.host, - result=f"diagnostic command failed (ReadTimeout): {str(e)}", + result=f"diagnostic command failed (ReadTimeout): {e!s}", failed=True, ) @@ -313,7 +313,7 @@ class ExecuteMultipleDeviceCommands(BaseTool): ) return Result( host=task.host, - result=f"diagnostic command failed (Unhandled): {str(e)}", + result=f"diagnostic command failed (Unhandled): {e!s}", failed=True, ) 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 ff11c6a44..c70c559ba 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_create_link.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_create_link.py @@ -238,7 +238,7 @@ class GNS3LinkTool(BaseTool): created_links.append(link_info) except Exception as e: - error_msg = f"Failed to create link {i}: {str(e)}" + error_msg = f"Failed to create link {i}: {e!s}" logger.error(error_msg) created_links.append({"error": error_msg}) @@ -257,7 +257,7 @@ class GNS3LinkTool(BaseTool): return [{"error": f"Invalid JSON input: {e}"}] except Exception as e: logger.error("Failed to process link creation: %s", e) - return [{"error": f"Failed to process link creation: {str(e)}"}] + return [{"error": f"Failed to process link creation: {e!s}"}] if __name__ == "__main__": 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 bdbc0bf42..60b2b0801 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_create_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_create_node.py @@ -244,7 +244,7 @@ class GNS3CreateNodeTool(BaseTool): except Exception as e: error_info = { - "error": f"Node {i + 1} creation failed: {str(e)}", + "error": f"Node {i + 1} creation failed: {e!s}", "status": "failed", } results.append(error_info) @@ -280,7 +280,7 @@ class GNS3CreateNodeTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Failed to process node creation request: %s", e) - return {"error": f"Failed to process node creation request: {str(e)}"} + return {"error": f"Failed to process node creation request: {e!s}"} if __name__ == "__main__": diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_get_node_temp.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_get_node_temp.py index af685ea4b..594c2c2cf 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_get_node_temp.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_get_node_temp.py @@ -168,7 +168,7 @@ class GNS3TemplateTool(BaseTool): except Exception as e: logger.error("Failed to connect to GNS3 server or retrieve templates: %s", e) - return {"error": f"Failed to retrieve templates: {str(e)}"} + return {"error": f"Failed to retrieve templates: {e!s}"} if __name__ == "__main__": 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 be7af824f..ed563cbbd 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_packet_filter.py @@ -239,7 +239,7 @@ class GNS3PacketFilterTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Failed to process packet filter request: %s", e) - return {"error": f"Failed to process packet filter request: {str(e)}"} + return {"error": f"Failed to process packet filter request: {e!s}"} def _get_available_filters(self, gns3_ctx: dict, project_id: str, link_id: str) -> dict[str, Any]: """Get available filter types for the link.""" @@ -260,7 +260,7 @@ class GNS3PacketFilterTool(BaseTool): return { "action": "get_available", "link_id": link_id, - "error": f"Failed to get available filters: {str(e)}", + "error": f"Failed to get available filters: {e!s}", "status": "failed", } @@ -318,7 +318,7 @@ class GNS3PacketFilterTool(BaseTool): except Exception as e: logger.error("Unexpected error during BPF validation: %s", e) - return {"valid": False, "error": f"BPF validation error: {str(e)}"} + return {"valid": False, "error": f"BPF validation error: {e!s}"} def _set_filters( self, @@ -384,7 +384,7 @@ class GNS3PacketFilterTool(BaseTool): return { "action": "set", "link_id": link_id, - "error": f"Failed to set filters: {str(e)}", + "error": f"Failed to set filters: {e!s}", "status": "failed", } @@ -407,7 +407,7 @@ class GNS3PacketFilterTool(BaseTool): return { "action": "get", "link_id": link_id, - "error": f"Failed to get filters: {str(e)}", + "error": f"Failed to get filters: {e!s}", "status": "failed", } @@ -449,7 +449,7 @@ class GNS3PacketFilterTool(BaseTool): return { "action": "clear", "link_id": link_id, - "error": f"Failed to clear filters: {str(e)}", + "error": f"Failed to clear filters: {e!s}", "status": "failed", } 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 4086cde1b..2fb17f8f8 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_start_node.py @@ -220,7 +220,7 @@ class GNS3StartNodeTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Failed to start nodes: %s", e) - return {"error": f"Failed to start nodes: {str(e)}"} + return {"error": f"Failed to start nodes: {e!s}"} # Backward-compat alias: the waiting variant was removed; both names now 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 d6965f19b..b4ef8cfd5 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_stop_node.py @@ -198,7 +198,7 @@ class GNS3StopNodeTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Failed to stop nodes: %s", e) - return {"error": f"Failed to stop nodes: {str(e)}"} + return {"error": f"Failed to stop nodes: {e!s}"} if __name__ == "__main__": 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 47a0cc4b1..25dda50fd 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_suspend_node.py @@ -209,7 +209,7 @@ class GNS3SuspendNodeTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Failed to suspend nodes: %s", e) - return {"error": f"Failed to suspend nodes: {str(e)}"} + return {"error": f"Failed to suspend nodes: {e!s}"} if __name__ == "__main__": diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_update_node_name.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_update_node_name.py index 7756b33a2..3121bc598 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_update_node_name.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_update_node_name.py @@ -241,7 +241,7 @@ class GNS3UpdateNodeNameTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Failed to update node names: %s", e) - return {"error": f"Failed to update node names: {str(e)}"} + return {"error": f"Failed to update node names: {e!s}"} if __name__ == "__main__": diff --git a/gns3server/agent/gns3_copilot/tools_v2/gns3_wait.py b/gns3server/agent/gns3_copilot/tools_v2/gns3_wait.py index 15a9ed24e..ed2f12802 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/gns3_wait.py +++ b/gns3server/agent/gns3_copilot/tools_v2/gns3_wait.py @@ -104,4 +104,4 @@ class GNS3WaitTool(BaseTool): return {"error": f"Invalid JSON input: {e}"} except Exception as e: logger.error("Wait tool failed: %s", e) - return {"error": f"Wait tool failed: {str(e)}"} + return {"error": f"Wait tool failed: {e!s}"} 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 c5f30bf79..a1123403c 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py +++ b/gns3server/agent/gns3_copilot/tools_v2/packet_analysis_tool.py @@ -325,7 +325,7 @@ class PacketAnalysisTool(BaseTool): except Exception as e: logger.error(f"PacketAnalysisTool error: {e}", exc_info=True) - return f'{{"error": "Analysis failed: {str(e)}"}}' + return f'{{"error": "Analysis failed: {e!s}"}}' finally: # Clean up temp file @@ -444,7 +444,7 @@ class PacketAnalysisTool(BaseTool): # Build command: tshark -r import shlex - cmd = ["tshark", "-r", pcap_file] + shlex.split(tshark_args) + cmd = ["tshark", "-r", pcap_file, *shlex.split(tshark_args)] logger.info(f"Running tshark: {' '.join(cmd)}") @@ -497,7 +497,7 @@ class PacketAnalysisTool(BaseTool): return '{"error": "tshark not installed. Please install tshark: apt install tshark"}' except Exception as e: logger.error(f"tshark execution error: {e}", exc_info=True) - return f'{{"error": "tshark failed: {str(e)}"}}' + return f'{{"error": "tshark failed: {e!s}"}}' if __name__ == "__main__": 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 1315730c7..26a512dc5 100644 --- a/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py +++ b/gns3server/agent/gns3_copilot/tools_v2/vpcs_tools_netmiko.py @@ -54,7 +54,7 @@ from gns3server.agent.gns3_copilot.utils import get_device_ports_from_topology # Import custom Netmiko device types for GNS3 emulation # This registers gns3_vpcs_telnet and other custom device types # NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered -from gns3server.agent.gns3_copilot.utils import custom_netmiko # noqa: F401 +from gns3server.agent.gns3_copilot.utils import custom_netmiko # Explicitly register VPCS device type to ensure it is available try: @@ -227,7 +227,7 @@ class VPCSCommands(BaseTool): except Exception as e: # Overall execution failed logger.error("Error executing commands on all VPCS devices: %s", e) - return [{"status": "failed", "error": f"Execution error: {str(e)}"}] + return [{"status": "failed", "error": f"Execution error: {e!s}"}] logger.debug( "VPCS command execution completed. Results: %s", @@ -262,7 +262,7 @@ class VPCSCommands(BaseTool): ) return Result( host=task.host, - result=f"Command failed (ReadTimeout): {str(e)}", + result=f"Command failed (ReadTimeout): {e!s}", failed=True, ) @@ -289,7 +289,7 @@ class VPCSCommands(BaseTool): ) return Result( host=task.host, - result=f"Command failed: {str(retry_e)}", + result=f"Command failed: {retry_e!s}", failed=True, ) diff --git a/gns3server/agent/gns3_copilot/utils/__init__.py b/gns3server/agent/gns3_copilot/utils/__init__.py index ed79a457c..7beb1d745 100644 --- a/gns3server/agent/gns3_copilot/utils/__init__.py +++ b/gns3server/agent/gns3_copilot/utils/__init__.py @@ -58,8 +58,8 @@ __url__ = "https://github.com/yueguobin/gns3-copilot" # Export main utility functions __all__ = [ "format_error_message", - "get_device_ports_from_topology", - "parse_tool_content", "format_tool_response", + "get_device_ports_from_topology", "normalize_tool_response", + "parse_tool_content", ] diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/__init__.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/__init__.py index 1aea7adf8..9cad247ca 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/__init__.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/__init__.py @@ -61,17 +61,17 @@ logger = logging.getLogger(__name__) # Import all custom drivers (auto-registers them with Netmiko) try: - from . import huawei_ce # noqa: F401 + from . import huawei_ce except Exception as e: logger.warning(f"Failed to import Huawei CE driver: {e}", exc_info=True) try: - from .ruijie_telnet import RuijieTelnetEnhanced # noqa: F401 + from .ruijie_telnet import RuijieTelnetEnhanced except Exception as e: logger.warning(f"Failed to import Ruijie driver: {e}", exc_info=True) try: - from . import vpcs_telnet # noqa: F401 + from . import vpcs_telnet except Exception as e: logger.warning(f"Failed to import VPCS driver: {e}", exc_info=True) diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py index d4f7b1a38..3097dd954 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/scripts/list_netmiko_telnet_devices.py @@ -338,7 +338,7 @@ def save_markdown(content: str, filename: str = "netmiko_devices.md") -> None: with open(output_path, "w", encoding="utf-8") as f: f.write(content) print(f"Markdown document saved to: {output_path}") - except IOError as e: + except OSError as e: print(f"Error: Failed to save Markdown file: {e}", file=sys.stderr) sys.exit(1) diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py index c6b6db344..62343c3ef 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_huawei_ce.py @@ -157,7 +157,7 @@ class TestHuaweiTelnetCEIntegration(unittest.TestCase): def test_mock_telnet_connection(self): """Test telnet_login logic with mocked connection.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.huawei_ce import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.huawei_ce import ( GNS3HuaweiTelnetCE, ) diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_ruijie_telnet.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_ruijie_telnet.py index dfef81823..adfbafea2 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_ruijie_telnet.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_ruijie_telnet.py @@ -178,7 +178,7 @@ class TestRuijieTelnetEnhancedIntegration(unittest.TestCase): def test_send_config_set_calls_preprocess(self): """Test that send_config_set calls preprocessing.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.ruijie_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.ruijie_telnet import ( RuijieTelnetEnhanced, ) @@ -205,7 +205,7 @@ class TestRuijieTelnetEnhancedIntegration(unittest.TestCase): def test_multiple_interactive_patterns(self): """Test that multiple interactive commands are detected.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.ruijie_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.ruijie_telnet import ( RuijieTelnetEnhanced, ) diff --git a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_vpcs_telnet.py b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_vpcs_telnet.py index 4659465cd..f24565d85 100644 --- a/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_vpcs_telnet.py +++ b/gns3server/agent/gns3_copilot/utils/custom_netmiko/tests/test_vpcs_telnet.py @@ -116,7 +116,7 @@ class TestVPCSTelnetInit(unittest.TestCase): @classmethod def setUpClass(cls): """Set up test fixtures.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( VPCSTelnet, ) @@ -165,7 +165,7 @@ class TestVPCSTelnetMethods(unittest.TestCase): @classmethod def setUpClass(cls): """Set up test fixtures.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( VPCSTelnet, ) @@ -208,7 +208,7 @@ class TestVPCSTelnetSendCommand(unittest.TestCase): @classmethod def setUpClass(cls): """Set up test fixtures.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( VPCSTelnet, ) @@ -273,7 +273,7 @@ class TestVPCSTelnetTelnetLogin(unittest.TestCase): @classmethod def setUpClass(cls): """Set up test fixtures.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( VPCSTelnet, ) @@ -353,7 +353,7 @@ class TestVPCSTelnetAnsiStripping(unittest.TestCase): @classmethod def setUpClass(cls): """Set up test fixtures.""" - from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501 + from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( VPCSTelnet, ) diff --git a/gns3server/agent/gns3_copilot/utils/gns3_drawing_utils.py b/gns3server/agent/gns3_copilot/utils/gns3_drawing_utils.py index a28278744..4fa48ee7d 100644 --- a/gns3server/agent/gns3_copilot/utils/gns3_drawing_utils.py +++ b/gns3server/agent/gns3_copilot/utils/gns3_drawing_utils.py @@ -238,7 +238,7 @@ def generate_ellipse_svg( svg_height: int, ) -> str: """Generate SVG for ellipse.""" - return f"""""" # noqa: E501 + return f"""""" def generate_rectangle_svg( @@ -247,7 +247,7 @@ def generate_rectangle_svg( color_scheme: dict[str, Any], ) -> str: """Generate SVG for rectangle.""" - return f"""""" # noqa: E501 + return f"""""" def generate_text_svg(text: str, color_scheme: dict[str, Any]) -> str: @@ -255,7 +255,7 @@ def generate_text_svg(text: str, color_scheme: dict[str, Any]) -> str: text_width = len(text) * 8 + 20 text_height = DEFAULT_FONT_SIZE + 16 - return f"""{text}""" # noqa: E501 + return f"""{text}""" def _hsv_to_hex(h: int, s: int, v: int) -> str: diff --git a/gns3server/agent/gns3_copilot/utils/parse_tool_content.py b/gns3server/agent/gns3_copilot/utils/parse_tool_content.py index 350a6bf50..736d792c3 100644 --- a/gns3server/agent/gns3_copilot/utils/parse_tool_content.py +++ b/gns3server/agent/gns3_copilot/utils/parse_tool_content.py @@ -496,7 +496,7 @@ def _test_parse_tool_content() -> None: for i, (input_data, expected) in enumerate(test_cases): result = parse_tool_content(input_data) status = "✓" if result == expected else "✗" - print(f"Test {i + 1}: {status} Input: {repr(input_data)} -> {result}") + print(f"Test {i + 1}: {status} Input: {input_data!r} -> {result}") print("\nTesting format_tool_response function:") format_tests = [ @@ -518,7 +518,7 @@ def _test_parse_tool_content() -> None: valid = "✓" except Exception: valid = "✗" - print(f"Format Test {i + 1}: {valid} Input: {repr(input_data)} -> {result}") + print(f"Format Test {i + 1}: {valid} Input: {input_data!r} -> {result}") if __name__ == "__main__": diff --git a/gns3server/agent/mcp/templates.py b/gns3server/agent/mcp/templates.py index a80f08ff9..56e02680a 100644 --- a/gns3server/agent/mcp/templates.py +++ b/gns3server/agent/mcp/templates.py @@ -173,7 +173,7 @@ def delete_template_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> template_id = matches[0]["template_id"] conn.http_call("delete", f"{conn.base_url}/templates/{template_id}") - return {"message": f"Template deleted"} + return {"message": "Template deleted"} # ── Tool definitions ─────────────────────────────────────────────────────── diff --git a/gns3server/api/routes/compute/notifications.py b/gns3server/api/routes/compute/notifications.py index ee533037b..66de26675 100644 --- a/gns3server/api/routes/compute/notifications.py +++ b/gns3server/api/routes/compute/notifications.py @@ -67,7 +67,7 @@ if __name__ == "__main__":