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(""): current_guide = current_guide[:-2]
+ 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]: {pt_color}>"),
- key_bindings=confirm_bindings
- )
+ try:
+ action = await confirm_session.prompt_async(
+ HTML(f"<{pt_color}>Send commands? (y/n/e/number/range) [n]: {pt_color}>"),
+ 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'(?