From a08d84139d57bbcb255e6d6a3f589ccae026612d Mon Sep 17 00:00:00 2001 From: Fede Luzzi Date: Fri, 8 May 2026 21:53:57 -0300 Subject: [PATCH] remoto con fixes de todo --- connpy/_version.py | 2 +- connpy/ai.py | 146 +++++++++++++++++ connpy/core.py | 167 +++++++++++++++---- connpy/grpc_layer/connpy_pb2.py | 152 +++++++++--------- connpy/grpc_layer/connpy_pb2_grpc.py | 2 +- connpy/grpc_layer/server.py | 37 ++++- connpy/grpc_layer/stubs.py | 232 +++++++++++++++++++-------- connpy/proto/connpy.proto | 1 + connpy/services/ai_service.py | 6 + 9 files changed, 565 insertions(+), 180 deletions(-) diff --git a/connpy/_version.py b/connpy/_version.py index 5501b3c..2718f39 100644 --- a/connpy/_version.py +++ b/connpy/_version.py @@ -1 +1 @@ -__version__ = "6.0.0b6" +__version__ = "6.0.0b7" diff --git a/connpy/ai.py b/connpy/ai.py index d986ac7..e85b57e 100755 --- a/connpy/ai.py +++ b/connpy/ai.py @@ -1351,5 +1351,151 @@ Node: {node_name}""" "error": str(e) } + @MethodHook + async def aask_copilot(self, terminal_buffer, user_question, node_info=None, chunk_callback=None): + import json + import re + from litellm import acompletion + import asyncio + import warnings + + # Suppress unawaited coroutine warnings from LiteLLM's internal streaming logic during sudden cancellation + warnings.filterwarnings("ignore", message="coroutine '.*async_streaming.*' was never awaited", category=RuntimeWarning) + + node_info = node_info or {} + os_info = node_info.get("os", "unknown") + node_name = node_info.get("name", "unknown") + + vendor_reference = "" + if os_info and os_info != "unknown": + try: + os_filename = os_info.lower().replace(" ", "_") + ref_path = os.path.join(self.config.defaultdir, "ai_references", f"{os_filename}.md") + if os.path.exists(ref_path): + with open(ref_path, "r") as f: + vendor_reference = f.read().strip() + except Exception: + pass + + system_prompt = f"""Role: TERMINAL COPILOT. You assist a network engineer during a live SSH session. +Rules: +1. Answer the user's question directly based on the Terminal Context. +2. If the user asks you to analyze, parse, or extract data from the Terminal Context, DO IT directly in the section (you can use markdown tables or lists). Do NOT just give them a command to do it themselves. +3. If the user wants to execute an action, provide the required CLI commands inside a block, one command per line. If no commands are needed, leave it empty or omit the block. +4. ULTRA-CONCISE. Keep your guide to the point. +5. You MUST output your response in the following strict format: + +Your brief tactical guide in markdown. 3-4 sentences max. + + +command 1 +command 2 + + +low, high, or destructive + +6. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops. + +Terminal Context: +{terminal_buffer} + +Device OS: {os_info} +Node: {node_name}""" + + if vendor_reference: + system_prompt += f"\n\nVendor Command Reference:\n{vendor_reference}" + + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_question} + ] + + try: + response = await acompletion( + model=self.engineer_model, + messages=messages, + api_key=self.engineer_key, + stream=True + ) + + full_content = "" + streamed_guide = "" + + async for chunk in response: + delta = chunk.choices[0].delta + if hasattr(delta, 'content') and delta.content: + full_content += delta.content + + if chunk_callback: + start_idx = full_content.find("") + if start_idx != -1: + after_start = full_content[start_idx + 7:] + end_idx = after_start.find("") + + if end_idx != -1: + current_guide = after_start[:end_idx] + else: + current_guide = after_start + if current_guide.endswith("<"): current_guide = current_guide[:-1] + elif current_guide.endswith("(.*?)", full_content, re.DOTALL) + if guide_match: + guide = guide_match.group(1).strip() + + cmd_match = re.search(r"(.*?)", full_content, re.DOTALL) + if cmd_match: + cmds_raw = cmd_match.group(1).strip() + if cmds_raw: + commands = [c.strip() for c in cmds_raw.split('\n') if c.strip()] + + risk_match = re.search(r"(.*?)", full_content, re.DOTALL) + if risk_match: + risk_level = risk_match.group(1).strip().lower() + + if not guide and full_content and not ("" in full_content): + guide = full_content.strip() + + return { + "commands": commands, + "guide": guide, + "risk_level": risk_level, + "error": None + } + + except asyncio.CancelledError: + # Client cancelled the request via gRPC or local interrupt + if 'response' in locals(): + try: + if hasattr(response, 'aclose'): + # Fire and forget the close to avoid blocking the cancel + asyncio.create_task(response.aclose()) + elif hasattr(response, 'close'): + response.close() + except Exception: + pass + return None + except Exception as e: + return { + "commands": [], + "guide": "", + "risk_level": "low", + "error": str(e) + } + @MethodHook def confirm(self, user_input): return True diff --git a/connpy/core.py b/connpy/core.py index 7a7d850..037ba7f 100755 --- a/connpy/core.py +++ b/connpy/core.py @@ -18,7 +18,31 @@ import asyncio import fcntl from . import printer from .tunnels import LocalStream +from contextlib import contextmanager +@contextmanager +def copilot_terminal_mode(): + import sys, tty, termios + fd = sys.stdin.fileno() + try: + old_settings = termios.tcgetattr(fd) + + # Primero pasamos a raw mode absoluto para matar ISIG, ICANON, ECHO, etc. + tty.setraw(fd) + + # Luego rehabilitamos OPOST para que rich.Live se dibuje correctamente + new_settings = termios.tcgetattr(fd) + new_settings[1] = new_settings[1] | termios.OPOST + termios.tcsetattr(fd, termios.TCSANOW, new_settings) + + yield + except Exception: + yield + finally: + try: + termios.tcsetattr(fd, termios.TCSANOW, old_settings) + except Exception: + pass #functions and classes @ClassHook @@ -393,7 +417,8 @@ class node: child_reader_queue = asyncio.Queue() # Track command byte positions for copilot context navigation - cmd_byte_positions = [0] + # Each entry is (byte_position, command_text_or_None) + cmd_byte_positions = [(0, None)] def _child_read_ready(): try: @@ -440,7 +465,7 @@ class node: 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): - cmd_byte_positions.append(self.mylog.tell()) + cmd_byte_positions.append((self.mylog.tell(), None)) try: os.write(child_fd, clean_data) @@ -613,19 +638,16 @@ class node: import asyncio import os import sys + import fcntl + + flags = 0 + stdin_fd = sys.stdin.fileno() try: # Disable LocalStream reader so it doesn't steal keystrokes from Prompt loop = asyncio.get_running_loop() loop.remove_reader(sys.stdin.fileno()) - # Override SIGINT so asyncio doesn't kill the event loop when we press Ctrl+C - import signal - orig_sigint = signal.getsignal(signal.SIGINT) - def custom_sigint(sig, frame): - raise KeyboardInterrupt() - signal.signal(signal.SIGINT, custom_sigint) - # 1. Salir de raw mode para poder usar input() y rich stdin_fd = sys.stdin.fileno() @@ -635,6 +657,9 @@ class node: import copy new_settings = copy.deepcopy(original_settings) new_settings[3] = new_settings[3] & ~termios.ECHOCTL + # CRITICAL: Prevent OS from translating Ctrl+C into SIGINT + # This prevents the asyncio event loop from crashing when user hits Ctrl+C + new_settings[3] = new_settings[3] & ~termios.ISIG termios.tcsetattr(stdin_fd, termios.TCSADRAIN, new_settings) # Remove O_NONBLOCK from stdin so Prompt.ask() works @@ -688,18 +713,32 @@ class node: prompt_re = re.compile(re.sub(r'(?\u25b6 {preview} [Tab: {mode_label}]") - session = PromptSession(history=copilot_history) - question = await session.prompt_async( - get_prompt_text, - key_bindings=bindings, - bottom_toolbar=get_toolbar - ) + import threading + def preload_ai_deps(): + try: + import litellm + except Exception: + pass + threading.Thread(target=preload_ai_deps, daemon=True).start() + + try: + session = PromptSession(history=copilot_history) + question = await session.prompt_async( + get_prompt_text, + key_bindings=bindings, + bottom_toolbar=get_toolbar + ) + except KeyboardInterrupt: + question = "" if cancelled[0] or not question.strip() or question.strip() == "CANCEL": console.print("\n[dim]Copilot cancelled.[/dim]") @@ -832,8 +882,48 @@ class node: except Exception: live.update(Panel(Markdown(live_text), title="[bold cyan]Copilot Guide[/bold cyan]", border_style="cyan")) - with Live(panel, console=console, refresh_per_second=10) as live: - result = await asyncio.to_thread(service.ask_copilot, active_buffer, enriched_question, node_info, chunk_callback=on_chunk) + with copilot_terminal_mode(), Live(panel, console=console, refresh_per_second=10) as live: + # Launch the AI call as a task + ai_task = asyncio.create_task(service.aask_copilot(active_buffer, enriched_question, node_info, chunk_callback=on_chunk)) + + # Make stdin non-blocking + import fcntl + flags = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL) + fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK) + + cancelled = False + result = None + + try: + while not ai_task.done(): + try: + key = os.read(sys.stdin.fileno(), 1024) + if b'\x03' in key: + cancelled = True + ai_task.cancel() + console.print("\n[dim]Copilot cancelled via Ctrl+C.[/dim]") + break + except OSError: + pass + # Yield to event loop to allow AI task to progress + await asyncio.sleep(0.05) + + if not cancelled: + result = ai_task.result() + except asyncio.CancelledError: + cancelled = True + console.print("\n[dim]Copilot cancelled.[/dim]") + except KeyboardInterrupt: + cancelled = True + ai_task.cancel() + console.print("\n[dim]Copilot cancelled via Ctrl+C.[/dim]") + finally: + # Restore stdin flags + fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flags) + + if cancelled or not result: + os.write(child_fd, b'\x15\r') + return if result.get("error"): console.print(f"[red]Error: {result['error']}[/red]") @@ -868,10 +958,13 @@ class node: event.app.exit(result='n') pt_color = "ansi" + risk_style - action = await confirm_session.prompt_async( - HTML(f"<{pt_color}>Send commands? (y/n/e/number/range) [n]: "), - key_bindings=confirm_bindings - ) + try: + action = await confirm_session.prompt_async( + HTML(f"<{pt_color}>Send commands? (y/n/e/number/range) [n]: "), + key_bindings=confirm_bindings + ) + except KeyboardInterrupt: + action = "n" if not action.strip(): action = "n" @@ -883,6 +976,8 @@ class node: os.write(child_fd, b'\x15') # Ctrl+U to clear line await asyncio.sleep(0.1) for cmd in commands: + if cmd_byte_positions is not None and hasattr(self, 'mylog'): + cmd_byte_positions.append((self.mylog.tell(), cmd)) os.write(child_fd, (cmd + "\n").encode()) await asyncio.sleep(0.3) elif action_l.startswith('e'): @@ -910,6 +1005,8 @@ class node: await asyncio.sleep(0.1) for cmd in edited_cmd.split('\n'): if cmd.strip(): + if cmd_byte_positions is not None and hasattr(self, 'mylog'): + cmd_byte_positions.append((self.mylog.tell(), cmd.strip())) os.write(child_fd, (cmd.strip() + "\n").encode()) await asyncio.sleep(0.3) else: @@ -938,9 +1035,13 @@ class node: os.write(child_fd, b'\x15') # Ctrl+U to clear line await asyncio.sleep(0.1) if len(valid_indices) == 1: + if cmd_byte_positions is not None and hasattr(self, 'mylog'): + cmd_byte_positions.append((self.mylog.tell(), commands[valid_indices[0]])) os.write(child_fd, (commands[valid_indices[0]] + "\n").encode()) else: for idx in valid_indices: + if cmd_byte_positions is not None and hasattr(self, 'mylog'): + cmd_byte_positions.append((self.mylog.tell(), commands[idx])) os.write(child_fd, (commands[idx] + "\n").encode()) await asyncio.sleep(0.3) else: @@ -966,8 +1067,6 @@ class node: # 6. Restaurar raw mode, O_NONBLOCK y SIGINT tty.setraw(stdin_fd) fcntl.fcntl(stdin_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) - if 'orig_sigint' in locals(): - signal.signal(signal.SIGINT, orig_sigint) # Re-enable LocalStream reader try: diff --git a/connpy/grpc_layer/connpy_pb2.py b/connpy/grpc_layer/connpy_pb2.py index 82caffb..72bb6c0 100644 --- a/connpy/grpc_layer/connpy_pb2.py +++ b/connpy/grpc_layer/connpy_pb2.py @@ -26,7 +26,7 @@ from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x63onnpy.proto\x12\x06\x63onnpy\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1bgoogle/protobuf/empty.proto\"\xdc\x01\n\x0fInteractRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04sftp\x18\x02 \x01(\x08\x12\r\n\x05\x64\x65\x62ug\x18\x03 \x01(\x08\x12\x12\n\nstdin_data\x18\x04 \x01(\x0c\x12\x0c\n\x04\x63ols\x18\x05 \x01(\x05\x12\x0c\n\x04rows\x18\x06 \x01(\x05\x12\x1e\n\x16\x63onnection_params_json\x18\x07 \x01(\t\x12\x18\n\x10\x63opilot_question\x18\x08 \x01(\t\x12\x16\n\x0e\x63opilot_action\x18\t \x01(\t\x12\x1e\n\x16\x63opilot_context_buffer\x18\n \x01(\t\"\xe4\x01\n\x10InteractResponse\x12\x13\n\x0bstdout_data\x18\x01 \x01(\x0c\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x15\n\rerror_message\x18\x03 \x01(\t\x12\x16\n\x0e\x63opilot_prompt\x18\x04 \x01(\x08\x12\x1e\n\x16\x63opilot_buffer_preview\x18\x05 \x01(\t\x12\x1d\n\x15\x63opilot_response_json\x18\x06 \x01(\t\x12\x1e\n\x16\x63opilot_node_info_json\x18\x07 \x01(\t\x12\x1c\n\x14\x63opilot_stream_chunk\x18\x08 \x01(\t\"7\n\rFilterRequest\x12\x12\n\nfilter_str\x18\x01 \x01(\t\x12\x12\n\nformat_str\x18\x02 \x01(\t\"5\n\rValueResponse\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.google.protobuf.Value\"\x17\n\tIdRequest\x12\n\n\x02id\x18\x01 \x01(\t\"S\n\x0bNodeRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12%\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x11\n\tis_folder\x18\x03 \x01(\x08\".\n\rDeleteRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tis_folder\x18\x02 \x01(\x08\"\x1d\n\x0cMessageValue\x12\r\n\x05value\x18\x01 \x01(\t\";\n\x0bMoveRequest\x12\x0e\n\x06src_id\x18\x01 \x01(\t\x12\x0e\n\x06\x64st_id\x18\x02 \x01(\t\x12\x0c\n\x04\x63opy\x18\x03 \x01(\x08\"W\n\x0b\x42ulkRequest\x12\x0b\n\x03ids\x18\x01 \x03(\t\x12\r\n\x05hosts\x18\x02 \x03(\t\x12,\n\x0b\x63ommon_data\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"7\n\x0eStructResponse\x12%\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"/\n\x0eProfileRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07resolve\x18\x02 \x01(\x08\"6\n\rStructRequest\x12%\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x1e\n\rStringRequest\x12\r\n\x05value\x18\x01 \x01(\t\"\x1f\n\x0eStringResponse\x12\r\n\x05value\x18\x01 \x01(\t\"C\n\rUpdateRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value\"B\n\rPluginRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bsource_file\x18\x02 \x01(\t\x12\x0e\n\x06update\x18\x03 \x01(\x08\"\xa5\x01\n\nRunRequest\x12\r\n\x05nodes\x18\x01 \x03(\t\x12\x10\n\x08\x63ommands\x18\x02 \x03(\t\x12\x0e\n\x06\x66older\x18\x03 \x01(\t\x12\x0e\n\x06prompt\x18\x04 \x01(\t\x12\x10\n\x08parallel\x18\x05 \x01(\x05\x12%\n\x04vars\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07timeout\x18\x07 \x01(\x05\x12\x0c\n\x04name\x18\x08 \x01(\t\"\xb8\x01\n\x0bTestRequest\x12\r\n\x05nodes\x18\x01 \x03(\t\x12\x10\n\x08\x63ommands\x18\x02 \x03(\t\x12\x10\n\x08\x65xpected\x18\x03 \x03(\t\x12\x0e\n\x06\x66older\x18\x04 \x01(\t\x12\x0e\n\x06prompt\x18\x05 \x01(\t\x12\x10\n\x08parallel\x18\x06 \x01(\x05\x12%\n\x04vars\x18\x07 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07timeout\x18\x08 \x01(\x05\x12\x0c\n\x04name\x18\t \x01(\t\"A\n\rScriptRequest\x12\x0e\n\x06param1\x18\x01 \x01(\t\x12\x0e\n\x06param2\x18\x02 \x01(\t\x12\x10\n\x08parallel\x18\x03 \x01(\x05\"3\n\rExportRequest\x12\x11\n\tfile_path\x18\x01 \x01(\t\x12\x0f\n\x07\x66olders\x18\x02 \x03(\t\"\x1c\n\x0bListRequest\x12\r\n\x05items\x18\x01 \x03(\t\"\xa6\x02\n\nAskRequest\x12\x12\n\ninput_text\x18\x01 \x01(\t\x12\x0e\n\x06\x64ryrun\x18\x02 \x01(\x08\x12,\n\x0c\x63hat_history\x18\x03 \x01(\x0b\x32\x16.google.protobuf.Value\x12\x12\n\nsession_id\x18\x04 \x01(\t\x12\r\n\x05\x64\x65\x62ug\x18\x05 \x01(\x08\x12\x16\n\x0e\x65ngineer_model\x18\x06 \x01(\t\x12\x18\n\x10\x65ngineer_api_key\x18\x07 \x01(\t\x12\x17\n\x0f\x61rchitect_model\x18\x08 \x01(\t\x12\x19\n\x11\x61rchitect_api_key\x18\t \x01(\t\x12\r\n\x05trust\x18\n \x01(\x08\x12\x1b\n\x13\x63onfirmation_answer\x18\x0b \x01(\t\x12\x11\n\tinterrupt\x18\x0c \x01(\x08\"\xc8\x01\n\nAIResponse\x12\x12\n\ntext_chunk\x18\x01 \x01(\t\x12\x10\n\x08is_final\x18\x02 \x01(\x08\x12,\n\x0b\x66ull_result\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x15\n\rstatus_update\x18\x04 \x01(\t\x12\x15\n\rdebug_message\x18\x05 \x01(\t\x12\x1d\n\x15requires_confirmation\x18\x06 \x01(\x08\x12\x19\n\x11important_message\x18\x07 \x01(\t\"\x1d\n\x0c\x42oolResponse\x12\r\n\x05value\x18\x01 \x01(\x08\"C\n\x0fProviderRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\x12\x0f\n\x07\x61pi_key\x18\x03 \x01(\t\"\x1b\n\nIntRequest\x12\r\n\x05value\x18\x01 \x01(\x05\"p\n\rNodeRunResult\x12\x11\n\tunique_id\x18\x01 \x01(\t\x12\x0e\n\x06output\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\x05\x12,\n\x0btest_result\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"m\n\x12\x46ullReplaceRequest\x12,\n\x0b\x63onnections\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\x08profiles\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"X\n\x0e\x43opilotRequest\x12\x17\n\x0fterminal_buffer\x18\x01 \x01(\t\x12\x15\n\ruser_question\x18\x02 \x01(\t\x12\x16\n\x0enode_info_json\x18\x03 \x01(\t\"U\n\x0f\x43opilotResponse\x12\x10\n\x08\x63ommands\x18\x01 \x03(\t\x12\r\n\x05guide\x18\x02 \x01(\t\x12\x12\n\nrisk_level\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t2\xe1\x07\n\x0bNodeService\x12<\n\nlist_nodes\x12\x15.connpy.FilterRequest\x1a\x15.connpy.ValueResponse\"\x00\x12>\n\x0clist_folders\x12\x15.connpy.FilterRequest\x1a\x15.connpy.ValueResponse\"\x00\x12?\n\x10get_node_details\x12\x11.connpy.IdRequest\x1a\x16.connpy.StructResponse\"\x00\x12<\n\x0e\x65xplode_unique\x12\x11.connpy.IdRequest\x1a\x15.connpy.ValueResponse\"\x00\x12\x42\n\x0egenerate_cache\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12\x39\n\x08\x61\x64\x64_node\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x0bupdate_node\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12>\n\x0b\x64\x65lete_node\x12\x15.connpy.DeleteRequest\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\tmove_node\x12\x13.connpy.MoveRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x39\n\x08\x62ulk_add\x12\x13.connpy.BulkRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x45\n\x16validate_parent_folder\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x12set_reserved_names\x12\x13.connpy.ListRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n\rinteract_node\x12\x17.connpy.InteractRequest\x1a\x18.connpy.InteractResponse\"\x00(\x01\x30\x01\x12\x44\n\x0c\x66ull_replace\x12\x1a.connpy.FullReplaceRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x45\n\rget_inventory\x12\x16.google.protobuf.Empty\x1a\x1a.connpy.FullReplaceRequest\"\x00\x32\x96\x03\n\x0eProfileService\x12?\n\rlist_profiles\x12\x15.connpy.FilterRequest\x1a\x15.connpy.ValueResponse\"\x00\x12?\n\x0bget_profile\x12\x16.connpy.ProfileRequest\x1a\x16.connpy.StructResponse\"\x00\x12<\n\x0b\x61\x64\x64_profile\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x44\n\x11resolve_node_data\x12\x15.connpy.StructRequest\x1a\x16.connpy.StructResponse\"\x00\x12=\n\x0e\x64\x65lete_profile\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12?\n\x0eupdate_profile\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x32\xae\x03\n\rConfigService\x12@\n\x0cget_settings\x12\x16.google.protobuf.Empty\x1a\x16.connpy.StructResponse\"\x00\x12\x43\n\x0fget_default_dir\x12\x16.google.protobuf.Empty\x1a\x16.connpy.StringResponse\"\x00\x12\x44\n\x11set_config_folder\x12\x15.connpy.StringRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x41\n\x0eupdate_setting\x12\x15.connpy.UpdateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x10\x65ncrypt_password\x12\x15.connpy.StringRequest\x1a\x16.connpy.StringResponse\"\x00\x12H\n\x15\x61pply_theme_from_file\x12\x15.connpy.StringRequest\x1a\x16.connpy.StructResponse\"\x00\x32\xca\x02\n\rPluginService\x12?\n\x0clist_plugins\x12\x16.google.protobuf.Empty\x1a\x15.connpy.ValueResponse\"\x00\x12=\n\nadd_plugin\x12\x15.connpy.PluginRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\rdelete_plugin\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\renable_plugin\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12=\n\x0e\x64isable_plugin\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x32\x9b\x02\n\x10\x45xecutionService\x12=\n\x0crun_commands\x12\x12.connpy.RunRequest\x1a\x15.connpy.NodeRunResult\"\x00\x30\x01\x12?\n\rtest_commands\x12\x13.connpy.TestRequest\x1a\x15.connpy.NodeRunResult\"\x00\x30\x01\x12\x41\n\x0erun_cli_script\x12\x15.connpy.ScriptRequest\x1a\x16.connpy.StructResponse\"\x00\x12\x44\n\x11run_yaml_playbook\x12\x15.connpy.ScriptRequest\x1a\x16.connpy.StructResponse\"\x00\x32\xe2\x01\n\x13ImportExportService\x12\x41\n\x0e\x65xport_to_file\x12\x15.connpy.ExportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x10import_from_file\x12\x15.connpy.StringRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x12set_reserved_names\x12\x13.connpy.ListRequest\x1a\x16.google.protobuf.Empty\"\x00\x32\xd0\x03\n\tAIService\x12\x33\n\x03\x61sk\x12\x12.connpy.AskRequest\x1a\x12.connpy.AIResponse\"\x00(\x01\x30\x01\x12\x38\n\x07\x63onfirm\x12\x15.connpy.StringRequest\x1a\x14.connpy.BoolResponse\"\x00\x12@\n\x0b\x61sk_copilot\x12\x16.connpy.CopilotRequest\x1a\x17.connpy.CopilotResponse\"\x00\x12@\n\rlist_sessions\x12\x16.google.protobuf.Empty\x1a\x15.connpy.ValueResponse\"\x00\x12\x41\n\x0e\x64\x65lete_session\x12\x15.connpy.StringRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n\x12\x63onfigure_provider\x12\x17.connpy.ProviderRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x44\n\x11load_session_data\x12\x15.connpy.StringRequest\x1a\x16.connpy.StructResponse\"\x00\x32\xc2\x02\n\rSystemService\x12\x39\n\tstart_api\x12\x12.connpy.IntRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x39\n\tdebug_api\x12\x12.connpy.IntRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x08stop_api\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12;\n\x0brestart_api\x12\x12.connpy.IntRequest\x1a\x16.google.protobuf.Empty\"\x00\x12@\n\x0eget_api_status\x12\x16.google.protobuf.Empty\x1a\x14.connpy.BoolResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x63onnpy.proto\x12\x06\x63onnpy\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1bgoogle/protobuf/empty.proto\"\xdc\x01\n\x0fInteractRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04sftp\x18\x02 \x01(\x08\x12\r\n\x05\x64\x65\x62ug\x18\x03 \x01(\x08\x12\x12\n\nstdin_data\x18\x04 \x01(\x0c\x12\x0c\n\x04\x63ols\x18\x05 \x01(\x05\x12\x0c\n\x04rows\x18\x06 \x01(\x05\x12\x1e\n\x16\x63onnection_params_json\x18\x07 \x01(\t\x12\x18\n\x10\x63opilot_question\x18\x08 \x01(\t\x12\x16\n\x0e\x63opilot_action\x18\t \x01(\t\x12\x1e\n\x16\x63opilot_context_buffer\x18\n \x01(\t\"\x86\x02\n\x10InteractResponse\x12\x13\n\x0bstdout_data\x18\x01 \x01(\x0c\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x15\n\rerror_message\x18\x03 \x01(\t\x12\x16\n\x0e\x63opilot_prompt\x18\x04 \x01(\x08\x12\x1e\n\x16\x63opilot_buffer_preview\x18\x05 \x01(\t\x12\x1d\n\x15\x63opilot_response_json\x18\x06 \x01(\t\x12\x1e\n\x16\x63opilot_node_info_json\x18\x07 \x01(\t\x12\x1c\n\x14\x63opilot_stream_chunk\x18\x08 \x01(\t\x12 \n\x18\x63opilot_injected_command\x18\t \x01(\t\"7\n\rFilterRequest\x12\x12\n\nfilter_str\x18\x01 \x01(\t\x12\x12\n\nformat_str\x18\x02 \x01(\t\"5\n\rValueResponse\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.google.protobuf.Value\"\x17\n\tIdRequest\x12\n\n\x02id\x18\x01 \x01(\t\"S\n\x0bNodeRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12%\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x11\n\tis_folder\x18\x03 \x01(\x08\".\n\rDeleteRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tis_folder\x18\x02 \x01(\x08\"\x1d\n\x0cMessageValue\x12\r\n\x05value\x18\x01 \x01(\t\";\n\x0bMoveRequest\x12\x0e\n\x06src_id\x18\x01 \x01(\t\x12\x0e\n\x06\x64st_id\x18\x02 \x01(\t\x12\x0c\n\x04\x63opy\x18\x03 \x01(\x08\"W\n\x0b\x42ulkRequest\x12\x0b\n\x03ids\x18\x01 \x03(\t\x12\r\n\x05hosts\x18\x02 \x03(\t\x12,\n\x0b\x63ommon_data\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"7\n\x0eStructResponse\x12%\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"/\n\x0eProfileRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07resolve\x18\x02 \x01(\x08\"6\n\rStructRequest\x12%\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x1e\n\rStringRequest\x12\r\n\x05value\x18\x01 \x01(\t\"\x1f\n\x0eStringResponse\x12\r\n\x05value\x18\x01 \x01(\t\"C\n\rUpdateRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.Value\"B\n\rPluginRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0bsource_file\x18\x02 \x01(\t\x12\x0e\n\x06update\x18\x03 \x01(\x08\"\xa5\x01\n\nRunRequest\x12\r\n\x05nodes\x18\x01 \x03(\t\x12\x10\n\x08\x63ommands\x18\x02 \x03(\t\x12\x0e\n\x06\x66older\x18\x03 \x01(\t\x12\x0e\n\x06prompt\x18\x04 \x01(\t\x12\x10\n\x08parallel\x18\x05 \x01(\x05\x12%\n\x04vars\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07timeout\x18\x07 \x01(\x05\x12\x0c\n\x04name\x18\x08 \x01(\t\"\xb8\x01\n\x0bTestRequest\x12\r\n\x05nodes\x18\x01 \x03(\t\x12\x10\n\x08\x63ommands\x18\x02 \x03(\t\x12\x10\n\x08\x65xpected\x18\x03 \x03(\t\x12\x0e\n\x06\x66older\x18\x04 \x01(\t\x12\x0e\n\x06prompt\x18\x05 \x01(\t\x12\x10\n\x08parallel\x18\x06 \x01(\x05\x12%\n\x04vars\x18\x07 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07timeout\x18\x08 \x01(\x05\x12\x0c\n\x04name\x18\t \x01(\t\"A\n\rScriptRequest\x12\x0e\n\x06param1\x18\x01 \x01(\t\x12\x0e\n\x06param2\x18\x02 \x01(\t\x12\x10\n\x08parallel\x18\x03 \x01(\x05\"3\n\rExportRequest\x12\x11\n\tfile_path\x18\x01 \x01(\t\x12\x0f\n\x07\x66olders\x18\x02 \x03(\t\"\x1c\n\x0bListRequest\x12\r\n\x05items\x18\x01 \x03(\t\"\xa6\x02\n\nAskRequest\x12\x12\n\ninput_text\x18\x01 \x01(\t\x12\x0e\n\x06\x64ryrun\x18\x02 \x01(\x08\x12,\n\x0c\x63hat_history\x18\x03 \x01(\x0b\x32\x16.google.protobuf.Value\x12\x12\n\nsession_id\x18\x04 \x01(\t\x12\r\n\x05\x64\x65\x62ug\x18\x05 \x01(\x08\x12\x16\n\x0e\x65ngineer_model\x18\x06 \x01(\t\x12\x18\n\x10\x65ngineer_api_key\x18\x07 \x01(\t\x12\x17\n\x0f\x61rchitect_model\x18\x08 \x01(\t\x12\x19\n\x11\x61rchitect_api_key\x18\t \x01(\t\x12\r\n\x05trust\x18\n \x01(\x08\x12\x1b\n\x13\x63onfirmation_answer\x18\x0b \x01(\t\x12\x11\n\tinterrupt\x18\x0c \x01(\x08\"\xc8\x01\n\nAIResponse\x12\x12\n\ntext_chunk\x18\x01 \x01(\t\x12\x10\n\x08is_final\x18\x02 \x01(\x08\x12,\n\x0b\x66ull_result\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x15\n\rstatus_update\x18\x04 \x01(\t\x12\x15\n\rdebug_message\x18\x05 \x01(\t\x12\x1d\n\x15requires_confirmation\x18\x06 \x01(\x08\x12\x19\n\x11important_message\x18\x07 \x01(\t\"\x1d\n\x0c\x42oolResponse\x12\r\n\x05value\x18\x01 \x01(\x08\"C\n\x0fProviderRequest\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\x12\x0f\n\x07\x61pi_key\x18\x03 \x01(\t\"\x1b\n\nIntRequest\x12\r\n\x05value\x18\x01 \x01(\x05\"p\n\rNodeRunResult\x12\x11\n\tunique_id\x18\x01 \x01(\t\x12\x0e\n\x06output\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\x05\x12,\n\x0btest_result\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"m\n\x12\x46ullReplaceRequest\x12,\n\x0b\x63onnections\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\x08profiles\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"X\n\x0e\x43opilotRequest\x12\x17\n\x0fterminal_buffer\x18\x01 \x01(\t\x12\x15\n\ruser_question\x18\x02 \x01(\t\x12\x16\n\x0enode_info_json\x18\x03 \x01(\t\"U\n\x0f\x43opilotResponse\x12\x10\n\x08\x63ommands\x18\x01 \x03(\t\x12\r\n\x05guide\x18\x02 \x01(\t\x12\x12\n\nrisk_level\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t2\xe1\x07\n\x0bNodeService\x12<\n\nlist_nodes\x12\x15.connpy.FilterRequest\x1a\x15.connpy.ValueResponse\"\x00\x12>\n\x0clist_folders\x12\x15.connpy.FilterRequest\x1a\x15.connpy.ValueResponse\"\x00\x12?\n\x10get_node_details\x12\x11.connpy.IdRequest\x1a\x16.connpy.StructResponse\"\x00\x12<\n\x0e\x65xplode_unique\x12\x11.connpy.IdRequest\x1a\x15.connpy.ValueResponse\"\x00\x12\x42\n\x0egenerate_cache\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12\x39\n\x08\x61\x64\x64_node\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x0bupdate_node\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12>\n\x0b\x64\x65lete_node\x12\x15.connpy.DeleteRequest\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\tmove_node\x12\x13.connpy.MoveRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x39\n\x08\x62ulk_add\x12\x13.connpy.BulkRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x45\n\x16validate_parent_folder\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x12set_reserved_names\x12\x13.connpy.ListRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n\rinteract_node\x12\x17.connpy.InteractRequest\x1a\x18.connpy.InteractResponse\"\x00(\x01\x30\x01\x12\x44\n\x0c\x66ull_replace\x12\x1a.connpy.FullReplaceRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x45\n\rget_inventory\x12\x16.google.protobuf.Empty\x1a\x1a.connpy.FullReplaceRequest\"\x00\x32\x96\x03\n\x0eProfileService\x12?\n\rlist_profiles\x12\x15.connpy.FilterRequest\x1a\x15.connpy.ValueResponse\"\x00\x12?\n\x0bget_profile\x12\x16.connpy.ProfileRequest\x1a\x16.connpy.StructResponse\"\x00\x12<\n\x0b\x61\x64\x64_profile\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x44\n\x11resolve_node_data\x12\x15.connpy.StructRequest\x1a\x16.connpy.StructResponse\"\x00\x12=\n\x0e\x64\x65lete_profile\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12?\n\x0eupdate_profile\x12\x13.connpy.NodeRequest\x1a\x16.google.protobuf.Empty\"\x00\x32\xae\x03\n\rConfigService\x12@\n\x0cget_settings\x12\x16.google.protobuf.Empty\x1a\x16.connpy.StructResponse\"\x00\x12\x43\n\x0fget_default_dir\x12\x16.google.protobuf.Empty\x1a\x16.connpy.StringResponse\"\x00\x12\x44\n\x11set_config_folder\x12\x15.connpy.StringRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x41\n\x0eupdate_setting\x12\x15.connpy.UpdateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x10\x65ncrypt_password\x12\x15.connpy.StringRequest\x1a\x16.connpy.StringResponse\"\x00\x12H\n\x15\x61pply_theme_from_file\x12\x15.connpy.StringRequest\x1a\x16.connpy.StructResponse\"\x00\x32\xca\x02\n\rPluginService\x12?\n\x0clist_plugins\x12\x16.google.protobuf.Empty\x1a\x15.connpy.ValueResponse\"\x00\x12=\n\nadd_plugin\x12\x15.connpy.PluginRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\rdelete_plugin\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\renable_plugin\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x12=\n\x0e\x64isable_plugin\x12\x11.connpy.IdRequest\x1a\x16.google.protobuf.Empty\"\x00\x32\x9b\x02\n\x10\x45xecutionService\x12=\n\x0crun_commands\x12\x12.connpy.RunRequest\x1a\x15.connpy.NodeRunResult\"\x00\x30\x01\x12?\n\rtest_commands\x12\x13.connpy.TestRequest\x1a\x15.connpy.NodeRunResult\"\x00\x30\x01\x12\x41\n\x0erun_cli_script\x12\x15.connpy.ScriptRequest\x1a\x16.connpy.StructResponse\"\x00\x12\x44\n\x11run_yaml_playbook\x12\x15.connpy.ScriptRequest\x1a\x16.connpy.StructResponse\"\x00\x32\xe2\x01\n\x13ImportExportService\x12\x41\n\x0e\x65xport_to_file\x12\x15.connpy.ExportRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x10import_from_file\x12\x15.connpy.StringRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x43\n\x12set_reserved_names\x12\x13.connpy.ListRequest\x1a\x16.google.protobuf.Empty\"\x00\x32\xd0\x03\n\tAIService\x12\x33\n\x03\x61sk\x12\x12.connpy.AskRequest\x1a\x12.connpy.AIResponse\"\x00(\x01\x30\x01\x12\x38\n\x07\x63onfirm\x12\x15.connpy.StringRequest\x1a\x14.connpy.BoolResponse\"\x00\x12@\n\x0b\x61sk_copilot\x12\x16.connpy.CopilotRequest\x1a\x17.connpy.CopilotResponse\"\x00\x12@\n\rlist_sessions\x12\x16.google.protobuf.Empty\x1a\x15.connpy.ValueResponse\"\x00\x12\x41\n\x0e\x64\x65lete_session\x12\x15.connpy.StringRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n\x12\x63onfigure_provider\x12\x17.connpy.ProviderRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x44\n\x11load_session_data\x12\x15.connpy.StringRequest\x1a\x16.connpy.StructResponse\"\x00\x32\xc2\x02\n\rSystemService\x12\x39\n\tstart_api\x12\x12.connpy.IntRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x39\n\tdebug_api\x12\x12.connpy.IntRequest\x1a\x16.google.protobuf.Empty\"\x00\x12<\n\x08stop_api\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12;\n\x0brestart_api\x12\x12.connpy.IntRequest\x1a\x16.google.protobuf.Empty\"\x00\x12@\n\x0eget_api_status\x12\x16.google.protobuf.Empty\x1a\x14.connpy.BoolResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -36,79 +36,79 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['_INTERACTREQUEST']._serialized_start=84 _globals['_INTERACTREQUEST']._serialized_end=304 _globals['_INTERACTRESPONSE']._serialized_start=307 - _globals['_INTERACTRESPONSE']._serialized_end=535 - _globals['_FILTERREQUEST']._serialized_start=537 - _globals['_FILTERREQUEST']._serialized_end=592 - _globals['_VALUERESPONSE']._serialized_start=594 - _globals['_VALUERESPONSE']._serialized_end=647 - _globals['_IDREQUEST']._serialized_start=649 - _globals['_IDREQUEST']._serialized_end=672 - _globals['_NODEREQUEST']._serialized_start=674 - _globals['_NODEREQUEST']._serialized_end=757 - _globals['_DELETEREQUEST']._serialized_start=759 - _globals['_DELETEREQUEST']._serialized_end=805 - _globals['_MESSAGEVALUE']._serialized_start=807 - _globals['_MESSAGEVALUE']._serialized_end=836 - _globals['_MOVEREQUEST']._serialized_start=838 - _globals['_MOVEREQUEST']._serialized_end=897 - _globals['_BULKREQUEST']._serialized_start=899 - _globals['_BULKREQUEST']._serialized_end=986 - _globals['_STRUCTRESPONSE']._serialized_start=988 - _globals['_STRUCTRESPONSE']._serialized_end=1043 - _globals['_PROFILEREQUEST']._serialized_start=1045 - _globals['_PROFILEREQUEST']._serialized_end=1092 - _globals['_STRUCTREQUEST']._serialized_start=1094 - _globals['_STRUCTREQUEST']._serialized_end=1148 - _globals['_STRINGREQUEST']._serialized_start=1150 - _globals['_STRINGREQUEST']._serialized_end=1180 - _globals['_STRINGRESPONSE']._serialized_start=1182 - _globals['_STRINGRESPONSE']._serialized_end=1213 - _globals['_UPDATEREQUEST']._serialized_start=1215 - _globals['_UPDATEREQUEST']._serialized_end=1282 - _globals['_PLUGINREQUEST']._serialized_start=1284 - _globals['_PLUGINREQUEST']._serialized_end=1350 - _globals['_RUNREQUEST']._serialized_start=1353 - _globals['_RUNREQUEST']._serialized_end=1518 - _globals['_TESTREQUEST']._serialized_start=1521 - _globals['_TESTREQUEST']._serialized_end=1705 - _globals['_SCRIPTREQUEST']._serialized_start=1707 - _globals['_SCRIPTREQUEST']._serialized_end=1772 - _globals['_EXPORTREQUEST']._serialized_start=1774 - _globals['_EXPORTREQUEST']._serialized_end=1825 - _globals['_LISTREQUEST']._serialized_start=1827 - _globals['_LISTREQUEST']._serialized_end=1855 - _globals['_ASKREQUEST']._serialized_start=1858 - _globals['_ASKREQUEST']._serialized_end=2152 - _globals['_AIRESPONSE']._serialized_start=2155 - _globals['_AIRESPONSE']._serialized_end=2355 - _globals['_BOOLRESPONSE']._serialized_start=2357 - _globals['_BOOLRESPONSE']._serialized_end=2386 - _globals['_PROVIDERREQUEST']._serialized_start=2388 - _globals['_PROVIDERREQUEST']._serialized_end=2455 - _globals['_INTREQUEST']._serialized_start=2457 - _globals['_INTREQUEST']._serialized_end=2484 - _globals['_NODERUNRESULT']._serialized_start=2486 - _globals['_NODERUNRESULT']._serialized_end=2598 - _globals['_FULLREPLACEREQUEST']._serialized_start=2600 - _globals['_FULLREPLACEREQUEST']._serialized_end=2709 - _globals['_COPILOTREQUEST']._serialized_start=2711 - _globals['_COPILOTREQUEST']._serialized_end=2799 - _globals['_COPILOTRESPONSE']._serialized_start=2801 - _globals['_COPILOTRESPONSE']._serialized_end=2886 - _globals['_NODESERVICE']._serialized_start=2889 - _globals['_NODESERVICE']._serialized_end=3882 - _globals['_PROFILESERVICE']._serialized_start=3885 - _globals['_PROFILESERVICE']._serialized_end=4291 - _globals['_CONFIGSERVICE']._serialized_start=4294 - _globals['_CONFIGSERVICE']._serialized_end=4724 - _globals['_PLUGINSERVICE']._serialized_start=4727 - _globals['_PLUGINSERVICE']._serialized_end=5057 - _globals['_EXECUTIONSERVICE']._serialized_start=5060 - _globals['_EXECUTIONSERVICE']._serialized_end=5343 - _globals['_IMPORTEXPORTSERVICE']._serialized_start=5346 - _globals['_IMPORTEXPORTSERVICE']._serialized_end=5572 - _globals['_AISERVICE']._serialized_start=5575 - _globals['_AISERVICE']._serialized_end=6039 - _globals['_SYSTEMSERVICE']._serialized_start=6042 - _globals['_SYSTEMSERVICE']._serialized_end=6364 + _globals['_INTERACTRESPONSE']._serialized_end=569 + _globals['_FILTERREQUEST']._serialized_start=571 + _globals['_FILTERREQUEST']._serialized_end=626 + _globals['_VALUERESPONSE']._serialized_start=628 + _globals['_VALUERESPONSE']._serialized_end=681 + _globals['_IDREQUEST']._serialized_start=683 + _globals['_IDREQUEST']._serialized_end=706 + _globals['_NODEREQUEST']._serialized_start=708 + _globals['_NODEREQUEST']._serialized_end=791 + _globals['_DELETEREQUEST']._serialized_start=793 + _globals['_DELETEREQUEST']._serialized_end=839 + _globals['_MESSAGEVALUE']._serialized_start=841 + _globals['_MESSAGEVALUE']._serialized_end=870 + _globals['_MOVEREQUEST']._serialized_start=872 + _globals['_MOVEREQUEST']._serialized_end=931 + _globals['_BULKREQUEST']._serialized_start=933 + _globals['_BULKREQUEST']._serialized_end=1020 + _globals['_STRUCTRESPONSE']._serialized_start=1022 + _globals['_STRUCTRESPONSE']._serialized_end=1077 + _globals['_PROFILEREQUEST']._serialized_start=1079 + _globals['_PROFILEREQUEST']._serialized_end=1126 + _globals['_STRUCTREQUEST']._serialized_start=1128 + _globals['_STRUCTREQUEST']._serialized_end=1182 + _globals['_STRINGREQUEST']._serialized_start=1184 + _globals['_STRINGREQUEST']._serialized_end=1214 + _globals['_STRINGRESPONSE']._serialized_start=1216 + _globals['_STRINGRESPONSE']._serialized_end=1247 + _globals['_UPDATEREQUEST']._serialized_start=1249 + _globals['_UPDATEREQUEST']._serialized_end=1316 + _globals['_PLUGINREQUEST']._serialized_start=1318 + _globals['_PLUGINREQUEST']._serialized_end=1384 + _globals['_RUNREQUEST']._serialized_start=1387 + _globals['_RUNREQUEST']._serialized_end=1552 + _globals['_TESTREQUEST']._serialized_start=1555 + _globals['_TESTREQUEST']._serialized_end=1739 + _globals['_SCRIPTREQUEST']._serialized_start=1741 + _globals['_SCRIPTREQUEST']._serialized_end=1806 + _globals['_EXPORTREQUEST']._serialized_start=1808 + _globals['_EXPORTREQUEST']._serialized_end=1859 + _globals['_LISTREQUEST']._serialized_start=1861 + _globals['_LISTREQUEST']._serialized_end=1889 + _globals['_ASKREQUEST']._serialized_start=1892 + _globals['_ASKREQUEST']._serialized_end=2186 + _globals['_AIRESPONSE']._serialized_start=2189 + _globals['_AIRESPONSE']._serialized_end=2389 + _globals['_BOOLRESPONSE']._serialized_start=2391 + _globals['_BOOLRESPONSE']._serialized_end=2420 + _globals['_PROVIDERREQUEST']._serialized_start=2422 + _globals['_PROVIDERREQUEST']._serialized_end=2489 + _globals['_INTREQUEST']._serialized_start=2491 + _globals['_INTREQUEST']._serialized_end=2518 + _globals['_NODERUNRESULT']._serialized_start=2520 + _globals['_NODERUNRESULT']._serialized_end=2632 + _globals['_FULLREPLACEREQUEST']._serialized_start=2634 + _globals['_FULLREPLACEREQUEST']._serialized_end=2743 + _globals['_COPILOTREQUEST']._serialized_start=2745 + _globals['_COPILOTREQUEST']._serialized_end=2833 + _globals['_COPILOTRESPONSE']._serialized_start=2835 + _globals['_COPILOTRESPONSE']._serialized_end=2920 + _globals['_NODESERVICE']._serialized_start=2923 + _globals['_NODESERVICE']._serialized_end=3916 + _globals['_PROFILESERVICE']._serialized_start=3919 + _globals['_PROFILESERVICE']._serialized_end=4325 + _globals['_CONFIGSERVICE']._serialized_start=4328 + _globals['_CONFIGSERVICE']._serialized_end=4758 + _globals['_PLUGINSERVICE']._serialized_start=4761 + _globals['_PLUGINSERVICE']._serialized_end=5091 + _globals['_EXECUTIONSERVICE']._serialized_start=5094 + _globals['_EXECUTIONSERVICE']._serialized_end=5377 + _globals['_IMPORTEXPORTSERVICE']._serialized_start=5380 + _globals['_IMPORTEXPORTSERVICE']._serialized_end=5606 + _globals['_AISERVICE']._serialized_start=5609 + _globals['_AISERVICE']._serialized_end=6073 + _globals['_SYSTEMSERVICE']._serialized_start=6076 + _globals['_SYSTEMSERVICE']._serialized_end=6398 # @@protoc_insertion_point(module_scope) diff --git a/connpy/grpc_layer/connpy_pb2_grpc.py b/connpy/grpc_layer/connpy_pb2_grpc.py index e7ceb6c..dd9b5e7 100644 --- a/connpy/grpc_layer/connpy_pb2_grpc.py +++ b/connpy/grpc_layer/connpy_pb2_grpc.py @@ -3,7 +3,7 @@ import grpc import warnings -from . import connpy_pb2 as connpy__pb2 +import connpy_pb2 as connpy__pb2 from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 GRPC_GENERATED_VERSION = '1.80.0' diff --git a/connpy/grpc_layer/server.py b/connpy/grpc_layer/server.py index d5fd47b..c019be4 100644 --- a/connpy/grpc_layer/server.py +++ b/connpy/grpc_layer/server.py @@ -217,6 +217,14 @@ class NodeServicer(connpy_pb2_grpc.NodeServiceServicer): )) # 2. Await the question from client via the copilot_queue + import threading + def preload_ai_deps(): + try: + import litellm + except Exception: + pass + threading.Thread(target=preload_ai_deps, daemon=True).start() + try: req_data = await asyncio.wait_for(remote_stream.copilot_queue.get(), timeout=120) if "question" not in req_data or not req_data["question"] or req_data["question"] == "CANCEL": @@ -240,7 +248,27 @@ class NodeServicer(connpy_pb2_grpc.NodeServiceServicer): copilot_stream_chunk=chunk_text )) - result = await asyncio.to_thread(service.ask_copilot, context_buffer, question, node_info, chunk_callback=chunk_callback) + ai_task = asyncio.create_task(service.aask_copilot(context_buffer, question, node_info, chunk_callback=chunk_callback)) + wait_action_task = asyncio.create_task(remote_stream.copilot_queue.get()) + + done, pending = await asyncio.wait( + [ai_task, wait_action_task], + return_when=asyncio.FIRST_COMPLETED + ) + + if wait_action_task in done: + req_data = wait_action_task.result() + ai_task.cancel() + if req_data.get("question") == "CANCEL" or req_data.get("action") == "cancel": + os.write(child_fd, b'\x15\r') + return + return + else: + wait_action_task.cancel() + result = ai_task.result() + if not result: + os.write(child_fd, b'\x15\r') + return # 4. Send response back to client response_queue.put(connpy_pb2.InteractResponse( @@ -250,10 +278,12 @@ class NodeServicer(connpy_pb2_grpc.NodeServiceServicer): # 5. Wait for user action try: action_data = await asyncio.wait_for(remote_stream.copilot_queue.get(), timeout=60) - if "action" not in action_data or not action_data["action"]: + if "action" not in action_data or not action_data["action"] or action_data["action"] == "cancel": + os.write(child_fd, b'\x15\r') return action = action_data["action"] except asyncio.TimeoutError: + os.write(child_fd, b'\x15\r') return if action == "send_all": @@ -262,6 +292,7 @@ class NodeServicer(connpy_pb2_grpc.NodeServiceServicer): await asyncio.sleep(0.1) for cmd in commands: os.write(child_fd, (cmd + "\n").encode()) + response_queue.put(connpy_pb2.InteractResponse(copilot_injected_command=cmd)) await asyncio.sleep(0.3) elif action.startswith("custom:"): custom_cmds = action[7:] @@ -270,6 +301,7 @@ class NodeServicer(connpy_pb2_grpc.NodeServiceServicer): for cmd in custom_cmds.split('\n'): if cmd.strip(): os.write(child_fd, (cmd.strip() + "\n").encode()) + response_queue.put(connpy_pb2.InteractResponse(copilot_injected_command=cmd.strip())) await asyncio.sleep(0.3) elif action not in ('cancel', 'n', 'no'): # Handle numbers and ranges like "1,2,4-6" @@ -294,6 +326,7 @@ class NodeServicer(connpy_pb2_grpc.NodeServiceServicer): await asyncio.sleep(0.1) for idx in valid_indices: os.write(child_fd, (commands[idx] + "\n").encode()) + response_queue.put(connpy_pb2.InteractResponse(copilot_injected_command=commands[idx])) await asyncio.sleep(0.3) else: os.write(child_fd, b'\x15\r') diff --git a/connpy/grpc_layer/stubs.py b/connpy/grpc_layer/stubs.py index 02c2b72..05f04fb 100644 --- a/connpy/grpc_layer/stubs.py +++ b/connpy/grpc_layer/stubs.py @@ -53,7 +53,7 @@ class NodeStub: request_queue = queue.Queue() client_buffer_bytes = bytearray() - cmd_byte_positions = [0] + cmd_byte_positions = [(0, None)] pause_stdin = [False] wake_r, wake_w = os.pipe() @@ -101,7 +101,7 @@ class NodeStub: if not data: break if b'\r' in data or b'\n' in data: - cmd_byte_positions.append(len(client_buffer_bytes)) + cmd_byte_positions.append((len(client_buffer_bytes), None)) yield connpy_pb2.InteractRequest(stdin_data=data) except OSError: break @@ -123,9 +123,26 @@ class NodeStub: tty.setraw(sys.stdin.fileno()) response_iterator = self.stub.interact_node(request_generator()) + import queue + response_queue = queue.Queue() + + def response_consumer(): + try: + for r in response_iterator: + response_queue.put(r) + except Exception: + pass + response_queue.put(None) + + t_consumer = threading.Thread(target=response_consumer, daemon=True) + t_consumer.start() + # First phase: Wait for connection status, print early data try: - for res in response_iterator: + while True: + res = response_queue.get() + if res is None: + return if res.stdout_data: data = res.stdout_data if debug: @@ -145,13 +162,16 @@ class NodeStub: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) printer.error(f"Connection failed: {res.error_message}") return - except StopIteration: + except queue.Empty: return # Second phase: Stream active session # Clear screen filter is only applied before success (Phase 1). # Once the user has a prompt, Ctrl+L must work normally. - for res in response_iterator: + while True: + res = response_queue.get() + if res is None: + break if res.copilot_prompt: pause_generator() import json @@ -165,6 +185,7 @@ class NodeStub: from prompt_toolkit.formatted_text import HTML from prompt_toolkit.history import InMemoryHistory from ..printer import connpy_theme + from ..core import copilot_terminal_mode if not hasattr(self, 'copilot_history'): self.copilot_history = InMemoryHistory() @@ -200,17 +221,30 @@ class NodeStub: prompt_re = re.compile(re.sub(r'(?