diff --git a/.gitignore b/.gitignore index f9ad727..31e76aa 100644 --- a/.gitignore +++ b/.gitignore @@ -152,6 +152,7 @@ testremote/ *.db *.patch scratch.py +connpy.code-workspace # Internal planning and implementation docs PLAN_CAPA_SERVICIOS.md diff --git a/README.md b/README.md index 020107c..b93008b 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,12 @@ The v6 release introduces the **AI Copilot**, an interactive terminal assistant ## 🤖 AI Copilot (New in v6) The AI Copilot is deeply integrated into your terminal workflow: - **Terminal Context Awareness**: The Copilot can "see" your screen output, helping you diagnose errors or analyze command results in real-time. +- **Dynamic Context Selection**: Flexibly select single, range, or line-based terminal blocks to feed the Copilot, filtering out interactive scrolling garbage automatically (e.g., Cisco IOS/XR scrolling, paginators). - **Hybrid Multi-Agent System**: Automatically escalates complex tasks between the **Network Engineer** (execution) and the **Network Architect** (strategy). - **MCP Integration**: Dynamically load tools from external providers (6WIND, AWS, etc.) via the Model Context Protocol. +- **Flexible Auth & Keyless AI**: Support for advanced LiteLLM credentials (`--engineer-auth` / `--architect-auth`) allowing keyless local models (Ollama), cloud engines (Vertex AI), or custom endpoints. +- **Enhanced Session Management**: Uniquely generated sessions, robust pagination, and interactive styling translating prompt themes directly to terminal escapes. +- **Semantic Prompt Integration**: Emit standard OSC prompt sequences (`\x1b]133;B`) for real-time remote/web front-end command tracking. - **Interactive Chat**: Launch with `conn ai` for a collaborative troubleshooting session. diff --git a/connpy/__init__.py b/connpy/__init__.py index c540f19..a3f0113 100644 --- a/connpy/__init__.py +++ b/connpy/__init__.py @@ -19,8 +19,12 @@ The v6 release introduces the **AI Copilot**, an interactive terminal assistant ## 🤖 AI Copilot (New in v6) The AI Copilot is deeply integrated into your terminal workflow: - **Terminal Context Awareness**: The Copilot can "see" your screen output, helping you diagnose errors or analyze command results in real-time. +- **Dynamic Context Selection**: Flexibly select single, range, or line-based terminal blocks to feed the Copilot, filtering out interactive scrolling garbage automatically (e.g., Cisco IOS/XR scrolling, paginators). - **Hybrid Multi-Agent System**: Automatically escalates complex tasks between the **Network Engineer** (execution) and the **Network Architect** (strategy). - **MCP Integration**: Dynamically load tools from external providers (6WIND, AWS, etc.) via the Model Context Protocol. +- **Flexible Auth & Keyless AI**: Support for advanced LiteLLM credentials (`--engineer-auth` / `--architect-auth`) allowing keyless local models (Ollama), cloud engines (Vertex AI), or custom endpoints. +- **Enhanced Session Management**: Uniquely generated sessions, robust pagination, and interactive styling translating prompt themes directly to terminal escapes. +- **Semantic Prompt Integration**: Emit standard OSC prompt sequences (`\x1b]133;B`) for real-time remote/web front-end command tracking. - **Interactive Chat**: Launch with `conn ai` for a collaborative troubleshooting session. @@ -203,5 +207,7 @@ __pdoc__ = { 'nodes.deferred_class_hooks': False, 'connapp': False, 'connapp.encrypt': True, - 'printer': False + 'printer': False, + 'tests': False } + diff --git a/connpy/_version.py b/connpy/_version.py index 4c61376..ba9efd8 100644 --- a/connpy/_version.py +++ b/connpy/_version.py @@ -1 +1 @@ -__version__ = "6.0.0b11" +__version__ = "6.0.0b12" diff --git a/connpy/tests/test_utils.py b/connpy/tests/test_utils.py new file mode 100644 index 0000000..36f19ea --- /dev/null +++ b/connpy/tests/test_utils.py @@ -0,0 +1,32 @@ +import pytest +from connpy.utils import log_cleaner + +def test_log_cleaner_empty(): + assert log_cleaner("") == "" + assert log_cleaner(None) == "" + +def test_log_cleaner_plain_text(): + assert log_cleaner("hello world") == "hello world" + +def test_log_cleaner_ansi_colors(): + # \x1b[31m is red, \x1b[0m is reset + assert log_cleaner("\x1b[31mhello\x1b[0m world") == "hello world" + +def test_log_cleaner_osc_window_title(): + # Set window title OSC: \x1b]0;my title\x07 followed by prompt + sample = "\x1b]0;fluzzi32@norman: ~\x07fluzzi32@norman:~$" + assert log_cleaner(sample) == "fluzzi32@norman:~$" + +def test_log_cleaner_osc_with_st_terminator(): + # OSC can also be terminated by \x1b\\ (ST) + sample = "\x1b]0;some title\x1b\\my_prompt>" + assert log_cleaner(sample) == "my_prompt>" + +def test_log_cleaner_mixed_ansi_and_osc(): + sample = "\x1b]0;title\x07\x1b[32muser@host\x1b[0m:\x1b[34m/path\x1b[0m$ " + assert log_cleaner(sample) == "user@host:/path$" + +def test_log_cleaner_carriage_return_and_backspace(): + # Test that standard control sequences like \r and \b still work as expected + assert log_cleaner("hello\rworld") == "world" + assert log_cleaner("hell\bo") == "helo" diff --git a/connpy/utils.py b/connpy/utils.py index 8181977..2a4e2d3 100644 --- a/connpy/utils.py +++ b/connpy/utils.py @@ -7,6 +7,9 @@ def log_cleaner(data: str) -> str: if not data: return "" + # Remove OSC (Operating System Command) sequences (e.g., set window title \x1b]0;...\x07) + data = re.sub(r'\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)', '', data) + lines = data.split('\n') cleaned_lines = [] diff --git a/docs/connpy/cli/ai_handler.html b/docs/connpy/cli/ai_handler.html index 3f88ec2..a60ecf6 100644 --- a/docs/connpy/cli/ai_handler.html +++ b/docs/connpy/cli/ai_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.ai_handler API documentation @@ -61,13 +61,22 @@ el.replaceWith(d); def dispatch(self, args): if args.list_sessions: - sessions = self.app.services.ai.list_sessions() + limit = 20 if not getattr(args, "all", False) else None + sessions, total = self.app.services.ai.list_sessions(limit=limit) if not sessions: printer.info("No saved AI sessions found.") return + columns = ["ID", "Title", "Created At", "Model"] rows = [[s["id"], s["title"], s["created_at"], s["model"]] for s in sessions] - printer.table("AI Persisted Sessions", columns, rows) + + title = "AI Persisted Sessions" + if limit and total > limit: + title += f" (Showing last {limit} of {total})" + + printer.table(title, columns, rows) + if limit and total > limit: + printer.info(f"Use '--list --all' to see all {total} sessions.") return if args.delete_session: @@ -84,7 +93,7 @@ el.replaceWith(d); # Determinar session_id para retomar session_id = None if args.resume: - sessions = self.app.services.ai.list_sessions() + sessions, _ = self.app.services.ai.list_sessions() session_id = sessions[0]["id"] if sessions else None if not session_id: printer.warning("No previous session found to resume.") @@ -102,16 +111,23 @@ el.replaceWith(d); arguments[key] = cli_val[0] elif settings.get(key): arguments[key] = settings.get(key) + + for key in ["engineer_auth", "architect_auth"]: + cli_val = getattr(args, key, None) + if cli_val: + arguments[key] = self._parse_auth_value(cli_val[0]) + elif settings.get(key): + arguments[key] = settings.get(key) # Check keys only if running in local mode (not remote) if getattr(self.app.services, "mode", "local") == "local": - if not arguments.get("engineer_api_key"): - printer.error("Engineer API key not configured. The chat cannot start.") - printer.info("Use 'connpy config --engineer-api-key <key>' to set it.") + if not arguments.get("engineer_api_key") and not arguments.get("engineer_auth"): + printer.error("Engineer API key/auth not configured. The chat cannot start.") + printer.info("Use 'connpy config --engineer-api-key <key>' or 'connpy config --engineer-auth <auth>' to set it.") sys.exit(1) - if not arguments.get("architect_api_key"): - printer.warning("Architect API key not configured. Architect will be unavailable.") - printer.info("Use 'connpy config --architect-api-key <key>' to enable it.") + if not arguments.get("architect_api_key") and not arguments.get("architect_auth"): + printer.warning("Architect API key/auth not configured. Architect will be unavailable.") + printer.info("Use 'connpy config --architect-api-key <key>' or 'connpy config --architect-auth <auth>' to enable it.") # El resto de la interacción el CLI la maneja con el agente subyacente self.app.myai = self.app.services.ai @@ -148,7 +164,7 @@ el.replaceWith(d); if history: mdprint(f"[debug]Analyzing {len(history)} previous messages...[/debug]\n") else: - printer.error(f"Could not load session {session_id}. Starting clean.") + printer.info(f"Session '{session_id}' not found. Starting clean.") if not history: mdprint(Rule(style="engineer")) @@ -162,7 +178,7 @@ el.replaceWith(d); if user_query.lower() in ['exit', 'quit', 'bye', 'cancel']: break with console.status("[ai_status]Agent is thinking...") as status: - result = self.app.myai.ask(user_query, chat_history=history, status=status, debug=args.debug, trust=args.trust, **self.ai_overrides) + result = self.app.myai.ask(user_query, chat_history=history, status=status, debug=args.debug, trust=args.trust, session_id=session_id, **self.ai_overrides) new_history = result.get("chat_history") if new_history is not None: @@ -193,8 +209,7 @@ el.replaceWith(d); action = mcp_args[0].lower() if action == "list": - settings = self.app.services.config_svc.get_settings() - mcp_servers = settings.get("ai", {}).get("mcp_servers", {}) + mcp_servers = self.app.services.ai.list_mcp_servers() if not mcp_servers: printer.info("No MCP servers configured.") else: @@ -259,8 +274,7 @@ el.replaceWith(d); from .forms import Forms self.app.cli_forms = Forms(self.app) - settings = self.app.services.config_svc.get_settings() - mcp_servers = settings.get("ai", {}).get("mcp_servers", {}) + mcp_servers = self.app.services.ai.list_mcp_servers() result = self.app.cli_forms.mcp_wizard(mcp_servers) if not result: @@ -294,7 +308,37 @@ el.replaceWith(d); printer.success(f"MCP server '{result['name']}' removed.") except Exception as e: - printer.error(str(e)) + printer.error(str(e)) + + def _parse_auth_value(self, value): + if not value or value.lower() in ["none", "clear"]: + return None + import os + import yaml + import json + if os.path.exists(value): + try: + with open(value, "r") as f: + content = f.read() + try: + return json.loads(content) + except ValueError: + return yaml.safe_load(content) + except Exception as e: + printer.error(f"Failed to read/parse auth file '{value}': {e}") + sys.exit(1) + + try: + return json.loads(value) + except ValueError: + try: + parsed = yaml.safe_load(value) + if isinstance(parsed, dict): + return parsed + raise ValueError() + except Exception: + printer.error("Auth parameter must be a valid JSON/YAML string, or a path to a JSON/YAML file.") + sys.exit(1)

Methods

@@ -316,8 +360,7 @@ el.replaceWith(d); action = mcp_args[0].lower() if action == "list": - settings = self.app.services.config_svc.get_settings() - mcp_servers = settings.get("ai", {}).get("mcp_servers", {}) + mcp_servers = self.app.services.ai.list_mcp_servers() if not mcp_servers: printer.info("No MCP servers configured.") else: @@ -382,8 +425,7 @@ el.replaceWith(d); from .forms import Forms self.app.cli_forms = Forms(self.app) - settings = self.app.services.config_svc.get_settings() - mcp_servers = settings.get("ai", {}).get("mcp_servers", {}) + mcp_servers = self.app.services.ai.list_mcp_servers() result = self.app.cli_forms.mcp_wizard(mcp_servers) if not result: @@ -431,13 +473,22 @@ el.replaceWith(d);
def dispatch(self, args):
     if args.list_sessions:
-        sessions = self.app.services.ai.list_sessions()
+        limit = 20 if not getattr(args, "all", False) else None
+        sessions, total = self.app.services.ai.list_sessions(limit=limit)
         if not sessions:
             printer.info("No saved AI sessions found.")
             return
+        
         columns = ["ID", "Title", "Created At", "Model"]
         rows = [[s["id"], s["title"], s["created_at"], s["model"]] for s in sessions]
-        printer.table("AI Persisted Sessions", columns, rows)
+        
+        title = "AI Persisted Sessions"
+        if limit and total > limit:
+            title += f" (Showing last {limit} of {total})"
+            
+        printer.table(title, columns, rows)
+        if limit and total > limit:
+            printer.info(f"Use '--list --all' to see all {total} sessions.")
         return
         
     if args.delete_session:
@@ -454,7 +505,7 @@ el.replaceWith(d);
     # Determinar session_id para retomar
     session_id = None
     if args.resume:
-        sessions = self.app.services.ai.list_sessions()
+        sessions, _ = self.app.services.ai.list_sessions()
         session_id = sessions[0]["id"] if sessions else None
         if not session_id:
             printer.warning("No previous session found to resume.")
@@ -472,16 +523,23 @@ el.replaceWith(d);
             arguments[key] = cli_val[0]
         elif settings.get(key):
             arguments[key] = settings.get(key)
+
+    for key in ["engineer_auth", "architect_auth"]:
+        cli_val = getattr(args, key, None)
+        if cli_val:
+            arguments[key] = self._parse_auth_value(cli_val[0])
+        elif settings.get(key):
+            arguments[key] = settings.get(key)
     
     # Check keys only if running in local mode (not remote)
     if getattr(self.app.services, "mode", "local") == "local":
-        if not arguments.get("engineer_api_key"):
-            printer.error("Engineer API key not configured. The chat cannot start.")
-            printer.info("Use 'connpy config --engineer-api-key <key>' to set it.")
+        if not arguments.get("engineer_api_key") and not arguments.get("engineer_auth"):
+            printer.error("Engineer API key/auth not configured. The chat cannot start.")
+            printer.info("Use 'connpy config --engineer-api-key <key>' or 'connpy config --engineer-auth <auth>' to set it.")
             sys.exit(1)
-        if not arguments.get("architect_api_key"):
-            printer.warning("Architect API key not configured. Architect will be unavailable.")
-            printer.info("Use 'connpy config --architect-api-key <key>' to enable it.")
+        if not arguments.get("architect_api_key") and not arguments.get("architect_auth"):
+            printer.warning("Architect API key/auth not configured. Architect will be unavailable.")
+            printer.info("Use 'connpy config --architect-api-key <key>' or 'connpy config --architect-auth <auth>' to enable it.")
 
     # El resto de la interacción el CLI la maneja con el agente subyacente
     self.app.myai = self.app.services.ai
@@ -512,7 +570,7 @@ el.replaceWith(d);
             if history:
                 mdprint(f"[debug]Analyzing {len(history)} previous messages...[/debug]\n")
         else:
-            printer.error(f"Could not load session {session_id}. Starting clean.")
+            printer.info(f"Session '{session_id}' not found. Starting clean.")
     
     if not history:
         mdprint(Rule(style="engineer"))
@@ -526,7 +584,7 @@ el.replaceWith(d);
             if user_query.lower() in ['exit', 'quit', 'bye', 'cancel']: break
             
             with console.status("[ai_status]Agent is thinking...") as status:
-                result = self.app.myai.ask(user_query, chat_history=history, status=status, debug=args.debug, trust=args.trust, **self.ai_overrides)
+                result = self.app.myai.ask(user_query, chat_history=history, status=status, debug=args.debug, trust=args.trust, session_id=session_id, **self.ai_overrides)
             
             new_history = result.get("chat_history")
             if new_history is not None:
@@ -608,7 +666,7 @@ el.replaceWith(d);
 
 
 
 
 
diff --git a/docs/connpy/cli/api_handler.html b/docs/connpy/cli/api_handler.html
index 1263f6e..29eb836 100644
--- a/docs/connpy/cli/api_handler.html
+++ b/docs/connpy/cli/api_handler.html
@@ -3,7 +3,7 @@
 
 
 
-
+
 connpy.cli.api_handler API documentation
 
 
@@ -193,7 +193,7 @@ el.replaceWith(d);
 
 
 
 
 
diff --git a/docs/connpy/cli/config_handler.html b/docs/connpy/cli/config_handler.html
index a95351c..85a922f 100644
--- a/docs/connpy/cli/config_handler.html
+++ b/docs/connpy/cli/config_handler.html
@@ -3,7 +3,7 @@
 
 
 
-
+
 connpy.cli.config_handler API documentation
 
 
@@ -70,8 +70,10 @@ el.replaceWith(d);
             "theme": self.set_theme,
             "engineer_model": self.set_ai_config,
             "engineer_api_key": self.set_ai_config,
+            "engineer_auth": self.set_ai_config,
             "architect_model": self.set_ai_config,
             "architect_api_key": self.set_ai_config,
+            "architect_auth": self.set_ai_config,
             "trusted_commands": self.set_ai_config,
             "service_mode": self.set_service_mode,
             "remote_host": self.set_remote_host,
@@ -178,11 +180,59 @@ el.replaceWith(d);
         try:
             settings = self.app.services.config_svc.get_settings()
             aiconfig = settings.get("ai", {})
-            aiconfig[args.command] = args.data[0]
+            val = args.data[0]
+            
+            # Check for unset/clear request
+            if val.lower() in ["none", "clear", ""]:
+                if args.command in aiconfig:
+                    del aiconfig[args.command]
+            else:
+                # If configuring auth, parse as dictionary (JSON/YAML or file path)
+                if args.command in ["engineer_auth", "architect_auth"]:
+                    parsed_val = self._parse_auth_value(val)
+                    if parsed_val is not None:
+                        aiconfig[args.command] = parsed_val
+                    else:
+                        if args.command in aiconfig:
+                            del aiconfig[args.command]
+                else:
+                    aiconfig[args.command] = val
+            
             self.app.services.config_svc.update_setting("ai", aiconfig)
             printer.success("Config saved")
-        except ConnpyError as e:
-            printer.error(str(e))
+ except (ConnpyError, InvalidConfigurationError) as e: + printer.error(str(e)) + + def _parse_auth_value(self, value): + if value.lower() in ["none", "clear", ""]: + return None + + # Check if it's a file path + import os + if os.path.exists(value): + try: + with open(value, "r") as f: + content = f.read() + import json + try: + return json.loads(content) + except ValueError: + return yaml.safe_load(content) + except Exception as e: + raise InvalidConfigurationError(f"Failed to read/parse auth file '{value}': {e}") + + # Try parsing as inline JSON/YAML + try: + import json + return json.loads(value) + except ValueError: + try: + parsed = yaml.safe_load(value) + if isinstance(parsed, dict): + return parsed + raise ValueError() + except Exception: + raise InvalidConfigurationError("Auth parameter must be a valid JSON/YAML string, or a path to a JSON/YAML file.")

Methods

@@ -206,8 +256,10 @@ el.replaceWith(d); "theme": self.set_theme, "engineer_model": self.set_ai_config, "engineer_api_key": self.set_ai_config, + "engineer_auth": self.set_ai_config, "architect_model": self.set_ai_config, "architect_api_key": self.set_ai_config, + "architect_auth": self.set_ai_config, "trusted_commands": self.set_ai_config, "service_mode": self.set_service_mode, "remote_host": self.set_remote_host, @@ -234,10 +286,27 @@ el.replaceWith(d); try: settings = self.app.services.config_svc.get_settings() aiconfig = settings.get("ai", {}) - aiconfig[args.command] = args.data[0] + val = args.data[0] + + # Check for unset/clear request + if val.lower() in ["none", "clear", ""]: + if args.command in aiconfig: + del aiconfig[args.command] + else: + # If configuring auth, parse as dictionary (JSON/YAML or file path) + if args.command in ["engineer_auth", "architect_auth"]: + parsed_val = self._parse_auth_value(val) + if parsed_val is not None: + aiconfig[args.command] = parsed_val + else: + if args.command in aiconfig: + del aiconfig[args.command] + else: + aiconfig[args.command] = val + self.app.services.config_svc.update_setting("ai", aiconfig) printer.success("Config saved") - except ConnpyError as e: + except (ConnpyError, InvalidConfigurationError) as e: printer.error(str(e))
@@ -482,7 +551,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/context_handler.html b/docs/connpy/cli/context_handler.html index a6b3dfb..11e2a86 100644 --- a/docs/connpy/cli/context_handler.html +++ b/docs/connpy/cli/context_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.context_handler API documentation @@ -249,7 +249,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/forms.html b/docs/connpy/cli/forms.html index da27067..4a2c1bb 100644 --- a/docs/connpy/cli/forms.html +++ b/docs/connpy/cli/forms.html @@ -3,7 +3,7 @@ - + connpy.cli.forms API documentation @@ -690,7 +690,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/help_text.html b/docs/connpy/cli/help_text.html index 1fe8afd..1440196 100644 --- a/docs/connpy/cli/help_text.html +++ b/docs/connpy/cli/help_text.html @@ -3,7 +3,7 @@ - + connpy.cli.help_text API documentation @@ -303,7 +303,7 @@ tasks: diff --git a/docs/connpy/cli/helpers.html b/docs/connpy/cli/helpers.html index c0a11ca..0116865 100644 --- a/docs/connpy/cli/helpers.html +++ b/docs/connpy/cli/helpers.html @@ -3,7 +3,7 @@ - + connpy.cli.helpers API documentation @@ -69,7 +69,7 @@ el.replaceWith(d); return answer[0] else: questions = [inquirer.List(name, message="Pick {} to {}:".format(name,action), choices=list_, carousel=True)] - answer = inquirer.prompt(questions) + answer = inquirer.prompt(questions, theme=theme) if answer == None: return None else: @@ -115,6 +115,65 @@ el.replaceWith(d);
+
+def get_theme() +
+
+
+ +Expand source code + +
def get_theme():
+    """Returns a fresh instance of the theme with current colors."""
+    return ConnpyTheme()
+
+

Returns a fresh instance of the theme with current colors.

+
+
+def hex_to_blessed(hex_str) +
+
+
+ +Expand source code + +
def hex_to_blessed(hex_str):
+    """Convert hex color string to blessed/ansi format."""
+    if not hex_str or not isinstance(hex_str, str):
+        return term.normal
+    
+    # Check for bold prefix
+    prefix = ""
+    if hex_str.startswith('bold '):
+        prefix = term.bold
+        hex_str = hex_str.replace('bold ', '').strip()
+    
+    # If it's a standard color name
+    if not hex_str.startswith('#'):
+        return prefix + getattr(term, hex_str, term.normal)
+    
+    # Parse hex
+    try:
+        h = hex_str.lstrip('#')
+        if len(h) == 3:
+            h = ''.join([c*2 for c in h])
+        r = int(h[0:2], 16)
+        g = int(h[2:4], 16)
+        b = int(h[4:6], 16)
+        
+        # Try RGB, fallback to standard cyan if it fails or returns empty
+        try:
+            c = term.color_rgb(r, g, b)
+            if not c: # Some terms return empty for RGB
+                return prefix + term.cyan
+            return prefix + c
+        except:
+            return prefix + term.cyan
+    except:
+        return prefix + term.normal
+
+

Convert hex color string to blessed/ansi format.

+
def nodes_completer(prefix, parsed_args, **kwargs)
@@ -181,6 +240,61 @@ el.replaceWith(d);
+

Classes

+
+
+class ConnpyTheme +
+
+
+ +Expand source code + +
class ConnpyTheme(Default):
+    def __init__(self):
+        super().__init__()
+        try:
+            from ..printer import _global_active_styles
+            # Use user_prompt as primary accent, fallback to info/cyan
+            accent = _global_active_styles.get("user_prompt", _global_active_styles.get("info", "cyan"))
+            accent_color = hex_to_blessed(accent)
+            
+            self.Question.mark_color = accent_color
+            self.List.selection_color = accent_color
+            self.List.selection_cursor = ">"
+        except:
+            # Absolute fallback to standard cyan
+            self.Question.mark_color = term.cyan
+            self.List.selection_color = term.bold_cyan
+            self.List.selection_cursor = ">"
+
+
+

Ancestors

+
    +
  • inquirer.themes.Default
  • +
  • inquirer.themes.Theme
  • +
+
+
+class ThemeProxy +
+
+
+ +Expand source code + +
class ThemeProxy:
+    """Proxy to ensure theme colors are resolved at runtime."""
+    def __getattr__(self, name):
+        return getattr(get_theme(), name)
+    def __iter__(self):
+        return iter(get_theme())
+    def __getitem__(self, item):
+        return get_theme()[item]
+
+

Proxy to ensure theme colors are resolved at runtime.

+
+
diff --git a/docs/connpy/cli/import_export_handler.html b/docs/connpy/cli/import_export_handler.html index 6f6aa1b..cbb12ec 100644 --- a/docs/connpy/cli/import_export_handler.html +++ b/docs/connpy/cli/import_export_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.import_export_handler API documentation @@ -272,7 +272,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/index.html b/docs/connpy/cli/index.html index d22bb1e..6980227 100644 --- a/docs/connpy/cli/index.html +++ b/docs/connpy/cli/index.html @@ -3,7 +3,7 @@ - + connpy.cli API documentation @@ -142,7 +142,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/node_handler.html b/docs/connpy/cli/node_handler.html index 992ba84..9123836 100644 --- a/docs/connpy/cli/node_handler.html +++ b/docs/connpy/cli/node_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.node_handler API documentation @@ -606,7 +606,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/plugin_handler.html b/docs/connpy/cli/plugin_handler.html index 17142e7..318bde6 100644 --- a/docs/connpy/cli/plugin_handler.html +++ b/docs/connpy/cli/plugin_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.plugin_handler API documentation @@ -385,7 +385,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/profile_handler.html b/docs/connpy/cli/profile_handler.html index 0d6680f..67daeaf 100644 --- a/docs/connpy/cli/profile_handler.html +++ b/docs/connpy/cli/profile_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.profile_handler API documentation @@ -314,7 +314,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/run_handler.html b/docs/connpy/cli/run_handler.html index 8f6048c..ba80e89 100644 --- a/docs/connpy/cli/run_handler.html +++ b/docs/connpy/cli/run_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.run_handler API documentation @@ -454,7 +454,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/sync_handler.html b/docs/connpy/cli/sync_handler.html index 4ddd115..4c751be 100644 --- a/docs/connpy/cli/sync_handler.html +++ b/docs/connpy/cli/sync_handler.html @@ -3,7 +3,7 @@ - + connpy.cli.sync_handler API documentation @@ -427,7 +427,7 @@ el.replaceWith(d); diff --git a/docs/connpy/cli/terminal_ui.html b/docs/connpy/cli/terminal_ui.html index aff4dcb..0d2e2c5 100644 --- a/docs/connpy/cli/terminal_ui.html +++ b/docs/connpy/cli/terminal_ui.html @@ -3,7 +3,7 @@ - + connpy.cli.terminal_ui API documentation @@ -168,7 +168,8 @@ el.replaceWith(d); if state['context_mode'] == self.mode_single: active_raw = raw_bytes[start:end] else: - active_raw = raw_bytes[start:] + # Concat only the bytes of valid blocks to skip intermediate empty/cancelled prompt noise + active_raw = b"".join(raw_bytes[b[0]:b[1]] for b in blocks[idx:]) return preview + "\n" + log_cleaner(active_raw.decode(errors='replace')) def get_prompt_text(): @@ -207,7 +208,6 @@ el.replaceWith(d); base_str = f'\u25b6 Ctrl+\u2191/\u2193 adjusts by 50 lines [Tab: {m_label}]' else: idx = max(0, state['total_cmds'] - state['context_cmd']) - import re def clean_preview(text): # Limpia saltos de línea y el prompt inicial (todo hasta #, > o $) para que quede solo el comando @@ -370,10 +370,10 @@ el.replaceWith(d); persona_title = "Network Architect" if active_persona == "architect" else "Network Engineer" active_buffer = get_active_buffer() + live_text = "" first_chunk = True - import sys from rich.rule import Rule from rich.status import Status from connpy.printer import IncrementalMarkdownParser @@ -622,7 +622,8 @@ el.replaceWith(d); if state['context_mode'] == self.mode_single: active_raw = raw_bytes[start:end] else: - active_raw = raw_bytes[start:] + # Concat only the bytes of valid blocks to skip intermediate empty/cancelled prompt noise + active_raw = b"".join(raw_bytes[b[0]:b[1]] for b in blocks[idx:]) return preview + "\n" + log_cleaner(active_raw.decode(errors='replace')) def get_prompt_text(): @@ -661,7 +662,6 @@ el.replaceWith(d); base_str = f'\u25b6 Ctrl+\u2191/\u2193 adjusts by 50 lines [Tab: {m_label}]' else: idx = max(0, state['total_cmds'] - state['context_cmd']) - import re def clean_preview(text): # Limpia saltos de línea y el prompt inicial (todo hasta #, > o $) para que quede solo el comando @@ -824,10 +824,10 @@ el.replaceWith(d); persona_title = "Network Architect" if active_persona == "architect" else "Network Engineer" active_buffer = get_active_buffer() + live_text = "" first_chunk = True - import sys from rich.rule import Rule from rich.status import Status from connpy.printer import IncrementalMarkdownParser @@ -1017,7 +1017,7 @@ on_ai_call: async function(active_buffer, question) -> result_dict

diff --git a/docs/connpy/cli/validators.html b/docs/connpy/cli/validators.html index 3bbf7cd..ee2f6b6 100644 --- a/docs/connpy/cli/validators.html +++ b/docs/connpy/cli/validators.html @@ -3,7 +3,7 @@ - + connpy.cli.validators API documentation @@ -508,7 +508,7 @@ el.replaceWith(d); diff --git a/docs/connpy/grpc_layer/connpy_pb2.html b/docs/connpy/grpc_layer/connpy_pb2.html index 4d42e1f..25db16e 100644 --- a/docs/connpy/grpc_layer/connpy_pb2.html +++ b/docs/connpy/grpc_layer/connpy_pb2.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.connpy_pb2 API documentation @@ -45,617 +45,6 @@ el.replaceWith(d);
-

Classes

-
-
-class AIResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class AskRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class BoolResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class BulkRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class CopilotRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class CopilotResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class DeleteRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class ExportRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class FilterRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class FullReplaceRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class IdRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class IntRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class InteractRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class InteractResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class ListRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class MCPRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class MessageValue -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class MoveRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class NodeRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class NodeRunResult -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class PluginRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class ProfileRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class ProviderRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class RunRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class ScriptRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class StringRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class StringResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class StructRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class StructResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class TestRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class UpdateRequest -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
-class ValueResponse -(*args, **kwargs) -
-
-

A ProtocolMessage

-

Ancestors

-
    -
  • google._upb._message.Message
  • -
  • google.protobuf.message.Message
  • -
-

Class variables

-
-
var DESCRIPTOR
-
-
-
-
-
-
diff --git a/docs/connpy/grpc_layer/connpy_pb2_grpc.html b/docs/connpy/grpc_layer/connpy_pb2_grpc.html index c01da87..cb46699 100644 --- a/docs/connpy/grpc_layer/connpy_pb2_grpc.html +++ b/docs/connpy/grpc_layer/connpy_pb2_grpc.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.connpy_pb2_grpc API documentation @@ -57,43 +57,48 @@ el.replaceWith(d); rpc_method_handlers = { 'ask': grpc.stream_stream_rpc_method_handler( servicer.ask, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.AskRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.AIResponse.SerializeToString, + request_deserializer=connpy__pb2.AskRequest.FromString, + response_serializer=connpy__pb2.AIResponse.SerializeToString, ), 'confirm': grpc.unary_unary_rpc_method_handler( servicer.confirm, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.BoolResponse.SerializeToString, + request_deserializer=connpy__pb2.StringRequest.FromString, + response_serializer=connpy__pb2.BoolResponse.SerializeToString, ), 'ask_copilot': grpc.unary_unary_rpc_method_handler( servicer.ask_copilot, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.CopilotRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.CopilotResponse.SerializeToString, + request_deserializer=connpy__pb2.CopilotRequest.FromString, + response_serializer=connpy__pb2.CopilotResponse.SerializeToString, ), 'list_sessions': grpc.unary_unary_rpc_method_handler( servicer.list_sessions, request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.SerializeToString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, ), 'delete_session': grpc.unary_unary_rpc_method_handler( servicer.delete_session, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, + request_deserializer=connpy__pb2.StringRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'configure_provider': grpc.unary_unary_rpc_method_handler( servicer.configure_provider, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ProviderRequest.FromString, + request_deserializer=connpy__pb2.ProviderRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'configure_mcp': grpc.unary_unary_rpc_method_handler( servicer.configure_mcp, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.MCPRequest.FromString, + request_deserializer=connpy__pb2.MCPRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), + 'list_mcp_servers': grpc.unary_unary_rpc_method_handler( + servicer.list_mcp_servers, + request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, + ), 'load_session_data': grpc.unary_unary_rpc_method_handler( servicer.load_session_data, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.StringRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -116,32 +121,32 @@ el.replaceWith(d); 'get_settings': grpc.unary_unary_rpc_method_handler( servicer.get_settings, request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), 'get_default_dir': grpc.unary_unary_rpc_method_handler( servicer.get_default_dir, request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StringResponse.SerializeToString, + response_serializer=connpy__pb2.StringResponse.SerializeToString, ), 'set_config_folder': grpc.unary_unary_rpc_method_handler( servicer.set_config_folder, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, + request_deserializer=connpy__pb2.StringRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'update_setting': grpc.unary_unary_rpc_method_handler( servicer.update_setting, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.UpdateRequest.FromString, + request_deserializer=connpy__pb2.UpdateRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'encrypt_password': grpc.unary_unary_rpc_method_handler( servicer.encrypt_password, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StringResponse.SerializeToString, + request_deserializer=connpy__pb2.StringRequest.FromString, + response_serializer=connpy__pb2.StringResponse.SerializeToString, ), 'apply_theme_from_file': grpc.unary_unary_rpc_method_handler( servicer.apply_theme_from_file, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.StringRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -163,23 +168,23 @@ el.replaceWith(d); rpc_method_handlers = { 'run_commands': grpc.unary_stream_rpc_method_handler( servicer.run_commands, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.RunRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.NodeRunResult.SerializeToString, + request_deserializer=connpy__pb2.RunRequest.FromString, + response_serializer=connpy__pb2.NodeRunResult.SerializeToString, ), 'test_commands': grpc.unary_stream_rpc_method_handler( servicer.test_commands, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.TestRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.NodeRunResult.SerializeToString, + request_deserializer=connpy__pb2.TestRequest.FromString, + response_serializer=connpy__pb2.NodeRunResult.SerializeToString, ), 'run_cli_script': grpc.unary_unary_rpc_method_handler( servicer.run_cli_script, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ScriptRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.ScriptRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), 'run_yaml_playbook': grpc.unary_unary_rpc_method_handler( servicer.run_yaml_playbook, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ScriptRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.ScriptRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -201,17 +206,17 @@ el.replaceWith(d); rpc_method_handlers = { 'export_to_file': grpc.unary_unary_rpc_method_handler( servicer.export_to_file, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ExportRequest.FromString, + request_deserializer=connpy__pb2.ExportRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'import_from_file': grpc.unary_unary_rpc_method_handler( servicer.import_from_file, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.FromString, + request_deserializer=connpy__pb2.StringRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'set_reserved_names': grpc.unary_unary_rpc_method_handler( servicer.set_reserved_names, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ListRequest.FromString, + request_deserializer=connpy__pb2.ListRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), } @@ -234,23 +239,23 @@ el.replaceWith(d); rpc_method_handlers = { 'list_nodes': grpc.unary_unary_rpc_method_handler( servicer.list_nodes, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.FilterRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.SerializeToString, + request_deserializer=connpy__pb2.FilterRequest.FromString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, ), 'list_folders': grpc.unary_unary_rpc_method_handler( servicer.list_folders, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.FilterRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.SerializeToString, + request_deserializer=connpy__pb2.FilterRequest.FromString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, ), 'get_node_details': grpc.unary_unary_rpc_method_handler( servicer.get_node_details, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.IdRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), 'explode_unique': grpc.unary_unary_rpc_method_handler( servicer.explode_unique, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.SerializeToString, + request_deserializer=connpy__pb2.IdRequest.FromString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, ), 'generate_cache': grpc.unary_unary_rpc_method_handler( servicer.generate_cache, @@ -259,53 +264,53 @@ el.replaceWith(d); ), 'add_node': grpc.unary_unary_rpc_method_handler( servicer.add_node, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.FromString, + request_deserializer=connpy__pb2.NodeRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'update_node': grpc.unary_unary_rpc_method_handler( servicer.update_node, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.FromString, + request_deserializer=connpy__pb2.NodeRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'delete_node': grpc.unary_unary_rpc_method_handler( servicer.delete_node, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.DeleteRequest.FromString, + request_deserializer=connpy__pb2.DeleteRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'move_node': grpc.unary_unary_rpc_method_handler( servicer.move_node, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.MoveRequest.FromString, + request_deserializer=connpy__pb2.MoveRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'bulk_add': grpc.unary_unary_rpc_method_handler( servicer.bulk_add, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.BulkRequest.FromString, + request_deserializer=connpy__pb2.BulkRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'validate_parent_folder': grpc.unary_unary_rpc_method_handler( servicer.validate_parent_folder, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, + request_deserializer=connpy__pb2.IdRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'set_reserved_names': grpc.unary_unary_rpc_method_handler( servicer.set_reserved_names, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ListRequest.FromString, + request_deserializer=connpy__pb2.ListRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'interact_node': grpc.stream_stream_rpc_method_handler( servicer.interact_node, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.InteractRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.InteractResponse.SerializeToString, + request_deserializer=connpy__pb2.InteractRequest.FromString, + response_serializer=connpy__pb2.InteractResponse.SerializeToString, ), 'full_replace': grpc.unary_unary_rpc_method_handler( servicer.full_replace, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.FromString, + request_deserializer=connpy__pb2.FullReplaceRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'get_inventory': grpc.unary_unary_rpc_method_handler( servicer.get_inventory, request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.SerializeToString, + response_serializer=connpy__pb2.FullReplaceRequest.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -328,26 +333,26 @@ el.replaceWith(d); 'list_plugins': grpc.unary_unary_rpc_method_handler( servicer.list_plugins, request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.SerializeToString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, ), 'add_plugin': grpc.unary_unary_rpc_method_handler( servicer.add_plugin, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.PluginRequest.FromString, + request_deserializer=connpy__pb2.PluginRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'delete_plugin': grpc.unary_unary_rpc_method_handler( servicer.delete_plugin, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, + request_deserializer=connpy__pb2.IdRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'enable_plugin': grpc.unary_unary_rpc_method_handler( servicer.enable_plugin, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, + request_deserializer=connpy__pb2.IdRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'disable_plugin': grpc.unary_unary_rpc_method_handler( servicer.disable_plugin, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, + request_deserializer=connpy__pb2.IdRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), } @@ -370,32 +375,32 @@ el.replaceWith(d); rpc_method_handlers = { 'list_profiles': grpc.unary_unary_rpc_method_handler( servicer.list_profiles, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.FilterRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.SerializeToString, + request_deserializer=connpy__pb2.FilterRequest.FromString, + response_serializer=connpy__pb2.ValueResponse.SerializeToString, ), 'get_profile': grpc.unary_unary_rpc_method_handler( servicer.get_profile, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.ProfileRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.ProfileRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), 'add_profile': grpc.unary_unary_rpc_method_handler( servicer.add_profile, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.FromString, + request_deserializer=connpy__pb2.NodeRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'resolve_node_data': grpc.unary_unary_rpc_method_handler( servicer.resolve_node_data, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.StructRequest.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.SerializeToString, + request_deserializer=connpy__pb2.StructRequest.FromString, + response_serializer=connpy__pb2.StructResponse.SerializeToString, ), 'delete_profile': grpc.unary_unary_rpc_method_handler( servicer.delete_profile, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.FromString, + request_deserializer=connpy__pb2.IdRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'update_profile': grpc.unary_unary_rpc_method_handler( servicer.update_profile, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.FromString, + request_deserializer=connpy__pb2.NodeRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), } @@ -418,12 +423,12 @@ el.replaceWith(d); rpc_method_handlers = { 'start_api': grpc.unary_unary_rpc_method_handler( servicer.start_api, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IntRequest.FromString, + request_deserializer=connpy__pb2.IntRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'debug_api': grpc.unary_unary_rpc_method_handler( servicer.debug_api, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IntRequest.FromString, + request_deserializer=connpy__pb2.IntRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'stop_api': grpc.unary_unary_rpc_method_handler( @@ -433,13 +438,13 @@ el.replaceWith(d); ), 'restart_api': grpc.unary_unary_rpc_method_handler( servicer.restart_api, - request_deserializer=connpy_dot_proto_dot_connpy__pb2.IntRequest.FromString, + request_deserializer=connpy__pb2.IntRequest.FromString, response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, ), 'get_api_status': grpc.unary_unary_rpc_method_handler( servicer.get_api_status, request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=connpy_dot_proto_dot_connpy__pb2.BoolResponse.SerializeToString, + response_serializer=connpy__pb2.BoolResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -480,8 +485,8 @@ el.replaceWith(d); request_iterator, target, '/connpy.AIService/ask', - connpy_dot_proto_dot_connpy__pb2.AskRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.AIResponse.FromString, + connpy__pb2.AskRequest.SerializeToString, + connpy__pb2.AIResponse.FromString, options, channel_credentials, insecure, @@ -507,8 +512,8 @@ el.replaceWith(d); request, target, '/connpy.AIService/confirm', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.BoolResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.BoolResponse.FromString, options, channel_credentials, insecure, @@ -534,8 +539,8 @@ el.replaceWith(d); request, target, '/connpy.AIService/ask_copilot', - connpy_dot_proto_dot_connpy__pb2.CopilotRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.CopilotResponse.FromString, + connpy__pb2.CopilotRequest.SerializeToString, + connpy__pb2.CopilotResponse.FromString, options, channel_credentials, insecure, @@ -562,7 +567,7 @@ el.replaceWith(d); target, '/connpy.AIService/list_sessions', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -588,7 +593,7 @@ el.replaceWith(d); request, target, '/connpy.AIService/delete_session', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -615,7 +620,7 @@ el.replaceWith(d); request, target, '/connpy.AIService/configure_provider', - connpy_dot_proto_dot_connpy__pb2.ProviderRequest.SerializeToString, + connpy__pb2.ProviderRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -642,7 +647,7 @@ el.replaceWith(d); request, target, '/connpy.AIService/configure_mcp', - connpy_dot_proto_dot_connpy__pb2.MCPRequest.SerializeToString, + connpy__pb2.MCPRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -654,6 +659,33 @@ el.replaceWith(d); metadata, _registered_method=True) + @staticmethod + def list_mcp_servers(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/connpy.AIService/list_mcp_servers', + google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + connpy__pb2.ValueResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def load_session_data(request, target, @@ -669,8 +701,8 @@ el.replaceWith(d); request, target, '/connpy.AIService/load_session_data', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -707,8 +739,8 @@ def ask(request_iterator, request_iterator, target, '/connpy.AIService/ask', - connpy_dot_proto_dot_connpy__pb2.AskRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.AIResponse.FromString, + connpy__pb2.AskRequest.SerializeToString, + connpy__pb2.AIResponse.FromString, options, channel_credentials, insecure, @@ -744,8 +776,8 @@ def ask_copilot(request, request, target, '/connpy.AIService/ask_copilot', - connpy_dot_proto_dot_connpy__pb2.CopilotRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.CopilotResponse.FromString, + connpy__pb2.CopilotRequest.SerializeToString, + connpy__pb2.CopilotResponse.FromString, options, channel_credentials, insecure, @@ -781,7 +813,7 @@ def configure_mcp(request, request, target, '/connpy.AIService/configure_mcp', - connpy_dot_proto_dot_connpy__pb2.MCPRequest.SerializeToString, + connpy__pb2.MCPRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -818,7 +850,7 @@ def configure_provider(request, request, target, '/connpy.AIService/configure_provider', - connpy_dot_proto_dot_connpy__pb2.ProviderRequest.SerializeToString, + connpy__pb2.ProviderRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -855,8 +887,8 @@ def confirm(request, request, target, '/connpy.AIService/confirm', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.BoolResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.BoolResponse.FromString, options, channel_credentials, insecure, @@ -892,7 +924,7 @@ def delete_session(request, request, target, '/connpy.AIService/delete_session', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -906,6 +938,43 @@ def delete_session(request,
+
+def list_mcp_servers(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None)
+
+
+
+ +Expand source code + +
@staticmethod
+def list_mcp_servers(request,
+        target,
+        options=(),
+        channel_credentials=None,
+        call_credentials=None,
+        insecure=False,
+        compression=None,
+        wait_for_ready=None,
+        timeout=None,
+        metadata=None):
+    return grpc.experimental.unary_unary(
+        request,
+        target,
+        '/connpy.AIService/list_mcp_servers',
+        google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString,
+        connpy__pb2.ValueResponse.FromString,
+        options,
+        channel_credentials,
+        insecure,
+        call_credentials,
+        compression,
+        wait_for_ready,
+        timeout,
+        metadata,
+        _registered_method=True)
+
+
+
def list_sessions(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None)
@@ -930,7 +999,7 @@ def list_sessions(request, target, '/connpy.AIService/list_sessions', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -966,8 +1035,8 @@ def load_session_data(request, request, target, '/connpy.AIService/load_session_data', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1035,6 +1104,12 @@ def load_session_data(request, context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def list_mcp_servers(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def load_session_data(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) @@ -1144,6 +1219,22 @@ def load_session_data(request,

Missing associated documentation comment in .proto file.

+
+def list_mcp_servers(self, request, context) +
+
+
+ +Expand source code + +
def list_mcp_servers(self, request, context):
+    """Missing associated documentation comment in .proto file."""
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+

Missing associated documentation comment in .proto file.

+
def list_sessions(self, request, context)
@@ -1198,43 +1289,48 @@ def load_session_data(request, """ self.ask = channel.stream_stream( '/connpy.AIService/ask', - request_serializer=connpy_dot_proto_dot_connpy__pb2.AskRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.AIResponse.FromString, + request_serializer=connpy__pb2.AskRequest.SerializeToString, + response_deserializer=connpy__pb2.AIResponse.FromString, _registered_method=True) self.confirm = channel.unary_unary( '/connpy.AIService/confirm', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.BoolResponse.FromString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, + response_deserializer=connpy__pb2.BoolResponse.FromString, _registered_method=True) self.ask_copilot = channel.unary_unary( '/connpy.AIService/ask_copilot', - request_serializer=connpy_dot_proto_dot_connpy__pb2.CopilotRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.CopilotResponse.FromString, + request_serializer=connpy__pb2.CopilotRequest.SerializeToString, + response_deserializer=connpy__pb2.CopilotResponse.FromString, _registered_method=True) self.list_sessions = channel.unary_unary( '/connpy.AIService/list_sessions', request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + response_deserializer=connpy__pb2.ValueResponse.FromString, _registered_method=True) self.delete_session = channel.unary_unary( '/connpy.AIService/delete_session', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.configure_provider = channel.unary_unary( '/connpy.AIService/configure_provider', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ProviderRequest.SerializeToString, + request_serializer=connpy__pb2.ProviderRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.configure_mcp = channel.unary_unary( '/connpy.AIService/configure_mcp', - request_serializer=connpy_dot_proto_dot_connpy__pb2.MCPRequest.SerializeToString, + request_serializer=connpy__pb2.MCPRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) + self.list_mcp_servers = channel.unary_unary( + '/connpy.AIService/list_mcp_servers', + request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + response_deserializer=connpy__pb2.ValueResponse.FromString, + _registered_method=True) self.load_session_data = channel.unary_unary( '/connpy.AIService/load_session_data', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True)

Missing associated documentation comment in .proto file.

@@ -1272,7 +1368,7 @@ def load_session_data(request, target, '/connpy.ConfigService/get_settings', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1299,7 +1395,7 @@ def load_session_data(request, target, '/connpy.ConfigService/get_default_dir', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StringResponse.FromString, + connpy__pb2.StringResponse.FromString, options, channel_credentials, insecure, @@ -1325,7 +1421,7 @@ def load_session_data(request, request, target, '/connpy.ConfigService/set_config_folder', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -1352,7 +1448,7 @@ def load_session_data(request, request, target, '/connpy.ConfigService/update_setting', - connpy_dot_proto_dot_connpy__pb2.UpdateRequest.SerializeToString, + connpy__pb2.UpdateRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -1379,8 +1475,8 @@ def load_session_data(request, request, target, '/connpy.ConfigService/encrypt_password', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StringResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringResponse.FromString, options, channel_credentials, insecure, @@ -1406,8 +1502,8 @@ def load_session_data(request, request, target, '/connpy.ConfigService/apply_theme_from_file', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1444,8 +1540,8 @@ def apply_theme_from_file(request, request, target, '/connpy.ConfigService/apply_theme_from_file', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1481,8 +1577,8 @@ def encrypt_password(request, request, target, '/connpy.ConfigService/encrypt_password', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StringResponse.FromString, + connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringResponse.FromString, options, channel_credentials, insecure, @@ -1519,7 +1615,7 @@ def get_default_dir(request, target, '/connpy.ConfigService/get_default_dir', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StringResponse.FromString, + connpy__pb2.StringResponse.FromString, options, channel_credentials, insecure, @@ -1556,7 +1652,7 @@ def get_settings(request, target, '/connpy.ConfigService/get_settings', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1592,7 +1688,7 @@ def set_config_folder(request, request, target, '/connpy.ConfigService/set_config_folder', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -1629,7 +1725,7 @@ def update_setting(request, request, target, '/connpy.ConfigService/update_setting', - connpy_dot_proto_dot_connpy__pb2.UpdateRequest.SerializeToString, + connpy__pb2.UpdateRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -1818,32 +1914,32 @@ def update_setting(request, self.get_settings = channel.unary_unary( '/connpy.ConfigService/get_settings', request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True) self.get_default_dir = channel.unary_unary( '/connpy.ConfigService/get_default_dir', request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StringResponse.FromString, + response_deserializer=connpy__pb2.StringResponse.FromString, _registered_method=True) self.set_config_folder = channel.unary_unary( '/connpy.ConfigService/set_config_folder', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.update_setting = channel.unary_unary( '/connpy.ConfigService/update_setting', - request_serializer=connpy_dot_proto_dot_connpy__pb2.UpdateRequest.SerializeToString, + request_serializer=connpy__pb2.UpdateRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.encrypt_password = channel.unary_unary( '/connpy.ConfigService/encrypt_password', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StringResponse.FromString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, + response_deserializer=connpy__pb2.StringResponse.FromString, _registered_method=True) self.apply_theme_from_file = channel.unary_unary( '/connpy.ConfigService/apply_theme_from_file', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True)

Missing associated documentation comment in .proto file.

@@ -1880,8 +1976,8 @@ def update_setting(request, request, target, '/connpy.ExecutionService/run_commands', - connpy_dot_proto_dot_connpy__pb2.RunRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.NodeRunResult.FromString, + connpy__pb2.RunRequest.SerializeToString, + connpy__pb2.NodeRunResult.FromString, options, channel_credentials, insecure, @@ -1907,8 +2003,8 @@ def update_setting(request, request, target, '/connpy.ExecutionService/test_commands', - connpy_dot_proto_dot_connpy__pb2.TestRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.NodeRunResult.FromString, + connpy__pb2.TestRequest.SerializeToString, + connpy__pb2.NodeRunResult.FromString, options, channel_credentials, insecure, @@ -1934,8 +2030,8 @@ def update_setting(request, request, target, '/connpy.ExecutionService/run_cli_script', - connpy_dot_proto_dot_connpy__pb2.ScriptRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.ScriptRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1961,8 +2057,8 @@ def update_setting(request, request, target, '/connpy.ExecutionService/run_yaml_playbook', - connpy_dot_proto_dot_connpy__pb2.ScriptRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.ScriptRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -1999,8 +2095,8 @@ def run_cli_script(request, request, target, '/connpy.ExecutionService/run_cli_script', - connpy_dot_proto_dot_connpy__pb2.ScriptRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.ScriptRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -2036,8 +2132,8 @@ def run_commands(request, request, target, '/connpy.ExecutionService/run_commands', - connpy_dot_proto_dot_connpy__pb2.RunRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.NodeRunResult.FromString, + connpy__pb2.RunRequest.SerializeToString, + connpy__pb2.NodeRunResult.FromString, options, channel_credentials, insecure, @@ -2073,8 +2169,8 @@ def run_yaml_playbook(request, request, target, '/connpy.ExecutionService/run_yaml_playbook', - connpy_dot_proto_dot_connpy__pb2.ScriptRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.ScriptRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -2110,8 +2206,8 @@ def test_commands(request, request, target, '/connpy.ExecutionService/test_commands', - connpy_dot_proto_dot_connpy__pb2.TestRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.NodeRunResult.FromString, + connpy__pb2.TestRequest.SerializeToString, + connpy__pb2.NodeRunResult.FromString, options, channel_credentials, insecure, @@ -2254,23 +2350,23 @@ def test_commands(request, """ self.run_commands = channel.unary_stream( '/connpy.ExecutionService/run_commands', - request_serializer=connpy_dot_proto_dot_connpy__pb2.RunRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.NodeRunResult.FromString, + request_serializer=connpy__pb2.RunRequest.SerializeToString, + response_deserializer=connpy__pb2.NodeRunResult.FromString, _registered_method=True) self.test_commands = channel.unary_stream( '/connpy.ExecutionService/test_commands', - request_serializer=connpy_dot_proto_dot_connpy__pb2.TestRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.NodeRunResult.FromString, + request_serializer=connpy__pb2.TestRequest.SerializeToString, + response_deserializer=connpy__pb2.NodeRunResult.FromString, _registered_method=True) self.run_cli_script = channel.unary_unary( '/connpy.ExecutionService/run_cli_script', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ScriptRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.ScriptRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True) self.run_yaml_playbook = channel.unary_unary( '/connpy.ExecutionService/run_yaml_playbook', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ScriptRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.ScriptRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True)

Missing associated documentation comment in .proto file.

@@ -2307,7 +2403,7 @@ def test_commands(request, request, target, '/connpy.ImportExportService/export_to_file', - connpy_dot_proto_dot_connpy__pb2.ExportRequest.SerializeToString, + connpy__pb2.ExportRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2334,7 +2430,7 @@ def test_commands(request, request, target, '/connpy.ImportExportService/import_from_file', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2361,7 +2457,7 @@ def test_commands(request, request, target, '/connpy.ImportExportService/set_reserved_names', - connpy_dot_proto_dot_connpy__pb2.ListRequest.SerializeToString, + connpy__pb2.ListRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2399,7 +2495,7 @@ def export_to_file(request, request, target, '/connpy.ImportExportService/export_to_file', - connpy_dot_proto_dot_connpy__pb2.ExportRequest.SerializeToString, + connpy__pb2.ExportRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2436,7 +2532,7 @@ def import_from_file(request, request, target, '/connpy.ImportExportService/import_from_file', - connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + connpy__pb2.StringRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2473,7 +2569,7 @@ def set_reserved_names(request, request, target, '/connpy.ImportExportService/set_reserved_names', - connpy_dot_proto_dot_connpy__pb2.ListRequest.SerializeToString, + connpy__pb2.ListRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2595,17 +2691,17 @@ def set_reserved_names(request, """ self.export_to_file = channel.unary_unary( '/connpy.ImportExportService/export_to_file', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ExportRequest.SerializeToString, + request_serializer=connpy__pb2.ExportRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.import_from_file = channel.unary_unary( '/connpy.ImportExportService/import_from_file', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StringRequest.SerializeToString, + request_serializer=connpy__pb2.StringRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.set_reserved_names = channel.unary_unary( '/connpy.ImportExportService/set_reserved_names', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ListRequest.SerializeToString, + request_serializer=connpy__pb2.ListRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) @@ -2643,8 +2739,8 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/list_nodes', - connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.FilterRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -2670,8 +2766,8 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/list_folders', - connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.FilterRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -2697,8 +2793,8 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/get_node_details', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -2724,8 +2820,8 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/explode_unique', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -2778,7 +2874,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/add_node', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2805,7 +2901,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/update_node', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2832,7 +2928,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/delete_node', - connpy_dot_proto_dot_connpy__pb2.DeleteRequest.SerializeToString, + connpy__pb2.DeleteRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2859,7 +2955,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/move_node', - connpy_dot_proto_dot_connpy__pb2.MoveRequest.SerializeToString, + connpy__pb2.MoveRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2886,7 +2982,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/bulk_add', - connpy_dot_proto_dot_connpy__pb2.BulkRequest.SerializeToString, + connpy__pb2.BulkRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2913,7 +3009,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/validate_parent_folder', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2940,7 +3036,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/set_reserved_names', - connpy_dot_proto_dot_connpy__pb2.ListRequest.SerializeToString, + connpy__pb2.ListRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -2967,8 +3063,8 @@ def set_reserved_names(request, request_iterator, target, '/connpy.NodeService/interact_node', - connpy_dot_proto_dot_connpy__pb2.InteractRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.InteractResponse.FromString, + connpy__pb2.InteractRequest.SerializeToString, + connpy__pb2.InteractResponse.FromString, options, channel_credentials, insecure, @@ -2994,7 +3090,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/full_replace', - connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.SerializeToString, + connpy__pb2.FullReplaceRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3022,7 +3118,7 @@ def set_reserved_names(request, target, '/connpy.NodeService/get_inventory', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.FromString, + connpy__pb2.FullReplaceRequest.FromString, options, channel_credentials, insecure, @@ -3059,7 +3155,7 @@ def add_node(request, request, target, '/connpy.NodeService/add_node', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3096,7 +3192,7 @@ def bulk_add(request, request, target, '/connpy.NodeService/bulk_add', - connpy_dot_proto_dot_connpy__pb2.BulkRequest.SerializeToString, + connpy__pb2.BulkRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3133,7 +3229,7 @@ def delete_node(request, request, target, '/connpy.NodeService/delete_node', - connpy_dot_proto_dot_connpy__pb2.DeleteRequest.SerializeToString, + connpy__pb2.DeleteRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3170,8 +3266,8 @@ def explode_unique(request, request, target, '/connpy.NodeService/explode_unique', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -3207,7 +3303,7 @@ def full_replace(request, request, target, '/connpy.NodeService/full_replace', - connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.SerializeToString, + connpy__pb2.FullReplaceRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3282,7 +3378,7 @@ def get_inventory(request, target, '/connpy.NodeService/get_inventory', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.FromString, + connpy__pb2.FullReplaceRequest.FromString, options, channel_credentials, insecure, @@ -3318,8 +3414,8 @@ def get_node_details(request, request, target, '/connpy.NodeService/get_node_details', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -3355,8 +3451,8 @@ def interact_node(request_iterator, request_iterator, target, '/connpy.NodeService/interact_node', - connpy_dot_proto_dot_connpy__pb2.InteractRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.InteractResponse.FromString, + connpy__pb2.InteractRequest.SerializeToString, + connpy__pb2.InteractResponse.FromString, options, channel_credentials, insecure, @@ -3392,8 +3488,8 @@ def list_folders(request, request, target, '/connpy.NodeService/list_folders', - connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.FilterRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -3429,8 +3525,8 @@ def list_nodes(request, request, target, '/connpy.NodeService/list_nodes', - connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.FilterRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -3466,7 +3562,7 @@ def move_node(request, request, target, '/connpy.NodeService/move_node', - connpy_dot_proto_dot_connpy__pb2.MoveRequest.SerializeToString, + connpy__pb2.MoveRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3503,7 +3599,7 @@ def set_reserved_names(request, request, target, '/connpy.NodeService/set_reserved_names', - connpy_dot_proto_dot_connpy__pb2.ListRequest.SerializeToString, + connpy__pb2.ListRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3540,7 +3636,7 @@ def update_node(request, request, target, '/connpy.NodeService/update_node', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3577,7 +3673,7 @@ def validate_parent_folder(request, request, target, '/connpy.NodeService/validate_parent_folder', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -3963,23 +4059,23 @@ def validate_parent_folder(request, """ self.list_nodes = channel.unary_unary( '/connpy.NodeService/list_nodes', - request_serializer=connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + request_serializer=connpy__pb2.FilterRequest.SerializeToString, + response_deserializer=connpy__pb2.ValueResponse.FromString, _registered_method=True) self.list_folders = channel.unary_unary( '/connpy.NodeService/list_folders', - request_serializer=connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + request_serializer=connpy__pb2.FilterRequest.SerializeToString, + response_deserializer=connpy__pb2.ValueResponse.FromString, _registered_method=True) self.get_node_details = channel.unary_unary( '/connpy.NodeService/get_node_details', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True) self.explode_unique = channel.unary_unary( '/connpy.NodeService/explode_unique', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, + response_deserializer=connpy__pb2.ValueResponse.FromString, _registered_method=True) self.generate_cache = channel.unary_unary( '/connpy.NodeService/generate_cache', @@ -3988,53 +4084,53 @@ def validate_parent_folder(request, _registered_method=True) self.add_node = channel.unary_unary( '/connpy.NodeService/add_node', - request_serializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + request_serializer=connpy__pb2.NodeRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.update_node = channel.unary_unary( '/connpy.NodeService/update_node', - request_serializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + request_serializer=connpy__pb2.NodeRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.delete_node = channel.unary_unary( '/connpy.NodeService/delete_node', - request_serializer=connpy_dot_proto_dot_connpy__pb2.DeleteRequest.SerializeToString, + request_serializer=connpy__pb2.DeleteRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.move_node = channel.unary_unary( '/connpy.NodeService/move_node', - request_serializer=connpy_dot_proto_dot_connpy__pb2.MoveRequest.SerializeToString, + request_serializer=connpy__pb2.MoveRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.bulk_add = channel.unary_unary( '/connpy.NodeService/bulk_add', - request_serializer=connpy_dot_proto_dot_connpy__pb2.BulkRequest.SerializeToString, + request_serializer=connpy__pb2.BulkRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.validate_parent_folder = channel.unary_unary( '/connpy.NodeService/validate_parent_folder', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.set_reserved_names = channel.unary_unary( '/connpy.NodeService/set_reserved_names', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ListRequest.SerializeToString, + request_serializer=connpy__pb2.ListRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.interact_node = channel.stream_stream( '/connpy.NodeService/interact_node', - request_serializer=connpy_dot_proto_dot_connpy__pb2.InteractRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.InteractResponse.FromString, + request_serializer=connpy__pb2.InteractRequest.SerializeToString, + response_deserializer=connpy__pb2.InteractResponse.FromString, _registered_method=True) self.full_replace = channel.unary_unary( '/connpy.NodeService/full_replace', - request_serializer=connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.SerializeToString, + request_serializer=connpy__pb2.FullReplaceRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.get_inventory = channel.unary_unary( '/connpy.NodeService/get_inventory', request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.FullReplaceRequest.FromString, + response_deserializer=connpy__pb2.FullReplaceRequest.FromString, _registered_method=True)

Missing associated documentation comment in .proto file.

@@ -4072,7 +4168,7 @@ def validate_parent_folder(request, target, '/connpy.PluginService/list_plugins', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -4098,7 +4194,7 @@ def validate_parent_folder(request, request, target, '/connpy.PluginService/add_plugin', - connpy_dot_proto_dot_connpy__pb2.PluginRequest.SerializeToString, + connpy__pb2.PluginRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4125,7 +4221,7 @@ def validate_parent_folder(request, request, target, '/connpy.PluginService/delete_plugin', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4152,7 +4248,7 @@ def validate_parent_folder(request, request, target, '/connpy.PluginService/enable_plugin', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4179,7 +4275,7 @@ def validate_parent_folder(request, request, target, '/connpy.PluginService/disable_plugin', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4217,7 +4313,7 @@ def add_plugin(request, request, target, '/connpy.PluginService/add_plugin', - connpy_dot_proto_dot_connpy__pb2.PluginRequest.SerializeToString, + connpy__pb2.PluginRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4254,7 +4350,7 @@ def delete_plugin(request, request, target, '/connpy.PluginService/delete_plugin', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4291,7 +4387,7 @@ def disable_plugin(request, request, target, '/connpy.PluginService/disable_plugin', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4328,7 +4424,7 @@ def enable_plugin(request, request, target, '/connpy.PluginService/enable_plugin', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4366,7 +4462,7 @@ def list_plugins(request, target, '/connpy.PluginService/list_plugins', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -4532,26 +4628,26 @@ def list_plugins(request, self.list_plugins = channel.unary_unary( '/connpy.PluginService/list_plugins', request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + response_deserializer=connpy__pb2.ValueResponse.FromString, _registered_method=True) self.add_plugin = channel.unary_unary( '/connpy.PluginService/add_plugin', - request_serializer=connpy_dot_proto_dot_connpy__pb2.PluginRequest.SerializeToString, + request_serializer=connpy__pb2.PluginRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.delete_plugin = channel.unary_unary( '/connpy.PluginService/delete_plugin', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.enable_plugin = channel.unary_unary( '/connpy.PluginService/enable_plugin', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.disable_plugin = channel.unary_unary( '/connpy.PluginService/disable_plugin', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) @@ -4589,8 +4685,8 @@ def list_plugins(request, request, target, '/connpy.ProfileService/list_profiles', - connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.FilterRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -4616,8 +4712,8 @@ def list_plugins(request, request, target, '/connpy.ProfileService/get_profile', - connpy_dot_proto_dot_connpy__pb2.ProfileRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.ProfileRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -4643,7 +4739,7 @@ def list_plugins(request, request, target, '/connpy.ProfileService/add_profile', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4670,8 +4766,8 @@ def list_plugins(request, request, target, '/connpy.ProfileService/resolve_node_data', - connpy_dot_proto_dot_connpy__pb2.StructRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StructRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -4697,7 +4793,7 @@ def list_plugins(request, request, target, '/connpy.ProfileService/delete_profile', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4724,7 +4820,7 @@ def list_plugins(request, request, target, '/connpy.ProfileService/update_profile', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4762,7 +4858,7 @@ def add_profile(request, request, target, '/connpy.ProfileService/add_profile', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4799,7 +4895,7 @@ def delete_profile(request, request, target, '/connpy.ProfileService/delete_profile', - connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + connpy__pb2.IdRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -4836,8 +4932,8 @@ def get_profile(request, request, target, '/connpy.ProfileService/get_profile', - connpy_dot_proto_dot_connpy__pb2.ProfileRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.ProfileRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -4873,8 +4969,8 @@ def list_profiles(request, request, target, '/connpy.ProfileService/list_profiles', - connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + connpy__pb2.FilterRequest.SerializeToString, + connpy__pb2.ValueResponse.FromString, options, channel_credentials, insecure, @@ -4910,8 +5006,8 @@ def resolve_node_data(request, request, target, '/connpy.ProfileService/resolve_node_data', - connpy_dot_proto_dot_connpy__pb2.StructRequest.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + connpy__pb2.StructRequest.SerializeToString, + connpy__pb2.StructResponse.FromString, options, channel_credentials, insecure, @@ -4947,7 +5043,7 @@ def update_profile(request, request, target, '/connpy.ProfileService/update_profile', - connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + connpy__pb2.NodeRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5135,32 +5231,32 @@ def update_profile(request, """ self.list_profiles = channel.unary_unary( '/connpy.ProfileService/list_profiles', - request_serializer=connpy_dot_proto_dot_connpy__pb2.FilterRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.ValueResponse.FromString, + request_serializer=connpy__pb2.FilterRequest.SerializeToString, + response_deserializer=connpy__pb2.ValueResponse.FromString, _registered_method=True) self.get_profile = channel.unary_unary( '/connpy.ProfileService/get_profile', - request_serializer=connpy_dot_proto_dot_connpy__pb2.ProfileRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.ProfileRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True) self.add_profile = channel.unary_unary( '/connpy.ProfileService/add_profile', - request_serializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + request_serializer=connpy__pb2.NodeRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.resolve_node_data = channel.unary_unary( '/connpy.ProfileService/resolve_node_data', - request_serializer=connpy_dot_proto_dot_connpy__pb2.StructRequest.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.StructResponse.FromString, + request_serializer=connpy__pb2.StructRequest.SerializeToString, + response_deserializer=connpy__pb2.StructResponse.FromString, _registered_method=True) self.delete_profile = channel.unary_unary( '/connpy.ProfileService/delete_profile', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IdRequest.SerializeToString, + request_serializer=connpy__pb2.IdRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.update_profile = channel.unary_unary( '/connpy.ProfileService/update_profile', - request_serializer=connpy_dot_proto_dot_connpy__pb2.NodeRequest.SerializeToString, + request_serializer=connpy__pb2.NodeRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) @@ -5198,7 +5294,7 @@ def update_profile(request, request, target, '/connpy.SystemService/start_api', - connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + connpy__pb2.IntRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5225,7 +5321,7 @@ def update_profile(request, request, target, '/connpy.SystemService/debug_api', - connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + connpy__pb2.IntRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5279,7 +5375,7 @@ def update_profile(request, request, target, '/connpy.SystemService/restart_api', - connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + connpy__pb2.IntRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5307,7 +5403,7 @@ def update_profile(request, target, '/connpy.SystemService/get_api_status', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.BoolResponse.FromString, + connpy__pb2.BoolResponse.FromString, options, channel_credentials, insecure, @@ -5344,7 +5440,7 @@ def debug_api(request, request, target, '/connpy.SystemService/debug_api', - connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + connpy__pb2.IntRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5382,7 +5478,7 @@ def get_api_status(request, target, '/connpy.SystemService/get_api_status', google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - connpy_dot_proto_dot_connpy__pb2.BoolResponse.FromString, + connpy__pb2.BoolResponse.FromString, options, channel_credentials, insecure, @@ -5418,7 +5514,7 @@ def restart_api(request, request, target, '/connpy.SystemService/restart_api', - connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + connpy__pb2.IntRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5455,7 +5551,7 @@ def start_api(request, request, target, '/connpy.SystemService/start_api', - connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + connpy__pb2.IntRequest.SerializeToString, google_dot_protobuf_dot_empty__pb2.Empty.FromString, options, channel_credentials, @@ -5658,12 +5754,12 @@ def stop_api(request, """ self.start_api = channel.unary_unary( '/connpy.SystemService/start_api', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + request_serializer=connpy__pb2.IntRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.debug_api = channel.unary_unary( '/connpy.SystemService/debug_api', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + request_serializer=connpy__pb2.IntRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.stop_api = channel.unary_unary( @@ -5673,13 +5769,13 @@ def stop_api(request, _registered_method=True) self.restart_api = channel.unary_unary( '/connpy.SystemService/restart_api', - request_serializer=connpy_dot_proto_dot_connpy__pb2.IntRequest.SerializeToString, + request_serializer=connpy__pb2.IntRequest.SerializeToString, response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, _registered_method=True) self.get_api_status = channel.unary_unary( '/connpy.SystemService/get_api_status', request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=connpy_dot_proto_dot_connpy__pb2.BoolResponse.FromString, + response_deserializer=connpy__pb2.BoolResponse.FromString, _registered_method=True)

Missing associated documentation comment in .proto file.

@@ -5726,6 +5822,7 @@ def stop_api(request,
  • configure_provider
  • confirm
  • delete_session
  • +
  • list_mcp_servers
  • list_sessions
  • load_session_data
  • @@ -5739,6 +5836,7 @@ def stop_api(request,
  • configure_provider
  • confirm
  • delete_session
  • +
  • list_mcp_servers
  • list_sessions
  • load_session_data
  • @@ -5931,7 +6029,7 @@ def stop_api(request, diff --git a/docs/connpy/grpc_layer/index.html b/docs/connpy/grpc_layer/index.html index 83e1647..ad05c93 100644 --- a/docs/connpy/grpc_layer/index.html +++ b/docs/connpy/grpc_layer/index.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer API documentation @@ -102,7 +102,7 @@ el.replaceWith(d); diff --git a/docs/connpy/grpc_layer/remote_plugin_pb2.html b/docs/connpy/grpc_layer/remote_plugin_pb2.html index 6e7bb97..c841aa0 100644 --- a/docs/connpy/grpc_layer/remote_plugin_pb2.html +++ b/docs/connpy/grpc_layer/remote_plugin_pb2.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.remote_plugin_pb2 API documentation @@ -62,7 +62,7 @@ el.replaceWith(d);
    var DESCRIPTOR
    -
    +

    The type of the None singleton.

    @@ -81,7 +81,7 @@ el.replaceWith(d);
    var DESCRIPTOR
    -
    +

    The type of the None singleton.

    @@ -100,7 +100,7 @@ el.replaceWith(d);
    var DESCRIPTOR
    -
    +

    The type of the None singleton.

    @@ -119,7 +119,7 @@ el.replaceWith(d);
    var DESCRIPTOR
    -
    +

    The type of the None singleton.

    @@ -168,7 +168,7 @@ el.replaceWith(d); diff --git a/docs/connpy/grpc_layer/remote_plugin_pb2_grpc.html b/docs/connpy/grpc_layer/remote_plugin_pb2_grpc.html index 6372fcd..61ed251 100644 --- a/docs/connpy/grpc_layer/remote_plugin_pb2_grpc.html +++ b/docs/connpy/grpc_layer/remote_plugin_pb2_grpc.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.remote_plugin_pb2_grpc API documentation @@ -366,7 +366,7 @@ def invoke_plugin(request, diff --git a/docs/connpy/grpc_layer/server.html b/docs/connpy/grpc_layer/server.html index 3b3bf41..f22e46a 100644 --- a/docs/connpy/grpc_layer/server.html +++ b/docs/connpy/grpc_layer/server.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.server API documentation @@ -177,9 +177,11 @@ el.replaceWith(d); print(f"AI Task Error: {e}") traceback.print_exc() chunk_queue.put(("status", f"Error: {str(e)}")) + # Crucial: always send final_mark to avoid client deadlock + chunk_queue.put(("final_mark", {"response": f"Error: {str(e)}", "chat_history": history, "error": True})) def request_listener(): - nonlocal bridge, is_web, ai_thread, agent_instance + nonlocal bridge, is_web, ai_thread, agent_instance, history try: for req in request_iterator: if req.interrupt: @@ -193,12 +195,21 @@ el.replaceWith(d); if req.input_text: is_web = "web" in (req.session_id or "").lower() or (req.session_id or "").lower().startswith("ws-") + + # Hydrate history from client if it's the first interaction in this stream + if not history and req.chat_history: + from .utils import from_value + history = from_value(req.chat_history) or [] if not bridge: bridge = StatusBridge(chunk_queue, request_queue=request_queue, is_web=is_web) overrides = {} if req.engineer_model: overrides["engineer_model"] = req.engineer_model if req.engineer_api_key: overrides["engineer_api_key"] = req.engineer_api_key + if req.architect_model: overrides["architect_model"] = req.architect_model + if req.architect_api_key: overrides["architect_api_key"] = req.architect_api_key + if req.HasField("engineer_auth"): overrides["engineer_auth"] = from_struct(req.engineer_auth) + if req.HasField("architect_auth"): overrides["architect_auth"] = from_struct(req.architect_auth) # Start AI in its own thread so we can keep listening for interrupts ai_thread = threading.Thread( @@ -263,7 +274,8 @@ el.replaceWith(d); @handle_errors def list_sessions(self, request, context): - return connpy_pb2.ValueResponse(data=to_value(self.service.list_sessions())) + sessions, total = self.service.list_sessions() + return connpy_pb2.ValueResponse(data=to_value(sessions)) @handle_errors def delete_session(self, request, context): @@ -272,7 +284,8 @@ el.replaceWith(d); @handle_errors def configure_provider(self, request, context): - self.service.configure_provider(request.provider, request.model, request.api_key) + auth_dict = from_struct(request.auth) if request.HasField("auth") else None + self.service.configure_provider(request.provider, request.model, request.api_key, auth=auth_dict) return Empty() @handle_errors @@ -286,6 +299,11 @@ el.replaceWith(d); ) return Empty() + @handle_errors + def list_mcp_servers(self, request, context): + mcp_servers = self.service.list_mcp_servers() + return connpy_pb2.ValueResponse(data=to_value(mcp_servers)) + @handle_errors def load_session_data(self, request, context): return connpy_pb2.StructResponse(data=to_struct(self.service.load_session_data(request.value))) @@ -305,6 +323,7 @@ el.replaceWith(d);
  • configure_provider
  • confirm
  • delete_session
  • +
  • list_mcp_servers
  • list_sessions
  • load_session_data
  • @@ -975,7 +994,9 @@ interceptor chooses to service this RPC, or None otherwise.

    asyncio.run(n._async_interact_loop(remote_stream, resize_callback, copilot_handler=remote_copilot_handler)) except Exception as e: - pass + import traceback + print(f"[ERROR in run_async_loop] {e}") + traceback.print_exc() finally: n._teardown_interact_environment() response_queue.put(None) # Signal EOF @@ -1195,6 +1216,7 @@ interceptor chooses to service this RPC, or None otherwise.

    self.service = ProfileService(config) self.node_service = NodeService(config) + @handle_errors def list_profiles(self, request, context): f = request.filter_str if request.filter_str else None @@ -1261,6 +1283,7 @@ interceptor chooses to service this RPC, or None otherwise.

    self.on_interrupt = self._force_interrupt self.thread = None self.is_web = is_web + self.is_remote = True def _force_interrupt(self): """Forcefully raise KeyboardInterrupt in the target thread.""" @@ -1560,7 +1583,7 @@ interceptor chooses to service this RPC, or None otherwise.

    diff --git a/docs/connpy/grpc_layer/stubs.html b/docs/connpy/grpc_layer/stubs.html index d133dcd..7454a4d 100644 --- a/docs/connpy/grpc_layer/stubs.html +++ b/docs/connpy/grpc_layer/stubs.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.stubs API documentation @@ -122,6 +122,10 @@ el.replaceWith(d); ) if chat_history is not None: initial_req.chat_history.CopyFrom(to_value(chat_history)) + if "engineer_auth" in overrides and overrides["engineer_auth"]: + initial_req.engineer_auth.CopyFrom(to_struct(overrides["engineer_auth"])) + if "architect_auth" in overrides and overrides["architect_auth"]: + initial_req.architect_auth.CopyFrom(to_struct(overrides["architect_auth"])) req_queue.put(initial_req) @@ -135,6 +139,7 @@ el.replaceWith(d); full_content = "" header_printed = False + current_responder = "engineer" final_result = {"response": "", "chat_history": []} # Background thread to pull responses from gRPC into a local queue @@ -179,6 +184,10 @@ el.replaceWith(d); break if response.status_update: + if response.status_update.startswith("__RESPONDER__:"): + current_responder = response.status_update.split(":")[1].lower() + continue + if response.requires_confirmation: if status: status.stop() @@ -231,7 +240,9 @@ el.replaceWith(d); stable_console = RichConsole(theme=connpy_theme, file=get_original_stdout()) # Print header on first chunk - stable_console.print(Rule("[bold engineer]Network Engineer[/bold engineer]", style="engineer")) + alias = "architect" if current_responder == "architect" else "engineer" + role_label = "Network Architect" if current_responder == "architect" else "Network Engineer" + stable_console.print(Rule(f"[bold {alias}]{role_label}[/bold {alias}]", style=alias)) header_printed = True # Initialize parser @@ -283,16 +294,23 @@ el.replaceWith(d); return self.stub.confirm(connpy_pb2.StringRequest(value=input_text)).value @handle_errors - def list_sessions(self): - return from_value(self.stub.list_sessions(Empty()).data) + def list_sessions(self, limit=None): + from .utils import from_value + res = self.stub.list_sessions(Empty()) + sessions = from_value(res.data) or [] + if limit and len(sessions) > limit: + return sessions[:limit], len(sessions) + return sessions, len(sessions) @handle_errors def delete_session(self, session_id): self.stub.delete_session(connpy_pb2.StringRequest(value=session_id)) @handle_errors - def configure_provider(self, provider, model=None, api_key=None): + def configure_provider(self, provider, model=None, api_key=None, auth=None): req = connpy_pb2.ProviderRequest(provider=provider, model=model or "", api_key=api_key or "") + if auth: + req.auth.CopyFrom(to_struct(auth)) self.stub.configure_provider(req) @handle_errors @@ -306,6 +324,11 @@ el.replaceWith(d); ) self.stub.configure_mcp(req) + @handle_errors + def list_mcp_servers(self): + res = self.stub.list_mcp_servers(Empty()) + return from_value(res.data) or {} + @handle_errors def load_session_data(self, session_id): return from_struct(self.stub.load_session_data(connpy_pb2.StringRequest(value=session_id)).data) @@ -344,6 +367,10 @@ def ask(self, input_text, dryrun=False, chat_history=None, session_id=None, debu ) if chat_history is not None: initial_req.chat_history.CopyFrom(to_value(chat_history)) + if "engineer_auth" in overrides and overrides["engineer_auth"]: + initial_req.engineer_auth.CopyFrom(to_struct(overrides["engineer_auth"])) + if "architect_auth" in overrides and overrides["architect_auth"]: + initial_req.architect_auth.CopyFrom(to_struct(overrides["architect_auth"])) req_queue.put(initial_req) @@ -357,6 +384,7 @@ def ask(self, input_text, dryrun=False, chat_history=None, session_id=None, debu full_content = "" header_printed = False + current_responder = "engineer" final_result = {"response": "", "chat_history": []} # Background thread to pull responses from gRPC into a local queue @@ -401,6 +429,10 @@ def ask(self, input_text, dryrun=False, chat_history=None, session_id=None, debu break if response.status_update: + if response.status_update.startswith("__RESPONDER__:"): + current_responder = response.status_update.split(":")[1].lower() + continue + if response.requires_confirmation: if status: status.stop() @@ -453,7 +485,9 @@ def ask(self, input_text, dryrun=False, chat_history=None, session_id=None, debu stable_console = RichConsole(theme=connpy_theme, file=get_original_stdout()) # Print header on first chunk - stable_console.print(Rule("[bold engineer]Network Engineer[/bold engineer]", style="engineer")) + alias = "architect" if current_responder == "architect" else "engineer" + role_label = "Network Architect" if current_responder == "architect" else "Network Engineer" + stable_console.print(Rule(f"[bold {alias}]{role_label}[/bold {alias}]", style=alias)) header_printed = True # Initialize parser @@ -524,7 +558,7 @@ def configure_mcp(self, name, url=None, enabled=True, auto_load_on_os=None, remo
    -def configure_provider(self, provider, model=None, api_key=None) +def configure_provider(self, provider, model=None, api_key=None, auth=None)
    @@ -532,8 +566,10 @@ def configure_mcp(self, name, url=None, enabled=True, auto_load_on_os=None, remo Expand source code
    @handle_errors
    -def configure_provider(self, provider, model=None, api_key=None):
    +def configure_provider(self, provider, model=None, api_key=None, auth=None):
         req = connpy_pb2.ProviderRequest(provider=provider, model=model or "", api_key=api_key or "")
    +    if auth:
    +        req.auth.CopyFrom(to_struct(auth))
         self.stub.configure_provider(req)
    @@ -566,8 +602,8 @@ def delete_session(self, session_id):
    -
    -def list_sessions(self) +
    +def list_mcp_servers(self)
    @@ -575,8 +611,28 @@ def delete_session(self, session_id): Expand source code
    @handle_errors
    -def list_sessions(self):
    -    return from_value(self.stub.list_sessions(Empty()).data)
    +def list_mcp_servers(self): + res = self.stub.list_mcp_servers(Empty()) + return from_value(res.data) or {}
    +
    +
    +
    +
    +def list_sessions(self, limit=None) +
    +
    +
    + +Expand source code + +
    @handle_errors
    +def list_sessions(self, limit=None):
    +    from .utils import from_value
    +    res = self.stub.list_sessions(Empty())
    +    sessions = from_value(res.data) or []
    +    if limit and len(sessions) > limit:
    +        return sessions[:limit], len(sessions)
    +    return sessions, len(sessions)
    @@ -2470,6 +2526,7 @@ def stop_api(self):
  • configure_provider
  • confirm
  • delete_session
  • +
  • list_mcp_servers
  • list_sessions
  • load_session_data
  • @@ -2561,7 +2618,7 @@ def stop_api(self): diff --git a/docs/connpy/grpc_layer/utils.html b/docs/connpy/grpc_layer/utils.html index 571fbd2..da5286b 100644 --- a/docs/connpy/grpc_layer/utils.html +++ b/docs/connpy/grpc_layer/utils.html @@ -3,7 +3,7 @@ - + connpy.grpc_layer.utils API documentation @@ -138,7 +138,7 @@ el.replaceWith(d); diff --git a/docs/connpy/index.html b/docs/connpy/index.html index 5b68952..20af089 100644 --- a/docs/connpy/index.html +++ b/docs/connpy/index.html @@ -3,7 +3,7 @@ - + connpy API documentation
    -
    connpy.tests
    -
    -
    -
    connpy.tunnels
    @@ -620,7 +616,7 @@ indicating successful verification.

    class ai -(config,
    org=None,
    api_key=None,
    engineer_model=None,
    architect_model=None,
    engineer_api_key=None,
    architect_api_key=None,
    console=None,
    confirm_handler=None,
    trust=False)
    +(config,
    org=None,
    api_key=None,
    engineer_model=None,
    architect_model=None,
    engineer_api_key=None,
    architect_api_key=None,
    console=None,
    confirm_handler=None,
    trust=False,
    engineer_auth=None,
    architect_auth=None,
    **kwargs)
    @@ -638,7 +634,7 @@ class ai: r'^systemctl\s+status\s+', r'^journalctl\s+' ] - def __init__(self, config, org=None, api_key=None, engineer_model=None, architect_model=None, engineer_api_key=None, architect_api_key=None, console=None, confirm_handler=None, trust=False): + def __init__(self, config, org=None, api_key=None, engineer_model=None, architect_model=None, engineer_api_key=None, architect_api_key=None, console=None, confirm_handler=None, trust=False, engineer_auth=None, architect_auth=None, **kwargs): self.config = config self.console = console or printer.console self.confirm_handler = confirm_handler or self._local_confirm_handler @@ -657,6 +653,29 @@ class ai: self.engineer_key = engineer_api_key or aiconfig.get("engineer_api_key") self.architect_key = architect_api_key or aiconfig.get("architect_api_key") + # Auth configurations (Prioridad: Argumento -> Config) + self.engineer_auth = engineer_auth if engineer_auth is not None else aiconfig.get("engineer_auth") + if self.engineer_auth is None: + self.engineer_auth = {} + elif not isinstance(self.engineer_auth, dict): + self.engineer_auth = {} + + self.architect_auth = architect_auth if architect_auth is not None else aiconfig.get("architect_auth") + if self.architect_auth is None: + self.architect_auth = {} + elif not isinstance(self.architect_auth, dict): + self.architect_auth = {} + + # Backward compatibility fallbacks: only inject api_key if the auth dict is empty/not configured + if self.engineer_key and not self.engineer_auth: + self.engineer_auth["api_key"] = self.engineer_key + if self.architect_key and not self.architect_auth: + self.architect_auth["api_key"] = self.architect_key + + # Strategic Reasoning Engine (Architect) availability + is_architect_keyless = "vertex" in self.architect_model.lower() or "ollama" in self.architect_model.lower() or "local" in self.architect_model.lower() + self.has_architect = bool(self.architect_key or self.architect_auth or is_architect_keyless) + # Custom Trusted Commands Regexes custom_trusted = aiconfig.get("trusted_commands", []) if isinstance(custom_trusted, str): @@ -697,12 +716,12 @@ class ai: # Session Management self.sessions_dir = os.path.join(self.config.defaultdir, "ai_sessions") os.makedirs(self.sessions_dir, exist_ok=True) - self.session_id = None - self.session_path = None + self.session_id = getattr(self.config, "session_id", None) + self.session_path = os.path.join(self.sessions_dir, f"{self.session_id}.json") if self.session_id else None # Prompts base agnósticos architect_instructions = "" - if self.architect_key: + if self.has_architect: architect_instructions = """ CRITICAL - CONSULT vs ESCALATE: - ALWAYS use 'consult_architect' for: Configuration planning, design decisions, complex troubleshooting. @@ -718,7 +737,7 @@ class ai: else: architect_instructions = """ CRITICAL - ARCHITECT UNAVAILABLE: - - The Strategic Reasoning Engine (Architect) is currently UNAVAILABLE because its API key is not configured. + - The Strategic Reasoning Engine (Architect) is currently UNAVAILABLE because its API key or authentication is not configured. - DO NOT attempt to consult or escalate to the architect. - If the user asks to consult the architect, inform them that the Architect is offline and offer to help them directly to the best of your abilities. """ @@ -824,15 +843,19 @@ class ai: if status_formatter: self.tool_status_formatters[name] = status_formatter - def _stream_completion(self, model, messages, tools, api_key, status=None, label="", debug=False, chunk_callback=None, **kwargs): + def _stream_completion(self, model, messages, tools, api_key=None, status=None, label="", debug=False, chunk_callback=None, auth=None, **kwargs): """Stream a completion call, rendering styled Markdown in real-time. Returns (response, streamed) where: - response: reconstructed ModelResponse (same as non-streaming) - streamed: True if text was rendered to console during streaming """ + auth_dict = auth if auth is not None else {} + if api_key and "api_key" not in auth_dict: + auth_dict = auth_dict.copy() + auth_dict["api_key"] = api_key - stream_resp = completion(model=model, messages=messages, tools=tools, api_key=api_key, stream=True, **kwargs) + stream_resp = completion(model=model, messages=messages, tools=tools, stream=True, **auth_dict, **kwargs) chunks = [] full_content = "" @@ -1275,7 +1298,7 @@ class ai: try: safe_messages = self._sanitize_messages(messages) - response = completion(model=self.engineer_model, messages=safe_messages, tools=tools, api_key=self.engineer_key) + response = completion(model=self.engineer_model, messages=safe_messages, tools=tools, **self.engineer_auth) except Exception as e: if status: status.stop() raise ValueError(f"Engineer failed to connect: {str(e)}") @@ -1409,16 +1432,27 @@ class ai: continue return sorted(sessions, key=lambda x: x["created_at"], reverse=True) - def list_sessions(self): + def list_sessions(self, limit=20): """Prints a list of sessions using printer.table.""" sessions = self._get_sessions() if not sessions: printer.info("No saved AI sessions found.") return + total = len(sessions) + if limit and total > limit: + sessions = sessions[:limit] + columns = ["ID", "Title", "Created At", "Model"] rows = [[s["id"], s["title"], s["created_at"], s["model"]] for s in sessions] - printer.table("AI Persisted Sessions", columns, rows) + + title = "AI Persisted Sessions" + if limit and total > limit: + title += f" (Showing last {limit} of {total})" + + printer.table(title, columns, rows) + if limit and total > limit: + printer.info(f"Use '--list --all' (if supported) or check the sessions directory to see all {total} sessions.") def load_session_data(self, session_id): """Loads a session's raw data by ID.""" @@ -1449,8 +1483,10 @@ class ai: return sessions[0]["id"] if sessions else None def _generate_session_id(self, query): - """Generates a unique session ID based on timestamp.""" - return datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + """Generates a unique session ID based on timestamp and a random suffix.""" + ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + suffix = secrets.token_hex(2) + return f"{ts}-{suffix}" def save_session(self, history, title=None, model=None): """Saves current history to the session file.""" @@ -1459,6 +1495,8 @@ class ai: first_user_msg = next((m["content"] for m in history if m["role"] == "user"), "new-session") self.session_id = self._generate_session_id(first_user_msg) self.session_path = os.path.join(self.sessions_dir, f"{self.session_id}.json") + elif not self.session_path: + self.session_path = os.path.join(self.sessions_dir, f"{self.session_id}.json") # If it's a new file, we might want to set a better title if not os.path.exists(self.session_path) and not title: @@ -1496,16 +1534,22 @@ class ai: @MethodHook def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=False, stream=True, session_id=None, chunk_callback=None): - if not self.engineer_key: - raise ValueError("Engineer API key not configured. Use 'connpy config --engineer-api-key <key>' to set it.") + is_engineer_keyless = "vertex" in self.engineer_model.lower() or "ollama" in self.engineer_model.lower() or "local" in self.engineer_model.lower() + if not self.engineer_key and not self.engineer_auth and not is_engineer_keyless: + raise ValueError("Engineer API key or authentication not configured. Use 'connpy config --engineer-auth <auth>' to set it.") if chat_history is None: chat_history = [] # Load session if provided and history is empty - if session_id and not chat_history: - session_data = self.load_session_data(session_id) - if session_data: - chat_history = session_data.get("history", []) + if session_id: + # Force the session_id even if it doesn't exist yet + self.session_id = session_id + self.session_path = os.path.join(self.sessions_dir, f"{session_id}.json") + + if not chat_history: + session_data = self.load_session_data(session_id) + if session_data: + chat_history = session_data.get("history", []) # If we loaded history, the caller might need it back # But typically ask() is called in a loop with an external history object @@ -1541,6 +1585,7 @@ class ai: tools = self._get_architect_tools() if current_brain == "architect" else self._get_engineer_tools() model = self.architect_model if current_brain == "architect" else self.engineer_model key = self.architect_key if current_brain == "architect" else self.engineer_key + current_auth = self.architect_auth if current_brain == "architect" else self.engineer_auth # Estructura optimizada para Prompt Caching (Solo para Anthropic directo, Vertex tiene reglas distintas) if "claude" in model.lower() and "vertex" not in model.lower(): @@ -1590,8 +1635,8 @@ class ai: label = "[architect][bold]Architect[/bold][/architect]" if current_brain == "architect" else "[engineer][bold]Engineer[/bold][/engineer]" if status: - # Notify responder identity ONLY for web/remote clients (StatusBridge has is_web) - if getattr(status, "is_web", False): + # Notify responder identity for web/remote clients + if getattr(status, "is_web", False) or getattr(status, "is_remote", False): status.update(f"__RESPONDER__:{current_brain}") status.update(f"{label} is thinking... (step {iteration})") @@ -1600,12 +1645,12 @@ class ai: safe_messages = self._sanitize_messages(messages) if stream: response, streamed_response = self._stream_completion( - model=model, messages=safe_messages, tools=tools, api_key=key, + model=model, messages=safe_messages, tools=tools, auth=current_auth, status=status, label=label, debug=debug, num_retries=3, chunk_callback=chunk_callback ) else: - response = completion(model=model, messages=safe_messages, tools=tools, api_key=key, num_retries=3) + response = completion(model=model, messages=safe_messages, tools=tools, num_retries=3, **current_auth) except Exception as e: if current_brain == "architect": if status: status.update("[unavailable]Architect unavailable! Falling back to Engineer...") @@ -1614,6 +1659,7 @@ class ai: model = self.engineer_model tools = self._get_engineer_tools() key = self.engineer_key + current_auth = self.engineer_auth # Rebuild messages with Engineer system prompt and original user request messages = [{"role": "system", "content": self.engineer_system_prompt}] # Add chat history if exists (excluding system prompt) @@ -1706,6 +1752,7 @@ class ai: model = self.architect_model tools = self._get_architect_tools() key = self.architect_key + current_auth = self.architect_auth messages[0] = {"role": "system", "content": self.architect_system_prompt} # Prepare handover context to inject AFTER all tool responses handover_msg = f"HANDOVER FROM EXECUTION ENGINE\n\nReason: {args['reason']}\n\nContext: {args['context']}\n\nYou are now in control of this conversation." @@ -1727,6 +1774,7 @@ class ai: model = self.engineer_model tools = self._get_engineer_tools() key = self.engineer_key + current_auth = self.engineer_auth messages[0] = {"role": "system", "content": self.engineer_system_prompt} # Prepare handover context to inject AFTER all tool responses handover_msg = f"HANDOVER FROM ARCHITECT\n\nSummary: {args['summary']}\n\nYou are now back in control. Continue handling the user's requests." @@ -1768,7 +1816,7 @@ class ai: messages.append({"role": "user", "content": "Hard iteration limit reached. Please provide a summary of your findings so far."}) try: safe_messages = self._sanitize_messages(messages) - response = completion(model=model, messages=safe_messages, tools=[], api_key=key) + response = completion(model=model, messages=safe_messages, tools=[], **current_auth) resp_msg = response.choices[0].message messages.append(resp_msg.model_dump(exclude_none=True)) except Exception as e: @@ -1788,7 +1836,7 @@ class ai: try: safe_messages = self._sanitize_messages(summary_messages) # Use tools=None to force a text summary during interruption - response = completion(model=model, messages=safe_messages, tools=None, api_key=key) + response = completion(model=model, messages=safe_messages, tools=None, **current_auth) resp_msg = response.choices[0].message messages.append(resp_msg.model_dump(exclude_none=True)) @@ -1925,6 +1973,7 @@ Node: {node_name}""" # Use models based on persona current_model = self.architect_model if persona == "architect" else self.engineer_model current_key = self.architect_key if persona == "architect" else self.engineer_key + current_auth = self.architect_auth if persona == "architect" else self.engineer_auth try: while iteration < max_iterations: @@ -1934,8 +1983,8 @@ Node: {node_name}""" model=current_model, messages=messages, tools=mcp_tools if mcp_tools else None, - api_key=current_key, - stream=True + stream=True, + **current_auth ) full_content = "" @@ -2008,8 +2057,8 @@ Node: {node_name}""" model=self.engineer_model, messages=messages, tools=None, - api_key=self.engineer_key, - stream=True + stream=True, + **self.engineer_auth ) full_content = "" @@ -2095,7 +2144,7 @@ Node: {node_name}"""
    var SAFE_COMMANDS
    -
    +

    The type of the None singleton.

    Instance variables

    @@ -2255,6 +2304,7 @@ Node: {node_name}""" # Use models based on persona current_model = self.architect_model if persona == "architect" else self.engineer_model current_key = self.architect_key if persona == "architect" else self.engineer_key + current_auth = self.architect_auth if persona == "architect" else self.engineer_auth try: while iteration < max_iterations: @@ -2264,8 +2314,8 @@ Node: {node_name}""" model=current_model, messages=messages, tools=mcp_tools if mcp_tools else None, - api_key=current_key, - stream=True + stream=True, + **current_auth ) full_content = "" @@ -2338,8 +2388,8 @@ Node: {node_name}""" model=self.engineer_model, messages=messages, tools=None, - api_key=self.engineer_key, - stream=True + stream=True, + **self.engineer_auth ) full_content = "" @@ -2429,16 +2479,22 @@ Node: {node_name}"""
    @MethodHook
     def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=False, stream=True, session_id=None, chunk_callback=None):
    -    if not self.engineer_key:
    -        raise ValueError("Engineer API key not configured. Use 'connpy config --engineer-api-key <key>' to set it.")
    +    is_engineer_keyless = "vertex" in self.engineer_model.lower() or "ollama" in self.engineer_model.lower() or "local" in self.engineer_model.lower()
    +    if not self.engineer_key and not self.engineer_auth and not is_engineer_keyless:
    +        raise ValueError("Engineer API key or authentication not configured. Use 'connpy config --engineer-auth <auth>' to set it.")
             
         if chat_history is None: chat_history = []
         
         # Load session if provided and history is empty
    -    if session_id and not chat_history:
    -        session_data = self.load_session_data(session_id)
    -        if session_data:
    -            chat_history = session_data.get("history", [])
    +    if session_id:
    +        # Force the session_id even if it doesn't exist yet
    +        self.session_id = session_id
    +        self.session_path = os.path.join(self.sessions_dir, f"{session_id}.json")
    +        
    +        if not chat_history:
    +            session_data = self.load_session_data(session_id)
    +            if session_data:
    +                chat_history = session_data.get("history", [])
                 # If we loaded history, the caller might need it back
                 # But typically ask() is called in a loop with an external history object
     
    @@ -2474,6 +2530,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
         tools = self._get_architect_tools() if current_brain == "architect" else self._get_engineer_tools()
         model = self.architect_model if current_brain == "architect" else self.engineer_model
         key = self.architect_key if current_brain == "architect" else self.engineer_key
    +    current_auth = self.architect_auth if current_brain == "architect" else self.engineer_auth
     
         # Estructura optimizada para Prompt Caching (Solo para Anthropic directo, Vertex tiene reglas distintas)
         if "claude" in model.lower() and "vertex" not in model.lower():
    @@ -2523,8 +2580,8 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
                 
                 label = "[architect][bold]Architect[/bold][/architect]" if current_brain == "architect" else "[engineer][bold]Engineer[/bold][/engineer]"
                 if status: 
    -                # Notify responder identity ONLY for web/remote clients (StatusBridge has is_web)
    -                if getattr(status, "is_web", False):
    +                # Notify responder identity for web/remote clients
    +                if getattr(status, "is_web", False) or getattr(status, "is_remote", False):
                         status.update(f"__RESPONDER__:{current_brain}")
                     status.update(f"{label} is thinking... (step {iteration})")
                 
    @@ -2533,12 +2590,12 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
                     safe_messages = self._sanitize_messages(messages)
                     if stream:
                         response, streamed_response = self._stream_completion(
    -                        model=model, messages=safe_messages, tools=tools, api_key=key,
    +                        model=model, messages=safe_messages, tools=tools, auth=current_auth,
                             status=status, label=label, debug=debug, num_retries=3,
                             chunk_callback=chunk_callback
                         )
                     else:
    -                    response = completion(model=model, messages=safe_messages, tools=tools, api_key=key, num_retries=3)
    +                    response = completion(model=model, messages=safe_messages, tools=tools, num_retries=3, **current_auth)
                 except Exception as e:
                     if current_brain == "architect":
                         if status: status.update("[unavailable]Architect unavailable! Falling back to Engineer...")
    @@ -2547,6 +2604,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
                         model = self.engineer_model
                         tools = self._get_engineer_tools()
                         key = self.engineer_key
    +                    current_auth = self.engineer_auth
                         # Rebuild messages with Engineer system prompt and original user request
                         messages = [{"role": "system", "content": self.engineer_system_prompt}]
                         # Add chat history if exists (excluding system prompt)
    @@ -2639,6 +2697,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
                         model = self.architect_model
                         tools = self._get_architect_tools()
                         key = self.architect_key
    +                    current_auth = self.architect_auth
                         messages[0] = {"role": "system", "content": self.architect_system_prompt}
                         # Prepare handover context to inject AFTER all tool responses
                         handover_msg = f"HANDOVER FROM EXECUTION ENGINE\n\nReason: {args['reason']}\n\nContext: {args['context']}\n\nYou are now in control of this conversation."
    @@ -2660,6 +2719,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
                         model = self.engineer_model
                         tools = self._get_engineer_tools()
                         key = self.engineer_key
    +                    current_auth = self.engineer_auth
                         messages[0] = {"role": "system", "content": self.engineer_system_prompt}
                         # Prepare handover context to inject AFTER all tool responses
                         handover_msg = f"HANDOVER FROM ARCHITECT\n\nSummary: {args['summary']}\n\nYou are now back in control. Continue handling the user's requests."
    @@ -2701,7 +2761,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
                     messages.append({"role": "user", "content": "Hard iteration limit reached. Please provide a summary of your findings so far."})
                     try:
                         safe_messages = self._sanitize_messages(messages)
    -                    response = completion(model=model, messages=safe_messages, tools=[], api_key=key)
    +                    response = completion(model=model, messages=safe_messages, tools=[], **current_auth)
                         resp_msg = response.choices[0].message
                         messages.append(resp_msg.model_dump(exclude_none=True))
                     except Exception as e:
    @@ -2721,7 +2781,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
             try:
                 safe_messages = self._sanitize_messages(summary_messages)
                 # Use tools=None to force a text summary during interruption
    -            response = completion(model=model, messages=safe_messages, tools=None, api_key=key)
    +            response = completion(model=model, messages=safe_messages, tools=None, **current_auth)
                 resp_msg = response.choices[0].message
                 messages.append(resp_msg.model_dump(exclude_none=True))
                 
    @@ -2844,23 +2904,34 @@ def confirm(self, user_input): return True

    List nodes matching the filter pattern. Returns metadata for <=5 nodes, names only for more.

    -def list_sessions(self) +def list_sessions(self, limit=20)
    Expand source code -
    def list_sessions(self):
    +
    def list_sessions(self, limit=20):
         """Prints a list of sessions using printer.table."""
         sessions = self._get_sessions()
         if not sessions:
             printer.info("No saved AI sessions found.")
             return
         
    +    total = len(sessions)
    +    if limit and total > limit:
    +        sessions = sessions[:limit]
    +        
         columns = ["ID", "Title", "Created At", "Model"]
         rows = [[s["id"], s["title"], s["created_at"], s["model"]] for s in sessions]
    -    printer.table("AI Persisted Sessions", columns, rows)
    + + title = "AI Persisted Sessions" + if limit and total > limit: + title += f" (Showing last {limit} of {total})" + + printer.table(title, columns, rows) + if limit and total > limit: + printer.info(f"Use '--list --all' (if supported) or check the sessions directory to see all {total} sessions.")

    Prints a list of sessions using printer.table.

    @@ -3070,6 +3141,8 @@ def confirm(self, user_input): return True first_user_msg = next((m["content"] for m in history if m["role"] == "user"), "new-session") self.session_id = self._generate_session_id(first_user_msg) self.session_path = os.path.join(self.sessions_dir, f"{self.session_id}.json") + elif not self.session_path: + self.session_path = os.path.join(self.sessions_dir, f"{self.session_id}.json") # If it's a new file, we might want to set a better title if not os.path.exists(self.session_path) and not title: @@ -4226,8 +4299,11 @@ class node: def _setup_interact_environment(self, debug=False, logger=None, async_mode=False): - size = re.search('columns=([0-9]+).*lines=([0-9]+)',str(os.get_terminal_size())) - self.child.setwinsize(int(size.group(2)),int(size.group(1))) + try: + size = re.search('columns=([0-9]+).*lines=([0-9]+)',str(os.get_terminal_size())) + self.child.setwinsize(int(size.group(2)),int(size.group(1))) + except OSError: + pass if logger: port_str = f":{self.port}" if self.port and self.protocol not in ["ssm", "kubectl", "docker"] else "" logger("success", f"Connected to {self.unique} at {self.host}{port_str} via: {self.protocol}") @@ -4264,6 +4340,7 @@ class node: async def _async_interact_loop(self, local_stream, resize_callback, copilot_handler=None): local_stream.setup(resize_callback=resize_callback) + self.current_local_stream = local_stream try: child_fd = self.child.child_fd @@ -4346,11 +4423,19 @@ class node: # Remove any stray \x00 bytes and forward normally clean_data = data.replace(b'\x00', b'') if clean_data: - # Track command boundaries when user hits Enter - if hasattr(self, 'mylog') and (b'\r' in clean_data or b'\n' in clean_data): - self.cmd_byte_positions.append((self.mylog.tell(), None)) + # Track command boundaries when user hits Enter or presses Ctrl+C + if hasattr(self, 'mylog') and (b'\r' in clean_data or b'\n' in clean_data or b'\x03' in clean_data): + pos = self.mylog.tell() + marker_cmd = "CANCELLED" if b'\x03' in clean_data else None + self.cmd_byte_positions.append((pos, marker_cmd)) + if hasattr(self, 'current_local_stream') and self.current_local_stream is not None: + try: + await self.current_local_stream.write(f'\x1b]133;B;{pos}\x07'.encode()) + except Exception: + pass - try: os.write(child_fd, clean_data) + try: + os.write(child_fd, clean_data) except OSError: break self.lastinput = time() @@ -4470,6 +4555,7 @@ class node: except Exception: pass finally: + self.current_local_stream = None local_stream.teardown() @MethodHook @@ -4498,6 +4584,11 @@ class node: if cmd != slc and hasattr(self, 'cmd_byte_positions') and self.cmd_byte_positions is not None: log_pos = self.mylog.tell() if hasattr(self, 'mylog') else 0 self.cmd_byte_positions.append((log_pos, cmd)) + if hasattr(self, 'current_local_stream') and self.current_local_stream is not None: + try: + await self.current_local_stream.write(f'\x1b]133;B;{log_pos}\x07'.encode()) + except Exception: + pass # Write physically to PTY os.write(child_fd, (cmd + "\n").encode()) @@ -5137,6 +5228,11 @@ async def inject_commands(self, commands, child_fd, on_inject=None): if cmd != slc and hasattr(self, 'cmd_byte_positions') and self.cmd_byte_positions is not None: log_pos = self.mylog.tell() if hasattr(self, 'mylog') else 0 self.cmd_byte_positions.append((log_pos, cmd)) + if hasattr(self, 'current_local_stream') and self.current_local_stream is not None: + try: + await self.current_local_stream.write(f'\x1b]133;B;{log_pos}\x07'.encode()) + except Exception: + pass # Write physically to PTY os.write(child_fd, (cmd + "\n").encode()) @@ -6225,7 +6321,6 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
  • connpy.mcp_client
  • connpy.proto
  • connpy.services
  • -
  • connpy.tests
  • connpy.tunnels
  • connpy.utils
  • @@ -6289,7 +6384,7 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None, diff --git a/docs/connpy/mcp_client.html b/docs/connpy/mcp_client.html index d2d6fd2..6a50e47 100644 --- a/docs/connpy/mcp_client.html +++ b/docs/connpy/mcp_client.html @@ -3,7 +3,7 @@ - + connpy.mcp_client API documentation @@ -343,7 +343,7 @@ el.replaceWith(d); diff --git a/docs/connpy/proto/index.html b/docs/connpy/proto/index.html index 573e196..0fc7ddf 100644 --- a/docs/connpy/proto/index.html +++ b/docs/connpy/proto/index.html @@ -3,7 +3,7 @@ - + connpy.proto API documentation @@ -60,7 +60,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/ai_service.html b/docs/connpy/services/ai_service.html index 4f0169c..f308f92 100644 --- a/docs/connpy/services/ai_service.html +++ b/docs/connpy/services/ai_service.html @@ -3,7 +3,7 @@ - + connpy.services.ai_service API documentation @@ -58,6 +58,37 @@ el.replaceWith(d);
    class AIService(BaseService):
         """Business logic for interacting with AI agents and LLM configurations."""
     
    +    def _clean_cisco_scrolling(self, text: str) -> str:
    +        """Resolves horizontal scrolling artifacts (backspaces, \r, ANSI) by merging overlapping segments."""
    +        def merge_overlapping(s1, s2):
    +            s2_clean = s2.lstrip(' $')
    +            max_overlap = min(len(s1), len(s2_clean))
    +            for i in range(max_overlap, 0, -1):
    +                if s1[-i:] == s2_clean[:i]:
    +                    return s1 + s2_clean[i:]
    +            return s1 + s2_clean
    +
    +        scroll_re = re.compile(r'(\x08{5,}\s*\$?|\$\r|\x1b\[\d+[GD]\s*\$?)')
    +        parts = scroll_re.split(text)
    +        merged = ""
    +        
    +        for part in parts:
    +            if scroll_re.match(part):
    +                continue
    +                
    +            cleaned = log_cleaner(part)
    +            if not merged:
    +                merged = cleaned
    +            else:
    +                merged_lines = merged.split('\n')
    +                cleaned_lines = cleaned.split('\n')
    +                
    +                merged_lines[-1] = merge_overlapping(merged_lines[-1], cleaned_lines[0])
    +                merged_lines.extend(cleaned_lines[1:])
    +                merged = "\n".join(merged_lines)
    +                
    +        return merged
    +
         def build_context_blocks(self, raw_bytes: bytes, cmd_byte_positions: list, node_info: dict, last_line: str = "") -> list:
             """Identifies command blocks in the terminal history."""
             blocks = []
    @@ -79,28 +110,69 @@ el.replaceWith(d);
                     prev_pos = cmd_byte_positions[i-1][0]
                     
                     if known_cmd:
    -                    prev_chunk = raw_bytes[prev_pos:pos]
    -                    prev_cleaned = log_cleaner(prev_chunk.decode(errors='replace'))
    -                    prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    -                    prompt_text = prev_lines[-1].strip() if prev_lines else ""
    -                    preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    -                    parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    +                    if known_cmd == "CANCELLED":
    +                        parsed_positions.append({"pos": pos, "type": "CANCELLED", "preview": ""})
    +                    else:
    +                        prev_chunk = raw_bytes[prev_pos:pos]
    +                        prev_cleaned = self._clean_cisco_scrolling(prev_chunk.decode(errors='replace'))
    +                        prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    +                        prompt_text = prev_lines[-1].strip() if prev_lines else ""
    +                        preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    +                        
    +                        if len(preview) > 80:
    +                            preview = preview[:77] + "..."
    +                        parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview})
                     else:
                         chunk = raw_bytes[prev_pos:pos]
    -                    cleaned = log_cleaner(chunk.decode(errors='replace'))
    -                    lines = [l for l in cleaned.split('\n') if l.strip()]
    -                    preview = lines[-1].strip() if lines else ""
                         
    -                    if preview:
    -                        match = prompt_re.search(preview)
    -                        if match:
    -                            cmd_text = preview[match.end():].strip()
    -                            if cmd_text:
    -                                parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    -                            else:
    -                                parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    -                        else:
    -                            parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
    +                    cleaned = self._clean_cisco_scrolling(chunk.decode(errors='replace'))
    +                    lines = [l for l in cleaned.split('\n') if l.strip()]
    +                    
    +                    found_in_pass1 = False
    +                    if lines:
    +                        # Search backwards through the last few lines for the prompt
    +                        for idx in range(len(lines) - 1, max(-1, len(lines) - 10), -1):
    +                            match = prompt_re.search(lines[idx])
    +                            if match:
    +                                ptxt = match.group(0).strip()
    +                                cmd_first_line = lines[idx][match.end():].strip()
    +                                cmd_rest = [l.strip() for l in lines[idx+1:]]
    +                                cmd_text = " ".join([cmd_first_line] + cmd_rest).strip()
    +                                
    +                                if cmd_text:
    +                                    pv = f"{ptxt} {cmd_text}".strip()
    +                                    if len(pv) > 80:
    +                                        pv = pv[:77] + "..."
    +                                    parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                                else:
    +                                    parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    +                                found_in_pass1 = True
    +                                break
    +                        
    +                        if not found_in_pass1:
    +                            # Fallback: The prompt might have been isolated in the previous chunk 
    +                            # due to asynchronous network delays splitting the output exactly at the newline.
    +                            prev_was_valid_cmd = i >= 2 and parsed_positions[i-2]["type"] == "VALID_CMD"
    +                            if prev_pos > 0 and not prev_was_valid_cmd:
    +                                # Fetch the very last chunk that we just processed
    +                                prev_prev_pos = cmd_byte_positions[i-2][0] if i >= 2 else 0
    +                                prev_chunk_text = self._clean_cisco_scrolling(raw_bytes[prev_prev_pos:prev_pos].decode(errors='replace'))
    +                                prev_lines_text = [l for l in prev_chunk_text.split('\n') if l.strip()]
    +                                
    +                                if prev_lines_text:
    +                                    prev_match = prompt_re.search(prev_lines_text[-1])
    +                                    if prev_match:
    +                                        ptxt = prev_match.group(0).strip()
    +                                        cmd_text = " ".join([l.strip() for l in lines]).strip()
    +                                        if cmd_text:
    +                                            pv = f"{ptxt} {cmd_text}".strip()
    +                                            if len(pv) > 80:
    +                                                pv = pv[:77] + "..."
    +                                            parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                                            found_in_pass1 = True
    +                            
    +                            if not found_in_pass1:
    +                                parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
                         else:
                             parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
     
    @@ -113,11 +185,11 @@ el.replaceWith(d);
                     start_pos = item["pos"]
                     preview = item["preview"]
                     
    -                # Find the end position: next VALID_CMD or EMPTY_PROMPT
    +                # Find the end position: next VALID_CMD or EMPTY_PROMPT or CANCELLED
                     end_pos = current_prompt_pos
                     for j in range(i + 1, len(parsed_positions)):
                         next_item = parsed_positions[j]
    -                    if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT"):
    +                    if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT", "CANCELLED"):
                             end_pos = next_item["pos"]
                             break
                     
    @@ -219,11 +291,14 @@ el.replaceWith(d);
             return await asyncio.wrap_future(future)
     
     
    -    def list_sessions(self):
    -        """Return a list of all saved AI sessions."""
    +    def list_sessions(self, limit=None):
    +        """Return a list of saved AI sessions, optionally limited."""
             from connpy.ai import ai
             agent = ai(self.config)
    -        return agent._get_sessions()
    +        sessions = agent._get_sessions()
    +        if limit and len(sessions) > limit:
    +            return sessions[:limit], len(sessions)
    +        return sessions, len(sessions)
     
         def delete_session(self, session_id):
             """Delete an AI session by ID."""
    @@ -235,13 +310,15 @@ el.replaceWith(d);
             else:
                 raise InvalidConfigurationError(f"Session '{session_id}' not found.")
     
    -    def configure_provider(self, provider, model=None, api_key=None):
    +    def configure_provider(self, provider, model=None, api_key=None, auth=None):
             """Update AI provider settings in the configuration."""
             settings = self.config.config.get("ai", {})
             if model:
                 settings[f"{provider}_model"] = model
             if api_key:
                 settings[f"{provider}_api_key"] = api_key
    +        if auth is not None:
    +            settings[f"{provider}_auth"] = auth
                 
             self.config.config["ai"] = settings
             self.config._saveconfig(self.config.file)
    @@ -280,6 +357,11 @@ el.replaceWith(d);
             self.config.config["ai"] = ai_settings
             self.config._saveconfig(self.config.file)
     
    +    def list_mcp_servers(self) -> dict:
    +        """Get the configured MCP servers."""
    +        ai_settings = self.config.config.get("ai", {})
    +        return ai_settings.get("mcp_servers", {})
    +
         def load_session_data(self, session_id):
             """Load a session's raw data by ID."""
             from connpy.ai import ai
    @@ -379,28 +461,69 @@ el.replaceWith(d);
                 prev_pos = cmd_byte_positions[i-1][0]
                 
                 if known_cmd:
    -                prev_chunk = raw_bytes[prev_pos:pos]
    -                prev_cleaned = log_cleaner(prev_chunk.decode(errors='replace'))
    -                prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    -                prompt_text = prev_lines[-1].strip() if prev_lines else ""
    -                preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    -                parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    +                if known_cmd == "CANCELLED":
    +                    parsed_positions.append({"pos": pos, "type": "CANCELLED", "preview": ""})
    +                else:
    +                    prev_chunk = raw_bytes[prev_pos:pos]
    +                    prev_cleaned = self._clean_cisco_scrolling(prev_chunk.decode(errors='replace'))
    +                    prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    +                    prompt_text = prev_lines[-1].strip() if prev_lines else ""
    +                    preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    +                    
    +                    if len(preview) > 80:
    +                        preview = preview[:77] + "..."
    +                    parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview})
                 else:
                     chunk = raw_bytes[prev_pos:pos]
    -                cleaned = log_cleaner(chunk.decode(errors='replace'))
    -                lines = [l for l in cleaned.split('\n') if l.strip()]
    -                preview = lines[-1].strip() if lines else ""
                     
    -                if preview:
    -                    match = prompt_re.search(preview)
    -                    if match:
    -                        cmd_text = preview[match.end():].strip()
    -                        if cmd_text:
    -                            parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    -                        else:
    -                            parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    -                    else:
    -                        parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
    +                cleaned = self._clean_cisco_scrolling(chunk.decode(errors='replace'))
    +                lines = [l for l in cleaned.split('\n') if l.strip()]
    +                
    +                found_in_pass1 = False
    +                if lines:
    +                    # Search backwards through the last few lines for the prompt
    +                    for idx in range(len(lines) - 1, max(-1, len(lines) - 10), -1):
    +                        match = prompt_re.search(lines[idx])
    +                        if match:
    +                            ptxt = match.group(0).strip()
    +                            cmd_first_line = lines[idx][match.end():].strip()
    +                            cmd_rest = [l.strip() for l in lines[idx+1:]]
    +                            cmd_text = " ".join([cmd_first_line] + cmd_rest).strip()
    +                            
    +                            if cmd_text:
    +                                pv = f"{ptxt} {cmd_text}".strip()
    +                                if len(pv) > 80:
    +                                    pv = pv[:77] + "..."
    +                                parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                            else:
    +                                parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    +                            found_in_pass1 = True
    +                            break
    +                    
    +                    if not found_in_pass1:
    +                        # Fallback: The prompt might have been isolated in the previous chunk 
    +                        # due to asynchronous network delays splitting the output exactly at the newline.
    +                        prev_was_valid_cmd = i >= 2 and parsed_positions[i-2]["type"] == "VALID_CMD"
    +                        if prev_pos > 0 and not prev_was_valid_cmd:
    +                            # Fetch the very last chunk that we just processed
    +                            prev_prev_pos = cmd_byte_positions[i-2][0] if i >= 2 else 0
    +                            prev_chunk_text = self._clean_cisco_scrolling(raw_bytes[prev_prev_pos:prev_pos].decode(errors='replace'))
    +                            prev_lines_text = [l for l in prev_chunk_text.split('\n') if l.strip()]
    +                            
    +                            if prev_lines_text:
    +                                prev_match = prompt_re.search(prev_lines_text[-1])
    +                                if prev_match:
    +                                    ptxt = prev_match.group(0).strip()
    +                                    cmd_text = " ".join([l.strip() for l in lines]).strip()
    +                                    if cmd_text:
    +                                        pv = f"{ptxt} {cmd_text}".strip()
    +                                        if len(pv) > 80:
    +                                            pv = pv[:77] + "..."
    +                                        parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                                        found_in_pass1 = True
    +                        
    +                        if not found_in_pass1:
    +                            parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
                     else:
                         parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
     
    @@ -413,11 +536,11 @@ el.replaceWith(d);
                 start_pos = item["pos"]
                 preview = item["preview"]
                 
    -            # Find the end position: next VALID_CMD or EMPTY_PROMPT
    +            # Find the end position: next VALID_CMD or EMPTY_PROMPT or CANCELLED
                 end_pos = current_prompt_pos
                 for j in range(i + 1, len(parsed_positions)):
                     next_item = parsed_positions[j]
    -                if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT"):
    +                if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT", "CANCELLED"):
                         end_pos = next_item["pos"]
                         break
                 
    @@ -478,20 +601,22 @@ el.replaceWith(d);
     

    Update MCP server settings in the configuration with smart merging.

    -def configure_provider(self, provider, model=None, api_key=None) +def configure_provider(self, provider, model=None, api_key=None, auth=None)
    Expand source code -
    def configure_provider(self, provider, model=None, api_key=None):
    +
    def configure_provider(self, provider, model=None, api_key=None, auth=None):
         """Update AI provider settings in the configuration."""
         settings = self.config.config.get("ai", {})
         if model:
             settings[f"{provider}_model"] = model
         if api_key:
             settings[f"{provider}_api_key"] = api_key
    +    if auth is not None:
    +        settings[f"{provider}_auth"] = auth
             
         self.config.config["ai"] = settings
         self.config._saveconfig(self.config.file)
    @@ -534,21 +659,39 @@ el.replaceWith(d);

    Delete an AI session by ID.

    -
    -def list_sessions(self) +
    +def list_mcp_servers(self) ‑> dict
    Expand source code -
    def list_sessions(self):
    -    """Return a list of all saved AI sessions."""
    +
    def list_mcp_servers(self) -> dict:
    +    """Get the configured MCP servers."""
    +    ai_settings = self.config.config.get("ai", {})
    +    return ai_settings.get("mcp_servers", {})
    +
    +

    Get the configured MCP servers.

    +
    +
    +def list_sessions(self, limit=None) +
    +
    +
    + +Expand source code + +
    def list_sessions(self, limit=None):
    +    """Return a list of saved AI sessions, optionally limited."""
         from connpy.ai import ai
         agent = ai(self.config)
    -    return agent._get_sessions()
    + sessions = agent._get_sessions() + if limit and len(sessions) > limit: + return sessions[:limit], len(sessions) + return sessions, len(sessions)
    -

    Return a list of all saved AI sessions.

    +

    Return a list of saved AI sessions, optionally limited.

    def load_session_data(self, session_id) @@ -671,6 +814,7 @@ el.replaceWith(d);
  • configure_provider
  • confirm
  • delete_session
  • +
  • list_mcp_servers
  • list_sessions
  • load_session_data
  • process_copilot_input
  • @@ -682,7 +826,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/base.html b/docs/connpy/services/base.html index 2ff6902..e72b7ab 100644 --- a/docs/connpy/services/base.html +++ b/docs/connpy/services/base.html @@ -3,7 +3,7 @@ - + connpy.services.base API documentation @@ -152,7 +152,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/config_service.html b/docs/connpy/services/config_service.html index 4156c44..ad87411 100644 --- a/docs/connpy/services/config_service.html +++ b/docs/connpy/services/config_service.html @@ -3,7 +3,7 @@ - + connpy.services.config_service API documentation @@ -319,7 +319,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/context_service.html b/docs/connpy/services/context_service.html index 2161ebb..0a772f7 100644 --- a/docs/connpy/services/context_service.html +++ b/docs/connpy/services/context_service.html @@ -3,7 +3,7 @@ - + connpy.services.context_service API documentation @@ -370,7 +370,7 @@ def current_context(self) -> str: diff --git a/docs/connpy/services/exceptions.html b/docs/connpy/services/exceptions.html index 164cec5..459d464 100644 --- a/docs/connpy/services/exceptions.html +++ b/docs/connpy/services/exceptions.html @@ -3,7 +3,7 @@ - + connpy.services.exceptions API documentation @@ -268,7 +268,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/execution_service.html b/docs/connpy/services/execution_service.html index a29e4c6..26eaa4d 100644 --- a/docs/connpy/services/execution_service.html +++ b/docs/connpy/services/execution_service.html @@ -3,7 +3,7 @@ - + connpy.services.execution_service API documentation @@ -449,7 +449,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/import_export_service.html b/docs/connpy/services/import_export_service.html index 4c98e1b..58e0736 100644 --- a/docs/connpy/services/import_export_service.html +++ b/docs/connpy/services/import_export_service.html @@ -3,7 +3,7 @@ - + connpy.services.import_export_service API documentation @@ -361,7 +361,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/index.html b/docs/connpy/services/index.html index 2e52a24..46d7ee0 100644 --- a/docs/connpy/services/index.html +++ b/docs/connpy/services/index.html @@ -3,7 +3,7 @@ - + connpy.services API documentation @@ -113,6 +113,37 @@ el.replaceWith(d);
    class AIService(BaseService):
         """Business logic for interacting with AI agents and LLM configurations."""
     
    +    def _clean_cisco_scrolling(self, text: str) -> str:
    +        """Resolves horizontal scrolling artifacts (backspaces, \r, ANSI) by merging overlapping segments."""
    +        def merge_overlapping(s1, s2):
    +            s2_clean = s2.lstrip(' $')
    +            max_overlap = min(len(s1), len(s2_clean))
    +            for i in range(max_overlap, 0, -1):
    +                if s1[-i:] == s2_clean[:i]:
    +                    return s1 + s2_clean[i:]
    +            return s1 + s2_clean
    +
    +        scroll_re = re.compile(r'(\x08{5,}\s*\$?|\$\r|\x1b\[\d+[GD]\s*\$?)')
    +        parts = scroll_re.split(text)
    +        merged = ""
    +        
    +        for part in parts:
    +            if scroll_re.match(part):
    +                continue
    +                
    +            cleaned = log_cleaner(part)
    +            if not merged:
    +                merged = cleaned
    +            else:
    +                merged_lines = merged.split('\n')
    +                cleaned_lines = cleaned.split('\n')
    +                
    +                merged_lines[-1] = merge_overlapping(merged_lines[-1], cleaned_lines[0])
    +                merged_lines.extend(cleaned_lines[1:])
    +                merged = "\n".join(merged_lines)
    +                
    +        return merged
    +
         def build_context_blocks(self, raw_bytes: bytes, cmd_byte_positions: list, node_info: dict, last_line: str = "") -> list:
             """Identifies command blocks in the terminal history."""
             blocks = []
    @@ -134,28 +165,69 @@ el.replaceWith(d);
                     prev_pos = cmd_byte_positions[i-1][0]
                     
                     if known_cmd:
    -                    prev_chunk = raw_bytes[prev_pos:pos]
    -                    prev_cleaned = log_cleaner(prev_chunk.decode(errors='replace'))
    -                    prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    -                    prompt_text = prev_lines[-1].strip() if prev_lines else ""
    -                    preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    -                    parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    +                    if known_cmd == "CANCELLED":
    +                        parsed_positions.append({"pos": pos, "type": "CANCELLED", "preview": ""})
    +                    else:
    +                        prev_chunk = raw_bytes[prev_pos:pos]
    +                        prev_cleaned = self._clean_cisco_scrolling(prev_chunk.decode(errors='replace'))
    +                        prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    +                        prompt_text = prev_lines[-1].strip() if prev_lines else ""
    +                        preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    +                        
    +                        if len(preview) > 80:
    +                            preview = preview[:77] + "..."
    +                        parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview})
                     else:
                         chunk = raw_bytes[prev_pos:pos]
    -                    cleaned = log_cleaner(chunk.decode(errors='replace'))
    -                    lines = [l for l in cleaned.split('\n') if l.strip()]
    -                    preview = lines[-1].strip() if lines else ""
                         
    -                    if preview:
    -                        match = prompt_re.search(preview)
    -                        if match:
    -                            cmd_text = preview[match.end():].strip()
    -                            if cmd_text:
    -                                parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    -                            else:
    -                                parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    -                        else:
    -                            parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
    +                    cleaned = self._clean_cisco_scrolling(chunk.decode(errors='replace'))
    +                    lines = [l for l in cleaned.split('\n') if l.strip()]
    +                    
    +                    found_in_pass1 = False
    +                    if lines:
    +                        # Search backwards through the last few lines for the prompt
    +                        for idx in range(len(lines) - 1, max(-1, len(lines) - 10), -1):
    +                            match = prompt_re.search(lines[idx])
    +                            if match:
    +                                ptxt = match.group(0).strip()
    +                                cmd_first_line = lines[idx][match.end():].strip()
    +                                cmd_rest = [l.strip() for l in lines[idx+1:]]
    +                                cmd_text = " ".join([cmd_first_line] + cmd_rest).strip()
    +                                
    +                                if cmd_text:
    +                                    pv = f"{ptxt} {cmd_text}".strip()
    +                                    if len(pv) > 80:
    +                                        pv = pv[:77] + "..."
    +                                    parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                                else:
    +                                    parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    +                                found_in_pass1 = True
    +                                break
    +                        
    +                        if not found_in_pass1:
    +                            # Fallback: The prompt might have been isolated in the previous chunk 
    +                            # due to asynchronous network delays splitting the output exactly at the newline.
    +                            prev_was_valid_cmd = i >= 2 and parsed_positions[i-2]["type"] == "VALID_CMD"
    +                            if prev_pos > 0 and not prev_was_valid_cmd:
    +                                # Fetch the very last chunk that we just processed
    +                                prev_prev_pos = cmd_byte_positions[i-2][0] if i >= 2 else 0
    +                                prev_chunk_text = self._clean_cisco_scrolling(raw_bytes[prev_prev_pos:prev_pos].decode(errors='replace'))
    +                                prev_lines_text = [l for l in prev_chunk_text.split('\n') if l.strip()]
    +                                
    +                                if prev_lines_text:
    +                                    prev_match = prompt_re.search(prev_lines_text[-1])
    +                                    if prev_match:
    +                                        ptxt = prev_match.group(0).strip()
    +                                        cmd_text = " ".join([l.strip() for l in lines]).strip()
    +                                        if cmd_text:
    +                                            pv = f"{ptxt} {cmd_text}".strip()
    +                                            if len(pv) > 80:
    +                                                pv = pv[:77] + "..."
    +                                            parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                                            found_in_pass1 = True
    +                            
    +                            if not found_in_pass1:
    +                                parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
                         else:
                             parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
     
    @@ -168,11 +240,11 @@ el.replaceWith(d);
                     start_pos = item["pos"]
                     preview = item["preview"]
                     
    -                # Find the end position: next VALID_CMD or EMPTY_PROMPT
    +                # Find the end position: next VALID_CMD or EMPTY_PROMPT or CANCELLED
                     end_pos = current_prompt_pos
                     for j in range(i + 1, len(parsed_positions)):
                         next_item = parsed_positions[j]
    -                    if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT"):
    +                    if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT", "CANCELLED"):
                             end_pos = next_item["pos"]
                             break
                     
    @@ -274,11 +346,14 @@ el.replaceWith(d);
             return await asyncio.wrap_future(future)
     
     
    -    def list_sessions(self):
    -        """Return a list of all saved AI sessions."""
    +    def list_sessions(self, limit=None):
    +        """Return a list of saved AI sessions, optionally limited."""
             from connpy.ai import ai
             agent = ai(self.config)
    -        return agent._get_sessions()
    +        sessions = agent._get_sessions()
    +        if limit and len(sessions) > limit:
    +            return sessions[:limit], len(sessions)
    +        return sessions, len(sessions)
     
         def delete_session(self, session_id):
             """Delete an AI session by ID."""
    @@ -290,13 +365,15 @@ el.replaceWith(d);
             else:
                 raise InvalidConfigurationError(f"Session '{session_id}' not found.")
     
    -    def configure_provider(self, provider, model=None, api_key=None):
    +    def configure_provider(self, provider, model=None, api_key=None, auth=None):
             """Update AI provider settings in the configuration."""
             settings = self.config.config.get("ai", {})
             if model:
                 settings[f"{provider}_model"] = model
             if api_key:
                 settings[f"{provider}_api_key"] = api_key
    +        if auth is not None:
    +            settings[f"{provider}_auth"] = auth
                 
             self.config.config["ai"] = settings
             self.config._saveconfig(self.config.file)
    @@ -335,6 +412,11 @@ el.replaceWith(d);
             self.config.config["ai"] = ai_settings
             self.config._saveconfig(self.config.file)
     
    +    def list_mcp_servers(self) -> dict:
    +        """Get the configured MCP servers."""
    +        ai_settings = self.config.config.get("ai", {})
    +        return ai_settings.get("mcp_servers", {})
    +
         def load_session_data(self, session_id):
             """Load a session's raw data by ID."""
             from connpy.ai import ai
    @@ -434,28 +516,69 @@ el.replaceWith(d);
                 prev_pos = cmd_byte_positions[i-1][0]
                 
                 if known_cmd:
    -                prev_chunk = raw_bytes[prev_pos:pos]
    -                prev_cleaned = log_cleaner(prev_chunk.decode(errors='replace'))
    -                prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    -                prompt_text = prev_lines[-1].strip() if prev_lines else ""
    -                preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    -                parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    +                if known_cmd == "CANCELLED":
    +                    parsed_positions.append({"pos": pos, "type": "CANCELLED", "preview": ""})
    +                else:
    +                    prev_chunk = raw_bytes[prev_pos:pos]
    +                    prev_cleaned = self._clean_cisco_scrolling(prev_chunk.decode(errors='replace'))
    +                    prev_lines = [l for l in prev_cleaned.split('\n') if l.strip()]
    +                    prompt_text = prev_lines[-1].strip() if prev_lines else ""
    +                    preview = f"{prompt_text}{known_cmd}" if prompt_text else known_cmd
    +                    
    +                    if len(preview) > 80:
    +                        preview = preview[:77] + "..."
    +                    parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview})
                 else:
                     chunk = raw_bytes[prev_pos:pos]
    -                cleaned = log_cleaner(chunk.decode(errors='replace'))
    -                lines = [l for l in cleaned.split('\n') if l.strip()]
    -                preview = lines[-1].strip() if lines else ""
                     
    -                if preview:
    -                    match = prompt_re.search(preview)
    -                    if match:
    -                        cmd_text = preview[match.end():].strip()
    -                        if cmd_text:
    -                            parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": preview[:80]})
    -                        else:
    -                            parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    -                    else:
    -                        parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
    +                cleaned = self._clean_cisco_scrolling(chunk.decode(errors='replace'))
    +                lines = [l for l in cleaned.split('\n') if l.strip()]
    +                
    +                found_in_pass1 = False
    +                if lines:
    +                    # Search backwards through the last few lines for the prompt
    +                    for idx in range(len(lines) - 1, max(-1, len(lines) - 10), -1):
    +                        match = prompt_re.search(lines[idx])
    +                        if match:
    +                            ptxt = match.group(0).strip()
    +                            cmd_first_line = lines[idx][match.end():].strip()
    +                            cmd_rest = [l.strip() for l in lines[idx+1:]]
    +                            cmd_text = " ".join([cmd_first_line] + cmd_rest).strip()
    +                            
    +                            if cmd_text:
    +                                pv = f"{ptxt} {cmd_text}".strip()
    +                                if len(pv) > 80:
    +                                    pv = pv[:77] + "..."
    +                                parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                            else:
    +                                parsed_positions.append({"pos": pos, "type": "EMPTY_PROMPT", "preview": ""})
    +                            found_in_pass1 = True
    +                            break
    +                    
    +                    if not found_in_pass1:
    +                        # Fallback: The prompt might have been isolated in the previous chunk 
    +                        # due to asynchronous network delays splitting the output exactly at the newline.
    +                        prev_was_valid_cmd = i >= 2 and parsed_positions[i-2]["type"] == "VALID_CMD"
    +                        if prev_pos > 0 and not prev_was_valid_cmd:
    +                            # Fetch the very last chunk that we just processed
    +                            prev_prev_pos = cmd_byte_positions[i-2][0] if i >= 2 else 0
    +                            prev_chunk_text = self._clean_cisco_scrolling(raw_bytes[prev_prev_pos:prev_pos].decode(errors='replace'))
    +                            prev_lines_text = [l for l in prev_chunk_text.split('\n') if l.strip()]
    +                            
    +                            if prev_lines_text:
    +                                prev_match = prompt_re.search(prev_lines_text[-1])
    +                                if prev_match:
    +                                    ptxt = prev_match.group(0).strip()
    +                                    cmd_text = " ".join([l.strip() for l in lines]).strip()
    +                                    if cmd_text:
    +                                        pv = f"{ptxt} {cmd_text}".strip()
    +                                        if len(pv) > 80:
    +                                            pv = pv[:77] + "..."
    +                                        parsed_positions.append({"pos": pos, "type": "VALID_CMD", "preview": pv})
    +                                        found_in_pass1 = True
    +                        
    +                        if not found_in_pass1:
    +                            parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
                     else:
                         parsed_positions.append({"pos": pos, "type": "SCROLLING", "preview": ""})
     
    @@ -468,11 +591,11 @@ el.replaceWith(d);
                 start_pos = item["pos"]
                 preview = item["preview"]
                 
    -            # Find the end position: next VALID_CMD or EMPTY_PROMPT
    +            # Find the end position: next VALID_CMD or EMPTY_PROMPT or CANCELLED
                 end_pos = current_prompt_pos
                 for j in range(i + 1, len(parsed_positions)):
                     next_item = parsed_positions[j]
    -                if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT"):
    +                if next_item["type"] in ("VALID_CMD", "EMPTY_PROMPT", "CANCELLED"):
                         end_pos = next_item["pos"]
                         break
                 
    @@ -533,20 +656,22 @@ el.replaceWith(d);
     

    Update MCP server settings in the configuration with smart merging.

    -def configure_provider(self, provider, model=None, api_key=None) +def configure_provider(self, provider, model=None, api_key=None, auth=None)
    Expand source code -
    def configure_provider(self, provider, model=None, api_key=None):
    +
    def configure_provider(self, provider, model=None, api_key=None, auth=None):
         """Update AI provider settings in the configuration."""
         settings = self.config.config.get("ai", {})
         if model:
             settings[f"{provider}_model"] = model
         if api_key:
             settings[f"{provider}_api_key"] = api_key
    +    if auth is not None:
    +        settings[f"{provider}_auth"] = auth
             
         self.config.config["ai"] = settings
         self.config._saveconfig(self.config.file)
    @@ -589,21 +714,39 @@ el.replaceWith(d);

    Delete an AI session by ID.

    -
    -def list_sessions(self) +
    +def list_mcp_servers(self) ‑> dict
    Expand source code -
    def list_sessions(self):
    -    """Return a list of all saved AI sessions."""
    +
    def list_mcp_servers(self) -> dict:
    +    """Get the configured MCP servers."""
    +    ai_settings = self.config.config.get("ai", {})
    +    return ai_settings.get("mcp_servers", {})
    +
    +

    Get the configured MCP servers.

    +
    +
    +def list_sessions(self, limit=None) +
    +
    +
    + +Expand source code + +
    def list_sessions(self, limit=None):
    +    """Return a list of saved AI sessions, optionally limited."""
         from connpy.ai import ai
         agent = ai(self.config)
    -    return agent._get_sessions()
    + sessions = agent._get_sessions() + if limit and len(sessions) > limit: + return sessions[:limit], len(sessions) + return sessions, len(sessions)
    -

    Return a list of all saved AI sessions.

    +

    Return a list of saved AI sessions, optionally limited.

    def load_session_data(self, session_id) @@ -3726,6 +3869,7 @@ el.replaceWith(d);
  • configure_provider
  • confirm
  • delete_session
  • +
  • list_mcp_servers
  • list_sessions
  • load_session_data
  • process_copilot_input
  • @@ -3840,7 +3984,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/node_service.html b/docs/connpy/services/node_service.html index 1700265..0a66d09 100644 --- a/docs/connpy/services/node_service.html +++ b/docs/connpy/services/node_service.html @@ -3,7 +3,7 @@ - + connpy.services.node_service API documentation @@ -786,7 +786,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/plugin_service.html b/docs/connpy/services/plugin_service.html index c99a7f7..e2d6e17 100644 --- a/docs/connpy/services/plugin_service.html +++ b/docs/connpy/services/plugin_service.html @@ -3,7 +3,7 @@ - + connpy.services.plugin_service API documentation @@ -709,7 +709,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/profile_service.html b/docs/connpy/services/profile_service.html index e3f746c..568aec0 100644 --- a/docs/connpy/services/profile_service.html +++ b/docs/connpy/services/profile_service.html @@ -3,7 +3,7 @@ - + connpy.services.profile_service API documentation @@ -429,7 +429,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/provider.html b/docs/connpy/services/provider.html index aac535c..fa72924 100644 --- a/docs/connpy/services/provider.html +++ b/docs/connpy/services/provider.html @@ -3,7 +3,7 @@ - + connpy.services.provider API documentation @@ -164,7 +164,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/sync_service.html b/docs/connpy/services/sync_service.html index aac676f..602c65a 100644 --- a/docs/connpy/services/sync_service.html +++ b/docs/connpy/services/sync_service.html @@ -3,7 +3,7 @@ - + connpy.services.sync_service API documentation @@ -964,7 +964,7 @@ el.replaceWith(d); diff --git a/docs/connpy/services/system_service.html b/docs/connpy/services/system_service.html index 95f059e..ded62e1 100644 --- a/docs/connpy/services/system_service.html +++ b/docs/connpy/services/system_service.html @@ -3,7 +3,7 @@ - + connpy.services.system_service API documentation @@ -325,7 +325,7 @@ el.replaceWith(d); diff --git a/docs/connpy/tunnels.html b/docs/connpy/tunnels.html index 8c1df81..48fdef9 100644 --- a/docs/connpy/tunnels.html +++ b/docs/connpy/tunnels.html @@ -3,7 +3,7 @@ - + connpy.tunnels API documentation @@ -545,7 +545,7 @@ Bridges the blocking gRPC iterators with the async _async_interact_loop.

    diff --git a/docs/connpy/utils.html b/docs/connpy/utils.html index 5097c45..4584ba1 100644 --- a/docs/connpy/utils.html +++ b/docs/connpy/utils.html @@ -3,7 +3,7 @@ - + connpy.utils API documentation @@ -59,11 +59,14 @@ el.replaceWith(d); if not data: return "" + # Remove OSC (Operating System Command) sequences (e.g., set window title \x1b]0;...\x07) + data = re.sub(r'\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)', '', data) + lines = data.split('\n') cleaned_lines = [] # Regex to capture: ANSI sequences, control characters (\r, \b, etc), and plain text chunks - token_re = re.compile(r'(\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/ ]*[@-~])|\r|\b|\x7f|[\x00-\x1F]|[^\x1B\r\b\x7f\x00-\x1F]+)') + token_re = re.compile(r'(\x1B(?:[\x30-\x5A\x5C-\x7E]|\[[0-?]*[ -/ ]*[@-~])|\r|\b|\x7f|[\x00-\x1F]|[^\x1B\r\b\x7f\x00-\x1F]+)') for line in lines: buffer = [] @@ -144,7 +147,7 @@ el.replaceWith(d);