code exec interface + preload

frdel committed Aug 12, 2025 at 20:04 UTC 66e01e68e32170f0c1ca0a18302821bd70226181
8 files changed +62 -19
preload.py
+2 -2
@@ -40,8 +40,8 @@ async def preload():
40 # async tasks to preload
41 tasks = [
42 preload_embedding(),
43 - preload_whisper(),
44 - preload_kokoro()
43 + # preload_whisper(),
44 + # preload_kokoro()
45 ]
46
47 await asyncio.gather(*tasks, return_exceptions=True)
python/api/synthesize.py
+4 -4
@@ -7,11 +7,11 @@ from python.helpers import runtime, settings, kokoro_tts
7 class Synthesize(ApiHandler):
8 async def process(self, input: dict, request: Request) -> dict | Response:
9 text = input.get("text", "")
10 - ctxid = input.get("ctxid", "")
10 + # ctxid = input.get("ctxid", "")
11
12 - context = self.get_context(ctxid)
13 - if not await kokoro_tts.is_downloaded():
14 - context.log.log(type="info", content="Kokoro TTS model is currently being initialized, please wait...")
12 + # context = self.get_context(ctxid)
13 + # if not await kokoro_tts.is_downloaded():
14 + # context.log.log(type="info", content="Kokoro TTS model is currently being initialized, please wait...")
15
16 try:
17 # # Clean and chunk text for long responses
python/api/transcribe.py
+4 -4
@@ -5,11 +5,11 @@ from python.helpers import runtime, settings, whisper
5 class Transcribe(ApiHandler):
6 async def process(self, input: dict, request: Request) -> dict | Response:
7 audio = input.get("audio")
8 - ctxid = input.get("ctxid", "")
8 + # ctxid = input.get("ctxid", "")
9
10 - context = self.get_context(ctxid)
11 - if not await whisper.is_downloaded():
12 - context.log.log(type="info", content="Whisper STT model is currently being initialized, please wait...")
10 + # context = self.get_context(ctxid)
11 + # if not await whisper.is_downloaded():
12 + # context.log.log(type="info", content="Whisper STT model is currently being initialized, please wait...")
13
14 set = settings.get_settings()
15 result = await whisper.transcribe(set["stt_model_size"], audio) # type: ignore
python/helpers/fasta2a_server.py
+1 -1
@@ -258,7 +258,7 @@ class DynamicA2AProxy:
258 # Atomic update of the app
259 self.app = new_app
260
261 - _PRINTER.print("[A2A] FastA2A server configured successfully")
261 + # _PRINTER.print("[A2A] FastA2A server configured successfully")
262
263 except Exception as e:
264 _PRINTER.print(f"[A2A] Failed to configure FastA2A server: {e}")
python/helpers/kokoro_tts.py
+13
@@ -7,6 +7,7 @@ import asyncio
7 import soundfile as sf
8 from python.helpers import runtime
9 from python.helpers.print_style import PrintStyle
10 +from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
11
12 warnings.filterwarnings("ignore", category=FutureWarning)
13 warnings.filterwarnings("ignore", category=UserWarning)
@@ -38,9 +39,21 @@ async def _preload():
39 try:
40 is_updating_model = True
41 if not _pipeline:
42 + NotificationManager.send_notification(
43 + NotificationType.INFO,
44 + NotificationPriority.NORMAL,
45 + "Loading Kokoro TTS model...",
46 + display_time=99,
47 + group="kokoro-preload")
48 PrintStyle.standard("Loading Kokoro TTS model...")
49 from kokoro import KPipeline
50 _pipeline = KPipeline(lang_code="a", repo_id="hexgrad/Kokoro-82M")
51 + NotificationManager.send_notification(
52 + NotificationType.INFO,
53 + NotificationPriority.NORMAL,
54 + "Kokoro TTS model loaded.",
55 + display_time=2,
56 + group="kokoro-preload")
57 finally:
58 is_updating_model = False
59
python/helpers/settings.py
+16 -2
@@ -84,6 +84,8 @@ class Settings(TypedDict):
84 rfc_port_http: int
85 rfc_port_ssh: int
86
87 + shell_interface: Literal['local','ssh']
88 +
89 stt_model_size: str
90 stt_language: str
91 stt_silence_threshold: float
@@ -793,6 +795,17 @@ def convert_out(settings: Settings) -> SettingsOutput:
795
796 dev_fields: list[SettingsField] = []
797
798 + dev_fields.append(
799 + {
800 + "id": "shell_interface",
801 + "title": "Shell Interface",
802 + "description": "Terminal interface used for Code Execution Tool. Local Python TTY works locally in both dockerized and development environments. SSH always connects to dockerized environment (automatically at localhost or RFC host address).",
803 + "type": "select",
804 + "value": settings["shell_interface"],
805 + "options": [{"value": "local", "label": "Local Python TTY"}, {"value": "ssh", "label": "SSH"}],
806 + }
807 + )
808 +
809 if runtime.is_development():
810 # dev_fields.append(
811 # {
@@ -1378,6 +1391,7 @@ def get_default_settings() -> Settings:
1391 rfc_password="",
1392 rfc_port_http=55080,
1393 rfc_port_ssh=55022,
1394 + shell_interface="local" if runtime.is_dockerized() else "ssh",
1395 stt_model_size="base",
1396 stt_language="en",
1397 stt_silence_threshold=0.3,
@@ -1539,7 +1553,7 @@ def set_root_password(password: str):
1553 def get_runtime_config(set: Settings):
1554 if runtime.is_dockerized():
1555 return {
1542 - "code_exec_ssh_enabled": False,
1556 + "code_exec_ssh_enabled": set["shell_interface"] == "ssh",
1557 "code_exec_ssh_addr": "localhost",
1558 "code_exec_ssh_port": 22,
1559 "code_exec_ssh_user": "root",
@@ -1553,7 +1567,7 @@ def get_runtime_config(set: Settings):
1567 if host.endswith("/"):
1568 host = host[:-1]
1569 return {
1556 - "code_exec_ssh_enabled": True,
1570 + "code_exec_ssh_enabled": set["shell_interface"] == "ssh",
1571 "code_exec_ssh_addr": host,
1572 "code_exec_ssh_port": set["rfc_port_ssh"],
1573 "code_exec_ssh_user": "root",
python/helpers/whisper.py
+16 -3
@@ -5,6 +5,7 @@ import tempfile
5 import asyncio
6 from python.helpers import runtime, rfc, settings, files
7 from python.helpers.print_style import PrintStyle
8 +from python.helpers.notification import NotificationManager, NotificationType, NotificationPriority
9
10 # Suppress FutureWarning from torch.load
11 warnings.filterwarnings("ignore", category=FutureWarning)
@@ -30,9 +31,21 @@ async def _preload(model_name:str):
31 try:
32 is_updating_model = True
33 if not _model or _model_name != model_name:
33 - PrintStyle.standard(f"Loading Whisper model: {model_name}")
34 - _model = whisper.load_model(name=model_name, download_root=files.get_abs_path("/tmp/models/whisper")) # type: ignore
35 - _model_name = model_name
34 + NotificationManager.send_notification(
35 + NotificationType.INFO,
36 + NotificationPriority.NORMAL,
37 + "Loading Whisper model...",
38 + display_time=99,
39 + group="whisper-preload")
40 + PrintStyle.standard(f"Loading Whisper model: {model_name}")
41 + _model = whisper.load_model(name=model_name, download_root=files.get_abs_path("/tmp/models/whisper")) # type: ignore
42 + _model_name = model_name
43 + NotificationManager.send_notification(
44 + NotificationType.INFO,
45 + NotificationPriority.NORMAL,
46 + "Whisper model loaded.",
47 + display_time=2,
48 + group="whisper-preload")
49 finally:
50 is_updating_model = False
51
python/tools/code_execution_tool.py
+6 -3
@@ -15,6 +15,7 @@ import re
15
16 @dataclass
17 class State:
18 + ssh_enabled: bool
19 shells: dict[int, LocalInteractiveSession | SSHInteractiveSession]
20
21
@@ -77,7 +78,8 @@ class CodeExecution(Tool):
78
79 async def prepare_state(self, reset=False, session: int | None = None):
80 self.state: State | None = self.agent.get_data("_cet_state")
80 - if not self.state:
81 + # always reset state when ssh_enabled changes
82 + if not self.state or self.state.ssh_enabled != self.agent.config.code_exec_ssh_enabled:
83 # initialize shells dictionary if not exists
84 shells: dict[int, LocalInteractiveSession | SSHInteractiveSession] = {}
85 else:
@@ -114,7 +116,7 @@ class CodeExecution(Tool):
116 shells[session] = shell
117 await shell.connect()
118
117 - self.state = State(shells=shells)
119 + self.state = State(shells=shells, ssh_enabled=self.agent.config.code_exec_ssh_enabled)
120 self.agent.set_data("_cet_state", self.state)
121 return self.state
122
@@ -201,9 +203,10 @@ class CodeExecution(Tool):
203
204 # Common shell prompt regex patterns (add more as needed)
205 prompt_patterns = [
204 - re.compile(r"\\(venv\\).+[$#] ?$"), # (venv) ...$ or (venv) ...#
206 + re.compile(r"\(venv\).+[$#] ?$"), # (venv) ...$ or (venv) ...#
207 re.compile(r"root@[^:]+:[^#]+# ?$"), # root@container:~#
208 re.compile(r"[a-zA-Z0-9_.-]+@[^:]+:[^$#]+[$#] ?$"), # user@host:~$
209 + re.compile(r"bash-\d+\.\d+\$ ?$"), # bash-3.2$ (version can vary)
210 ]
211
212 # potential dialog detection