Merge branch '3.1' into mypy-agent-copilot-tools-nornir

This commit is contained in:
Jeremy Grossmann authored and GitHub committed 2026-09-29 12:38:40 +02:00
commit 77a323e1e8
24 files changed
+208 -187

No files matched your search

@@ -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,
@@ -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"],
+16 -21
View File
@@ -256,17 +256,16 @@ class AgentService:
)
# Ensure checkpointer is initialized
if not self._checkpointer_conn:
await self._get_checkpointer()
checkpointer = await self._get_checkpointer()
# Get or create chat session
repo = ChatSessionsRepository(self._checkpointer_conn)
repo = ChatSessionsRepository(checkpointer.conn)
session = await repo.get_session_by_thread(session_id)
is_new_session = session is None
if is_new_session:
# Create new session
copilot_mode = llm_config.get("copilot_mode", "teaching_assistant").lower()
copilot_mode = (llm_config or {}).get("copilot_mode", "teaching_assistant").lower()
session = await repo.create_session(
thread_id=session_id,
user_id=user_id or "",
@@ -290,7 +289,7 @@ class AgentService:
# Build config - only thread-safe identifiers
# Determine recursion_limit based on copilot_mode
copilot_mode = llm_config.get("copilot_mode", "teaching_assistant").lower()
copilot_mode = (llm_config or {}).get("copilot_mode", "teaching_assistant").lower()
if copilot_mode == "troubleshooting_injection":
recursion_limit = 100 # Need more recursion depth for fault injection workflow
log.debug("Using extended recursion_limit for troubleshooting_injection mode: 100")
@@ -453,10 +452,10 @@ class AgentService:
yield chunk
else:
# Use stateless converter for other events
chunk = self._convert_event_to_chunk(event, session_id)
if chunk:
log.debug("Yielding chunk: type=%s", chunk.get("type"))
yield chunk
converted = self._convert_event_to_chunk(event, session_id)
if converted:
log.debug("Yielding chunk: type=%s", converted.get("type"))
yield converted
# Check if stream was aborted and yield tool_end events for aborted tools
from gns3server.agent.gns3_copilot.agent.gns3_copilot import (
@@ -624,10 +623,9 @@ class AgentService:
Returns:
List of session dictionaries
"""
if not self._checkpointer_conn:
await self._get_checkpointer()
checkpointer = await self._get_checkpointer()
repo = ChatSessionsRepository(self._checkpointer_conn)
repo = ChatSessionsRepository(checkpointer.conn)
sessions = await repo.list_sessions(user_id=user_id, copilot_mode=copilot_mode, limit=limit)
return [s.to_dict() for s in sessions]
@@ -641,10 +639,9 @@ class AgentService:
Returns:
True if deleted, False if not found
"""
if not self._checkpointer_conn:
await self._get_checkpointer()
checkpointer = await self._get_checkpointer()
repo = ChatSessionsRepository(self._checkpointer_conn)
repo = ChatSessionsRepository(checkpointer.conn)
return await repo.delete_session(session_id)
async def rename_session(self, session_id: str, new_title: str) -> Optional[Dict[str, Any]]:
@@ -658,10 +655,9 @@ class AgentService:
Returns:
Updated session dictionary or None
"""
if not self._checkpointer_conn:
await self._get_checkpointer()
checkpointer = await self._get_checkpointer()
repo = ChatSessionsRepository(self._checkpointer_conn)
repo = ChatSessionsRepository(checkpointer.conn)
session = await repo.update_session(thread_id=session_id, title=new_title)
return session.to_dict() if session else None
@@ -676,10 +672,9 @@ class AgentService:
Returns:
Updated session dictionary or None
"""
if not self._checkpointer_conn:
await self._get_checkpointer()
checkpointer = await self._get_checkpointer()
repo = ChatSessionsRepository(self._checkpointer_conn)
repo = ChatSessionsRepository(checkpointer.conn)
session = await repo.pin_session(thread_id=session_id, pinned=pinned)
return session.to_dict() if session else None
@@ -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
@@ -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
@@ -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")
@@ -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: