mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-28 16:20:56 +08:00
* ♻️ refactor(core): 重构 AI 能力与定时任务调度系统 - 【AI 能力与工具】重构 Capability 注册与管理机制,引入 CapabilityManager 统一管理 - 移除全局能力注册表,改用声明式装饰器 `@capability` 进行解耦注册 - 重构工具解析器链,使用统一的 BaseToolResolver 代替原有的多个特定解析器 - 增强工具查询过滤,支持通配符匹配、工具箱过滤和排除标签 - 【定时任务调度】重构定时任务管理器,引入 SchedulerRegistry 统一管理任务元数据 - 引入 JobConfig 聚合定时任务配置,支持用户维度的定时任务调度 - 重构执行分发器,支持并发限制、串行间隔和随机延迟打散 - 【运行上下文】引入 ScheduledDeps 以支持后台和定时任务环境下的依赖注入 - 优化 RunContext,支持从定时任务上下文快速构造,并提供 emit 辅助方法 - 【日志与监控】引入 AILoggerProxy,实现 AI 各模块的专属日志输出 - 将各模块的全局 logger 替换为对应的模块专属日志代理 - 【其他优化】修复 Pydantic V1 兼容层中 model_validator 的装饰器兼容性问题 - 在非交互式环境(如定时任务)中自动隐藏 HITL 交互工具以节省 Token * ♻️ refactor(core): 优化内部导入路径并提升 Pydantic 兼容性 - 【重构】将 `services/ai` 模块内的绝对导入重构为相对导入,优化包结构 - 【重构】移除不必要的 `if TYPE_CHECKING` 保护,通过 `from __future__ import annotations` 直接导入类型 - 【清理】清理 `core/messages/types.py` 中未使用的 `AssistantContentUnion` 等联合类型定义 - 【优化】在 `utils/pydantic_compat.py` 中新增 `model_rebuild` 兼容函数,统一 Pydantic V1/V2 的模型重建逻辑 - 【优化】将部分函数内部的延迟导入提升至模块顶部,规范代码结构 * ♻️ refactor(imports): 优化导入路径为相对导入并清理冗余导入 - 【重构】将 AI 服务相关模块中的绝对导入路径修改为相对导入,提升模块内聚性与可移植性 - 【清理】移除多处函数内部或类方法中未使用的冗余导入,避免循环引用和资源浪费 - 【格式化】微调部分工具装饰器和返回语句的格式与尾随逗号 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
597 lines
21 KiB
Python
597 lines
21 KiB
Python
from abc import ABC, abstractmethod
|
|
import asyncio
|
|
from collections.abc import Awaitable, Callable
|
|
from pathlib import Path
|
|
from typing import Any, ClassVar
|
|
import uuid
|
|
|
|
import aiohttp
|
|
|
|
from zhenxun.services.ai.utils.logger import log_sandbox as logger
|
|
from zhenxun.utils.utils import infer_plugin_namespace
|
|
|
|
from .drivers.base import BaseSandboxSession
|
|
from .models import (
|
|
LanguageProfile,
|
|
SandboxExecutionResult,
|
|
)
|
|
|
|
|
|
def parse_shebang(script_path: str | Path) -> str | None:
|
|
"""解析脚本首行的 Shebang,获取解释器名称"""
|
|
path = Path(script_path)
|
|
if not path.is_file():
|
|
return None
|
|
|
|
try:
|
|
with open(path, encoding="utf-8") as f:
|
|
first_line = f.readline().strip()
|
|
|
|
if not first_line.startswith("#!"):
|
|
return None
|
|
|
|
shebang = first_line[2:].strip()
|
|
if not shebang:
|
|
return None
|
|
|
|
parts = shebang.split()
|
|
if Path(parts[0]).name == "env":
|
|
for part in parts[1:]:
|
|
if not part.startswith("-"):
|
|
return part
|
|
return None
|
|
return Path(parts[0]).name
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def get_execution_command(
|
|
script_path: str | Path, args: list[str] | None = None
|
|
) -> str:
|
|
"""根据 Shebang 或文件后缀生成对应的脚本执行命令"""
|
|
path = Path(script_path)
|
|
interpreter = parse_shebang(path)
|
|
if not interpreter:
|
|
ext = path.suffix.lower()
|
|
if ext == ".py":
|
|
interpreter = "python3"
|
|
elif ext in (".js", ".ts"):
|
|
interpreter = "node"
|
|
elif ext in (".sh", ".bash"):
|
|
interpreter = "bash"
|
|
else:
|
|
interpreter = "sh"
|
|
|
|
cmd_parts = [interpreter, path.as_posix()]
|
|
if args:
|
|
cmd_parts.extend(args)
|
|
return " ".join(cmd_parts)
|
|
|
|
|
|
class JupyterWSClient:
|
|
"""管理与单个 Jupyter Kernel WebSocket 通道通信的客户端"""
|
|
|
|
def __init__(
|
|
self,
|
|
http_session: aiohttp.ClientSession,
|
|
base_url: str,
|
|
ws_url: str,
|
|
kernel_id: str,
|
|
):
|
|
"""初始化 Jupyter WebSocket 客户端并绑定内核ID"""
|
|
self.http_session = http_session
|
|
self.base_url = base_url
|
|
self.ws_url = ws_url
|
|
self.kernel_id = kernel_id
|
|
self.ws: aiohttp.ClientWebSocketResponse | None = None
|
|
|
|
async def _connect_ws(self) -> aiohttp.ClientWebSocketResponse:
|
|
"""建立并返回与 Jupyter Kernel 活跃的 WebSocket 连接"""
|
|
if self.ws is None or self.ws.closed:
|
|
self.ws = await self.http_session.ws_connect(
|
|
f"{self.ws_url}/api/kernels/{self.kernel_id}/channels"
|
|
)
|
|
assert self.ws is not None
|
|
return self.ws
|
|
|
|
async def interrupt(self):
|
|
"""向 Jupyter Kernel 发送 HTTP POST 中断正在执行的进程"""
|
|
try:
|
|
async with self.http_session.post(
|
|
f"{self.base_url}/api/kernels/{self.kernel_id}/interrupt"
|
|
):
|
|
pass
|
|
logger.debug(
|
|
f"[JupyterWSClient] 成功向 Kernel {self.kernel_id} 发送中断信号"
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"[JupyterWSClient] 中断 Kernel 失败: {e}")
|
|
|
|
async def execute(self, code: str, timeout: int = 30, on_output=None):
|
|
"""在 Jupyter 核心中执行代码,并通过 WebSocket 接收标准输出、标准错误和图像"""
|
|
background_tasks: set[asyncio.Task[None]] = set()
|
|
|
|
try:
|
|
ws = await self._connect_ws()
|
|
except Exception as e:
|
|
return SandboxExecutionResult(
|
|
exit_code=-1, error=f"网络连接 Jupyter Kernel 失败: {e}"
|
|
)
|
|
|
|
msg_id = uuid.uuid4().hex
|
|
req = {
|
|
"header": {
|
|
"msg_id": msg_id,
|
|
"msg_type": "execute_request",
|
|
"version": "5.0",
|
|
},
|
|
"parent_header": {},
|
|
"metadata": {},
|
|
"channel": "shell",
|
|
"content": {"code": code, "silent": False, "store_history": False},
|
|
}
|
|
|
|
try:
|
|
if ws.closed:
|
|
ws = await self._connect_ws()
|
|
await ws.send_json(req)
|
|
except Exception as e:
|
|
logger.warning(f"[JupyterWSClient] WebSocket 发送失败,尝试重连: {e}")
|
|
self.ws = None
|
|
ws = await self._connect_ws()
|
|
await ws.send_json(req)
|
|
|
|
stdout_parts = []
|
|
stderr_parts = []
|
|
images = []
|
|
exit_code = 0
|
|
current_out_len = 0
|
|
MAX_OUT_LEN = 50000
|
|
|
|
async def _receive_loop():
|
|
nonlocal exit_code, current_out_len
|
|
while True:
|
|
msg = await ws.receive_json()
|
|
if msg.get("parent_header", {}).get("msg_id") != msg_id:
|
|
continue
|
|
|
|
msg_type = msg["msg_type"]
|
|
content = msg["content"]
|
|
|
|
def _append_text(text: str, is_stdout: bool = True) -> bool:
|
|
nonlocal current_out_len
|
|
if current_out_len + len(text) > MAX_OUT_LEN:
|
|
allowed_len = max(0, MAX_OUT_LEN - current_out_len)
|
|
truncated = (
|
|
text[:allowed_len]
|
|
+ "\n...[系统警告:输出超长已被强行截断]..."
|
|
)
|
|
(stdout_parts if is_stdout else stderr_parts).append(truncated)
|
|
current_out_len += len(truncated)
|
|
return False
|
|
current_out_len += len(text)
|
|
(stdout_parts if is_stdout else stderr_parts).append(text)
|
|
if on_output:
|
|
t = asyncio.create_task(
|
|
on_output(
|
|
"stdout" if is_stdout else "stderr",
|
|
text.encode("utf-8"),
|
|
)
|
|
)
|
|
background_tasks.add(t)
|
|
t.add_done_callback(background_tasks.discard)
|
|
return True
|
|
|
|
if msg_type == "stream":
|
|
if not _append_text(
|
|
content["text"], is_stdout=(content["name"] == "stdout")
|
|
):
|
|
task = asyncio.create_task(self.interrupt())
|
|
background_tasks.add(task)
|
|
task.add_done_callback(background_tasks.discard)
|
|
exit_code = -1
|
|
break
|
|
elif msg_type == "error":
|
|
if not _append_text(
|
|
"\n".join(content["traceback"]), is_stdout=False
|
|
):
|
|
break
|
|
exit_code = 1
|
|
elif msg_type in ["display_data", "execute_result"]:
|
|
data = content.get("data", {})
|
|
if "text/plain" in data:
|
|
if not _append_text(data["text/plain"] + "\n", is_stdout=True):
|
|
break
|
|
if "image/png" in data:
|
|
images.append(data["image/png"])
|
|
elif msg_type == "execute_reply":
|
|
if content.get("status") == "error":
|
|
exit_code = 1
|
|
break
|
|
|
|
try:
|
|
await asyncio.wait_for(_receive_loop(), timeout=timeout)
|
|
except (asyncio.TimeoutError, TimeoutError):
|
|
await self.interrupt()
|
|
return SandboxExecutionResult(
|
|
stdout="".join(stdout_parts),
|
|
stderr="".join(stderr_parts),
|
|
exit_code=-1,
|
|
error=f"执行超时 ({timeout}s)",
|
|
)
|
|
except Exception as e:
|
|
return SandboxExecutionResult(
|
|
stdout="".join(stdout_parts),
|
|
stderr="".join(stderr_parts),
|
|
exit_code=-1,
|
|
error=str(e),
|
|
)
|
|
|
|
return SandboxExecutionResult(
|
|
stdout="".join(stdout_parts),
|
|
stderr="".join(stderr_parts),
|
|
exit_code=exit_code,
|
|
images=images,
|
|
)
|
|
|
|
async def close(self):
|
|
"""关闭与 Jupyter Kernel 的 WebSocket 通道连接"""
|
|
if self.ws and not self.ws.closed:
|
|
await self.ws.close()
|
|
self.ws = None
|
|
|
|
|
|
class JupyterServerManager:
|
|
"""管理沙箱内的 Jupyter 引擎生命周期及长连接"""
|
|
|
|
def __init__(self, session: BaseSandboxSession):
|
|
"""初始化 Jupyter 服务生命周期及会话连接管理器"""
|
|
self.session = session
|
|
self._http_session: aiohttp.ClientSession | None = None
|
|
self.base_url = ""
|
|
self.ws_url = ""
|
|
self._is_started = False
|
|
self._clients: dict[str, JupyterWSClient] = {}
|
|
|
|
async def ensure_started(self, env_vars: dict[str, str] | None = None):
|
|
"""确保沙箱环境内部已拉起并运行着 Jupyter Server"""
|
|
if self._is_started:
|
|
return
|
|
|
|
jupyter_port = self.session.get_meta("jupyter_port")
|
|
if not jupyter_port:
|
|
raise RuntimeError("沙箱未分配或映射 Jupyter 端口,无法建立服务")
|
|
|
|
check_jupyter = await self.session.run_process("command -v jupyter-server")
|
|
if check_jupyter.error:
|
|
raise RuntimeError(
|
|
f"检查 jupyter-server 失败 (系统错误): {check_jupyter.error}"
|
|
)
|
|
if check_jupyter.exit_code != 0:
|
|
raise RuntimeError("沙箱内未安装 jupyter-server,请检查 Blueprint")
|
|
|
|
await self.session.run_process(
|
|
"mkdir -p /tmp/jupyter_runtime /tmp/jupyter_data && "
|
|
"chmod 777 /tmp/jupyter_runtime /tmp/jupyter_data"
|
|
)
|
|
|
|
await self.session.run_process("pkill -9 -f jupyter-server || true")
|
|
await asyncio.sleep(0.5)
|
|
|
|
env_str = " ".join([f"{k}={v}" for k, v in (env_vars or {}).items()])
|
|
start_cmd = (
|
|
f"nohup env {env_str} jupyter-server "
|
|
"--ServerApp.ip=0.0.0.0 --ServerApp.port=8888 "
|
|
"--ServerApp.token='' --ServerApp.password='' "
|
|
"--ServerApp.disable_check_xsrf=True "
|
|
"--ServerApp.allow_origin='*' --ServerApp.allow_root=True "
|
|
f"> {self.session.workspace_path}/jupyter.log 2>&1 &"
|
|
)
|
|
start_res = await self.session.run_process(start_cmd)
|
|
if start_res.error:
|
|
raise RuntimeError(f"执行启动命令失败 (系统错误): {start_res.error}")
|
|
|
|
self.base_url = f"http://127.0.0.1:{jupyter_port}"
|
|
self.ws_url = f"ws://127.0.0.1:{jupyter_port}"
|
|
self._http_session = aiohttp.ClientSession()
|
|
|
|
for _ in range(15):
|
|
try:
|
|
async with self._http_session.get(
|
|
f"{self.base_url}/api/kernels"
|
|
) as resp:
|
|
if resp.status == 200:
|
|
self._is_started = True
|
|
logger.info(
|
|
"[JupyterManager] Jupyter 引擎拉起成功 "
|
|
f"(Port: {jupyter_port})"
|
|
)
|
|
return
|
|
except Exception:
|
|
pass
|
|
await asyncio.sleep(1)
|
|
|
|
log_res = await self.session.run_process(
|
|
f"cat {self.session.workspace_path}/jupyter.log"
|
|
)
|
|
error_details = log_res.stdout if log_res.exit_code == 0 else "无法读取日志"
|
|
raise RuntimeError(
|
|
f"Jupyter 服务动态拉起并等待 API 响应超时。日志内容: {error_details}"
|
|
)
|
|
|
|
async def get_client(self, kernel_name: str) -> JupyterWSClient:
|
|
"""获取并连接到指定内核的 Jupyter WebSocket 客户端"""
|
|
await self.ensure_started()
|
|
if kernel_name in self._clients:
|
|
return self._clients[kernel_name]
|
|
|
|
if not self._http_session:
|
|
raise RuntimeError("HTTP 会话未建立")
|
|
|
|
async with self._http_session.post(
|
|
f"{self.base_url}/api/kernels", json={"name": kernel_name}
|
|
) as resp:
|
|
kernel_id = (await resp.json()).get("id")
|
|
if not kernel_id:
|
|
raise RuntimeError(f"分配 Kernel {kernel_name} 失败")
|
|
|
|
client = JupyterWSClient(
|
|
self._http_session, self.base_url, self.ws_url, kernel_id
|
|
)
|
|
self._clients[kernel_name] = client
|
|
return client
|
|
|
|
async def close(self):
|
|
"""关闭并清理所有 Jupyter WS 连接及 HTTP 会话资源"""
|
|
for client in self._clients.values():
|
|
await client.close()
|
|
self._clients.clear()
|
|
if self._http_session:
|
|
await self._http_session.close()
|
|
self._http_session = None
|
|
self._is_started = False
|
|
|
|
|
|
class BaseCodeExecutor(ABC):
|
|
"""代码执行器抽象基类"""
|
|
|
|
def __init__(self, session: BaseSandboxSession):
|
|
"""初始化代码执行器,绑定沙箱会话"""
|
|
self.session = session
|
|
|
|
@abstractmethod
|
|
async def execute_code(
|
|
self,
|
|
code: str,
|
|
timeout: int = 30,
|
|
injected_code: str | None = None,
|
|
on_output: Callable[[str, bytes], Awaitable[None]] | None = None,
|
|
) -> SandboxExecutionResult:
|
|
"""在沙箱会话中执行指定代码并返回执行结果"""
|
|
pass
|
|
|
|
|
|
class GenericCLIExecutor(BaseCodeExecutor):
|
|
"""模板驱动的通用代码执行器"""
|
|
|
|
def __init__(self, session, profile: "LanguageProfile"):
|
|
"""初始化通用命令行代码执行器,传入语言配置模板"""
|
|
super().__init__(session)
|
|
self.profile = profile
|
|
|
|
async def execute_code(
|
|
self,
|
|
code: str,
|
|
timeout: int = 30,
|
|
injected_code: str | None = None,
|
|
on_output: Callable[[str, bytes], Awaitable[None]] | None = None,
|
|
) -> SandboxExecutionResult:
|
|
"""将代码写入临时文件,必要时编译并执行,返回执行结果"""
|
|
env = None
|
|
if injected_code:
|
|
await self.session.write(
|
|
f"{self.session.workspace_path}/zhenxun_host.py",
|
|
injected_code.encode("utf-8"),
|
|
)
|
|
|
|
script_path = f"{self.session.workspace_path}/main{self.profile.source_ext}"
|
|
await self.session.write(script_path, code.encode("utf-8"))
|
|
|
|
if self.profile.compile_cmd:
|
|
compile_cmd = self.profile.compile_cmd.format(source_file=script_path)
|
|
res = await self.session.run_process(
|
|
compile_cmd, timeout=timeout, env=env, on_output=on_output
|
|
)
|
|
if res.exit_code != 0:
|
|
return res
|
|
|
|
run_cmd = self.profile.run_cmd.format(source_file=script_path)
|
|
return await self.session.run_process(
|
|
run_cmd, timeout=timeout, env=env, on_output=on_output
|
|
)
|
|
|
|
|
|
class CodeExecutorRegistry:
|
|
"""多语言代码执行器动态注册中心"""
|
|
|
|
_executors: ClassVar[
|
|
dict[str, dict[str, dict[bool, Callable[[Any], BaseCodeExecutor]]]]
|
|
] = {}
|
|
_profiles: ClassVar[dict[str, "LanguageProfile"]] = {}
|
|
|
|
_aliases: ClassVar[dict[str, str]] = {
|
|
"py": "python",
|
|
"js": "javascript",
|
|
"sh": "bash",
|
|
"shell": "bash",
|
|
"ts": "typescript",
|
|
}
|
|
|
|
@classmethod
|
|
def _normalize_lang(cls, language: str) -> str:
|
|
"""规范化语言名称,转换为统一小写格式并应用别名"""
|
|
lang_lower = language.lower().strip()
|
|
return cls._aliases.get(lang_lower, lang_lower)
|
|
|
|
@classmethod
|
|
def register(
|
|
cls,
|
|
language: str,
|
|
executor_cls: Callable[[Any], BaseCodeExecutor],
|
|
is_stateful: bool = False,
|
|
scope: str | None = None,
|
|
) -> None:
|
|
"""注册一个语言对应的代码执行器构造工厂"""
|
|
ns = scope if scope is not None else infer_plugin_namespace()
|
|
lang_norm = cls._normalize_lang(language)
|
|
|
|
if ns not in cls._executors:
|
|
cls._executors[ns] = {}
|
|
if lang_norm not in cls._executors[ns]:
|
|
cls._executors[ns][lang_norm] = {}
|
|
|
|
cls._executors[ns][lang_norm][is_stateful] = executor_cls
|
|
logger.debug(
|
|
f"[CodeExecutorRegistry] 成功注册执行器: {ns} -> {lang_norm} "
|
|
f"(Stateful: {is_stateful})"
|
|
)
|
|
|
|
@classmethod
|
|
def register_jupyter_language(
|
|
cls,
|
|
language: str,
|
|
kernel_name: str,
|
|
scope: str | None = None,
|
|
) -> None:
|
|
"""快速注册一个基于 Jupyter 内核的有状态运行语言"""
|
|
cls.register(
|
|
language,
|
|
lambda session: GenericJupyterExecutor(session, kernel_name=kernel_name),
|
|
is_stateful=True,
|
|
scope=scope,
|
|
)
|
|
|
|
@classmethod
|
|
def register_profile(cls, profile: "LanguageProfile") -> None:
|
|
"""注册一个基于命令行的无状态运行语言配置模板"""
|
|
cls._profiles[profile.language.lower()] = profile
|
|
for alias in profile.aliases:
|
|
cls._profiles[alias.lower()] = profile
|
|
logger.debug(f"[CodeExecutorRegistry] 成功注册语言配置模板: {profile.language}")
|
|
|
|
@classmethod
|
|
def create_executor(
|
|
cls, language: str, needs_state: bool, session: Any, namespace: str = "global"
|
|
) -> BaseCodeExecutor:
|
|
"""根据语言和有无状态需求为指定会话创建具体的执行器实例"""
|
|
lang_norm = cls._normalize_lang(language)
|
|
|
|
for target_ns in [namespace, "global"]:
|
|
if target_ns in cls._executors and lang_norm in cls._executors[target_ns]:
|
|
lang_executors = cls._executors[target_ns][lang_norm]
|
|
if needs_state and True in lang_executors:
|
|
return lang_executors[True](session)
|
|
if False in lang_executors:
|
|
return lang_executors[False](session)
|
|
|
|
for ns_dict in cls._executors.values():
|
|
if lang_norm in ns_dict:
|
|
lang_executors = ns_dict[lang_norm]
|
|
if needs_state and True in lang_executors:
|
|
return lang_executors[True](session)
|
|
if False in lang_executors:
|
|
return lang_executors[False](session)
|
|
|
|
if lang_norm in cls._profiles:
|
|
return GenericCLIExecutor(session, cls._profiles[lang_norm])
|
|
|
|
raise ValueError(
|
|
f"当前沙箱生态未提供针对语言 '{language}' 的代码执行器。"
|
|
f"支持的语言有: {cls.get_supported_languages()}"
|
|
)
|
|
|
|
@classmethod
|
|
def get_supported_languages(cls, namespace: str = "global") -> list[str]:
|
|
"""获取所有目前已注册支持的编程语言列表"""
|
|
langs = set(cls._executors.get("global", {}).keys())
|
|
if namespace in cls._executors:
|
|
langs.update(cls._executors[namespace].keys())
|
|
langs.update(cls._profiles.keys())
|
|
return list(langs)
|
|
|
|
|
|
class GenericJupyterExecutor(BaseCodeExecutor):
|
|
"""基于 Jupyter 协议的泛化有状态执行器。支持多语言 REPL。"""
|
|
|
|
def __init__(self, session, kernel_name: str = "python3"):
|
|
"""初始化泛用 Jupyter 有状态执行器,设置默认内核"""
|
|
super().__init__(session)
|
|
self.kernel_name = kernel_name
|
|
self.manager = JupyterServerManager(session)
|
|
|
|
async def execute_code(
|
|
self,
|
|
code: str,
|
|
timeout: int = 30,
|
|
injected_code: str | None = None,
|
|
on_output: Callable[[str, bytes], Awaitable[None]] | None = None,
|
|
) -> SandboxExecutionResult:
|
|
"""利用 Jupyter 内核长连接异步执行代码并返回运行结果"""
|
|
try:
|
|
await self.manager.ensure_started()
|
|
except Exception as e:
|
|
return SandboxExecutionResult(exit_code=-1, error=str(e))
|
|
|
|
if injected_code:
|
|
await self.session.write(
|
|
f"{self.session.workspace_path}/zhenxun_host.py",
|
|
injected_code.encode("utf-8"),
|
|
)
|
|
|
|
try:
|
|
client = await self.manager.get_client(self.kernel_name)
|
|
except Exception as e:
|
|
return SandboxExecutionResult(exit_code=-1, error=str(e))
|
|
|
|
result = await client.execute(code, timeout=timeout, on_output=on_output)
|
|
return result
|
|
|
|
async def close(self):
|
|
"""关闭 Jupyter 客户端及后台运行环境"""
|
|
await self.manager.close()
|
|
|
|
|
|
CodeExecutorRegistry.register_profile(
|
|
LanguageProfile(
|
|
language="python",
|
|
aliases=["py"],
|
|
source_ext=".py",
|
|
run_cmd="python3 {source_file}",
|
|
)
|
|
)
|
|
CodeExecutorRegistry.register_profile(
|
|
LanguageProfile(
|
|
language="bash",
|
|
aliases=["sh", "shell"],
|
|
source_ext=".sh",
|
|
run_cmd="bash {source_file}",
|
|
)
|
|
)
|
|
CodeExecutorRegistry.register_profile(
|
|
LanguageProfile(
|
|
language="javascript",
|
|
aliases=["js", "node"],
|
|
source_ext=".js",
|
|
run_cmd="node {source_file}",
|
|
)
|
|
)
|
|
CodeExecutorRegistry.register_jupyter_language("python", "python3", scope="global")
|
|
|
|
__all__ = [
|
|
"BaseCodeExecutor",
|
|
"CodeExecutorRegistry",
|
|
"GenericCLIExecutor",
|
|
"GenericJupyterExecutor",
|
|
"get_execution_command",
|
|
]
|