"添加前端模板和运行代码模块"

This commit is contained in:
Codex
2026-06-01 13:43:09 +08:00
parent 24045274ad
commit 8092c4b1f8
587 changed files with 99761 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use anyhow::{Context as AnyHowContext, Result};
use log::{info, warn};
use pin_project::pin_project;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::future::Future;
use tokio::{
io,
process::Command,
time::{Duration, Sleep, sleep},
};
use crate::{deno_runner::JsRunner, deno_runner::TsRunner, python_runner::PythonRunner};
///语言脚本,选择对应的语言脚本运行期
#[derive(Debug, Clone)]
pub enum LanguageScript {
Js,
Ts,
Python,
}
impl LanguageScript {
/// 获取文件后缀
pub fn get_file_suffix(&self) -> &str {
match self {
LanguageScript::Js => ".js",
LanguageScript::Ts => ".ts",
LanguageScript::Python => ".py",
}
}
}
///执行结果,包含js/python 执行结果,和打印的log日志
#[derive(Debug, Serialize, Deserialize)]
pub struct CodeScriptExecutionResult {
//js/python 执行结果
pub result: Option<Value>,
//js/python 打印的log日志
pub logs: Vec<String>,
// 是否执行成功,ture:默认值,执行成功
#[serde(skip_serializing)]
pub success: bool,
//如果执行错误的话,错误信息
#[serde(skip_serializing)]
pub error: Option<String>,
}
///运行代码的抽象
#[allow(async_fn_in_trait)]
pub trait RunCode {
///运行代码并传递参数,可选设置超时时间
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult>;
}
/// 代码执行器
pub struct CodeExecutor;
impl CodeExecutor {
/// 执行代码并传递参数,可选设置超时时间
pub async fn execute_with_params(
code: &str,
language: LanguageScript,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
info!(
"开始执行代码... 语言[{language:?}],执行参数: {params:?}"
);
match language {
LanguageScript::Js => {
JsRunner
.run_with_params(code, params, timeout_seconds)
.await
}
LanguageScript::Ts => {
TsRunner
.run_with_params(code, params, timeout_seconds)
.await
}
LanguageScript::Python => {
PythonRunner
.run_with_params(code, params, timeout_seconds)
.await
}
}
}
/// 兼容旧代码的方法,不指定超时时间
pub async fn execute_with_params_compat(
code: &str,
language: LanguageScript,
params: Option<serde_json::Value>,
) -> Result<CodeScriptExecutionResult> {
Self::execute_with_params(code, language, params, None).await
}
/// 解析执行输出
pub async fn parse_execution_output(
stdout: &[u8],
stderr: &[u8],
) -> Result<CodeScriptExecutionResult> {
let stdout_str = String::from_utf8_lossy(stdout).to_string();
let stderr_str = String::from_utf8_lossy(stderr).to_string();
// 尝试从stdout中查找JSON输出
let json_pattern = r#"\{"logs":\s*\[.*\],\s*"result":.*,\s*"error":.*\}"#;
let re = Regex::new(json_pattern)?;
if let Some(captures) = re.find(&stdout_str) {
let json_str = captures.as_str();
let parsed: serde_json::Value =
serde_json::from_str(json_str).context("Failed to parse JSON output")?;
// 从JSON中提取logs、result和error
let logs = parsed["logs"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
// 处理结果尝试解析JSON字符串
let result = if parsed["result"].is_null() {
None
} else if let Some(result_str) = parsed["result"].as_str() {
// 如果是字符串尝试解析为JSON对象
match serde_json::from_str::<Value>(result_str) {
Ok(json_value) => Some(json_value),
Err(_) => Some(Value::String(result_str.to_string())),
}
} else {
// 其他类型(数字、布尔值等)直接使用
Some(parsed["result"].clone())
};
let error = parsed["error"].as_str().map(String::from);
return Ok(CodeScriptExecutionResult {
logs,
result,
success: error.is_none(),
error,
});
}
// 如果没有找到结构化输出,返回原始输出
Ok(CodeScriptExecutionResult {
logs: if !stdout_str.is_empty() {
vec![stdout_str]
} else {
vec![]
},
result: None,
success: false,
error: Some(format!(
"Failed to extract structured output: {stderr_str}"
)),
})
}
}
/// 封装 tokio::command 的执行,并设置堆大小限制
#[allow(dead_code)]
pub struct TokioHeapSize {
//堆大小限制
pub heap_size: u64,
}
impl Default for TokioHeapSize {
fn default() -> Self {
// 堆大小限制为 2GB
Self::new(2048 * 1024 * 1024)
}
}
impl TokioHeapSize {
pub fn new(heap_size: u64) -> Self {
Self { heap_size }
}
/// 启动带有堆内存限制的子进程,返回 tokio::process::Child
#[allow(dead_code)]
pub async fn with_heap_limit(&self, command: &mut Command) {
#[cfg(target_os = "linux")]
{
use libc::{RLIMIT_AS, rlimit, setrlimit};
let heap_size = self.heap_size.clone();
unsafe {
command.pre_exec(move || {
let rlim = rlimit {
rlim_cur: heap_size,
rlim_max: heap_size,
};
if setrlimit(RLIMIT_AS, &rlim) != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
}
// macOS/Windows 下不做限制
}
}
///使用 pin-project 实现一个代码执行器,参数:timeout 超时时间; command 执行命令; 以及内置限制 command命令执行的堆大小限制
#[pin_project]
pub struct CommandExecutor<F> {
#[pin]
timeout: Sleep,
#[pin]
future: F,
}
impl<F> CommandExecutor<F> {
pub fn default(future: F) -> Self {
let timeout = sleep(Duration::from_secs(180));
Self { timeout, future }
}
pub fn with_timeout(future: F, timeout_seconds: u64) -> Self {
let timeout = sleep(Duration::from_secs(timeout_seconds));
Self { timeout, future }
}
#[allow(dead_code)]
pub fn change_timeout(&mut self, timeout: Duration) {
self.timeout = sleep(timeout);
}
}
impl<F: Future> Future for CommandExecutor<F> {
type Output = io::Result<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
//根据 heap_size ,通过setrlimit设置执行 command 的堆大小限制
match this.future.poll(cx) {
Poll::Ready(result) => Poll::Ready(Ok(result)),
Poll::Pending => match this.timeout.poll(cx) {
Poll::Ready(()) => {
warn!("执行命令超时");
Poll::Ready(Err(io::Error::new(
io::ErrorKind::TimedOut,
"future timed out",
)))
}
Poll::Pending => Poll::Pending,
},
}
}
}

View File

@@ -0,0 +1,8 @@
mod code_run_model;
mod tool_params;
pub use code_run_model::{
CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode,
};
#[allow(unused_imports)]
pub use tool_params::RunCodeHttpResult;

View File

@@ -0,0 +1,50 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
///代码运行请求,mcp调用tool工具时传入的参数
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunCodeMessageRequest {
//js运行参数
pub(crate) json_param: HashMap<String, Value>,
//运行的代码
pub code: String,
//前端生成的随机uid,用于查找websocket连接,发送执行过程中的log日志
pub uid: String,
pub engine_type: String,
}
//http返回的结构,data的结构一般是: CodeScriptExecutionResult,就是代码脚本的执行结果
#[derive(Debug, Serialize, Deserialize)]
pub struct RunCodeHttpResult {
//js 结果,json_value
pub data: Value,
// 是否执行成功,ture:默认值,执行成功
pub success: bool,
//如果执行错误的话,错误日志
pub error: Option<String>,
}
/// 代码执行参数
#[derive(Debug, Serialize, Deserialize)]
pub struct CodeExecutionParams {
/// 要执行的代码内容
pub code: String,
/// 代码语言
pub language: String,
/// 是否显示日志
pub show_logs: bool,
}
/// 代码执行结果
#[derive(Debug, Serialize, Deserialize)]
pub struct ToolExecutionResult {
/// 执行日志
pub logs: Vec<String>,
/// 执行结果
pub result: Option<Value>,
/// 执行是否成功
pub success: bool,
/// 错误信息
pub error: Option<String>,
}