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

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,109 @@
---
description:
globs:
alwaysApply: false
---
# 代码执行流程
本项目的核心功能是执行JavaScript、TypeScript和Python代码并捕获执行结果。以下是代码执行的主要流程
## 代码执行模型
项目使用模块化的代码执行模型,定义在[src/model/code_run_model.rs](mdc:src/model/code_run_model.rs)中:
```rust
/// 执行结果,包含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>,
}
/// 代码执行器
pub struct CodeExecutor;
impl CodeExecutor {
/// 执行代码
pub async fn execute(
code: &str,
language: LanguageScript,
) -> Result<CodeScriptExecutionResult> {
Self::execute_with_params(code, language, None).await
}
/// 执行代码并传递参数
pub async fn execute_with_params(
code: &str,
language: LanguageScript,
params: Option<serde_json::Value>,
) -> Result<CodeScriptExecutionResult> {
match language {
LanguageScript::Js => JsRunner::new().run_with_params(code, params).await,
LanguageScript::Ts => TsRunner::new().run_with_params(code, params).await,
LanguageScript::Python => PythonRunner::new().run_with_params(code, params).await,
}
}
}
```
## 语言特定执行器
### JavaScript执行器
JavaScript代码通过[JsRunner](mdc:src/deno_runner/js_runner.rs)执行:
1. 准备JavaScript代码添加日志捕获和handler函数执行逻辑
2. 使用代码哈希值缓存处理过的代码
3. 使用Deno执行代码
4. 解析执行结果
### TypeScript执行器
TypeScript代码通过[TsRunner](mdc:src/deno_runner/ts_runner.rs)执行:
1. 准备TypeScript代码添加日志捕获和handler函数执行逻辑
2. 使用代码哈希值缓存处理过的代码
3. 使用Deno执行代码
4. 解析执行结果
### Python执行器
Python代码通过[PythonRunner](mdc:src/python_runner/python_runner.rs)执行:
1. 分析Python代码的依赖
2. 准备Python代码添加日志捕获和handler/main函数执行逻辑
3. 使用代码哈希值缓存处理过的代码
4. 使用uv在隔离环境中执行Python代码
5. 解析执行结果
## 参数传递
参数通过JSON格式传递给脚本
1. JavaScript/TypeScript参数直接传递给handler函数
2. Python参数通过环境变量`INPUT_JSON`传递然后在脚本中解析并传递给handler或main函数
## 结果处理
执行结果通过[CodeScriptExecutionResult](mdc:src/model/code_run_model.rs)结构体表示,包含:
- result: 执行结果通过handler/main函数返回
- logs: 执行过程中的日志
- success: 执行是否成功
- error: 执行错误(如果有)
## 缓存机制
为了提高性能,项目使用[CodeFileCache](mdc:src/cache)缓存处理过的代码:
1. 计算代码的哈希值
2. 检查是否存在对应的缓存
3. 如果存在,直接使用缓存;否则处理代码并缓存结果

View File

@@ -0,0 +1,72 @@
---
description:
globs:
alwaysApply: false
---
# 项目依赖
本项目使用多个Rust库来实现其功能。以下是主要依赖项及其用途
## Cargo.toml
```toml
[dependencies]
tokio = { version = "1.32", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
env_logger = "0.10"
anyhow = "1.0"
tempfile = "3.8"
regex = "1.9"
clap = { version = "4.4", features = ["derive"] }
thiserror = "1.0"
```
## 主要依赖项
### 异步运行时
- [tokio](https://crates.io/crates/tokio): 异步运行时,用于支持异步/await语法和并发执行
### 序列化/反序列化
- [serde](https://crates.io/crates/serde): 通用序列化/反序列化框架
- [serde_json](https://crates.io/crates/serde_json): JSON序列化/反序列化支持
### 错误处理
- [anyhow](https://crates.io/crates/anyhow): 简化错误处理的库
- [thiserror](https://crates.io/crates/thiserror): 用于定义自定义错误类型
### 日志记录
- [log](https://crates.io/crates/log): 日志记录接口
- [env_logger](https://crates.io/crates/env_logger): 基于环境变量的日志记录器
### 命令行参数解析
- [clap](https://crates.io/crates/clap): 命令行参数解析库,支持派生宏
### 其他工具
- [rmcp](https://crates.io/crates/rmcp): Model Context Protocol (MCP) SDK
- [tempfile](https://crates.io/crates/tempfile): 临时文件创建和管理
- [regex](https://crates.io/crates/regex): 正则表达式支持
## 外部依赖
除了Rust库外本项目还依赖以下外部工具
1. [Deno](https://deno.land/): 现代JavaScript和TypeScript运行时用于执行JavaScript代码
2. [uv](https://github.com/astral-sh/uv): Python包管理器和隔离环境工具用于安全执行Python代码
## 安装外部依赖
```bash
# 安装Deno
curl -fsSL https://deno.land/x/install/install.sh | sh
# 安装uv
curl -fsSL https://astral.sh/uv/install.sh | sh
```

View File

@@ -0,0 +1,101 @@
---
description:
globs:
alwaysApply: false
---
# 错误处理
本项目使用[anyhow](mdc:https:/crates.io/crates/anyhow)和[thiserror](mdc:https:/crates.io/crates/thiserror)库进行错误处理,错误类型定义在[error.rs](mdc:src/error.rs)中。
## 错误类型
```rust
#[derive(Error, Debug)]
pub enum AppError {
#[error("Failed to execute code: {0}")]
ExecutionError(String),
#[error("Failed to parse output: {0}")]
ParseError(String),
#[error("Failed to create temporary file: {0}")]
IoError(#[from] std::io::Error),
#[error("Failed to parse JSON: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Unsupported language: {0}")]
UnsupportedLanguage(String),
#[error("Handler function not found")]
HandlerNotFound,
#[error("MCP error: {0}")]
McpError(String),
#[error("{0}")]
Other(String),
}
pub type AppResult<T> = Result<T, AppError>;
```
## 错误处理流程
1. 在代码执行过程中使用`anyhow::Result`类型传递错误
2. 使用`?`操作符传播错误
3. 使用`context`方法添加错误上下文
4. 在`main`函数中处理所有未捕获的错误
## 示例
```rust
// 读取代码文件
fn get_code(args: &CodeArgs) -> Result<String> {
if let Some(file) = &args.file {
fs::read_to_string(file).context("Failed to read code file")
} else if let Some(code) = &args.code {
Ok(code.clone())
} else {
anyhow::bail!("Either file or code must be provided")
}
}
// 执行JavaScript代码
fn execute_js(code: &str, show_logs: bool) -> Result<ExecutionResult> {
// Prepare the JavaScript code with wrapped handler
let wrapped_code = prepare_js_code(code, show_logs);
// Create a temporary file
let mut temp_file = NamedTempFile::new()?;
write!(temp_file.as_file_mut(), "{}", wrapped_code)?;
// Execute with Deno
let output = Command::new("deno")
.arg("run")
.arg("--allow-all")
.arg(temp_file.path())
.output()
.context("Failed to execute Deno")?;
// Parse the output
parse_execution_output(&output.stdout, &output.stderr)
}
```
## 错误输出
执行结果中的错误通过`ExecutionResult`结构体的`error`字段返回,并在`print_result`函数中输出:
```rust
fn print_result(result: ExecutionResult) {
// ...
if let Some(error) = result.error {
eprintln!("Error: {}", error);
}
}
```

View File

@@ -0,0 +1,138 @@
---
description:
globs:
alwaysApply: false
---
# 示例代码
本项目包含JavaScript和Python的示例代码用于测试代码执行功能。
## JavaScript示例
[examples/test_js.js](mdc:examples/test_js.js)是一个简单的JavaScript示例包含
```javascript
// Sample JavaScript file with handler function
// Log some debug information
console.log("Initializing JavaScript test...");
// Define a simple add function
function add(a, b) {
console.log(`Adding ${a} + ${b}`);
return a + b;
}
// Some processing
const numbers = [1, 2, 3, 4, 5];
console.log("Processing numbers:", numbers);
// Handler function that will be called to get the result
function handler() {
console.log("Handler function called");
// Calculate sum
const sum = numbers.reduce((acc, num) => add(acc, num), 0);
console.log("Final calculation completed");
return `The sum of [${numbers.join(", ")}] is ${sum}`;
}
```
执行这个示例将输出:
```
--- Logs ---
Initializing JavaScript test...
Processing numbers: [1,2,3,4,5]
Handler function called
Adding 0 + 1
Adding 1 + 2
Adding 3 + 3
Adding 6 + 4
Adding 10 + 5
Final calculation completed
------------
Result: The sum of [1, 2, 3, 4, 5] is 15
```
## Python示例
[examples/test_python.py](mdc:examples/test_python.py)是一个简单的Python示例包含
```python
#!/usr/bin/env python3
# Sample Python file with handler function
import time
# Log some debug information
print("Initializing Python test...")
# Define a simple function
def multiply(a, b):
print(f"Multiplying {a} * {b}")
return a * b
# Some processing
numbers = [1, 2, 3, 4, 5]
print(f"Processing numbers: {numbers}")
# Handler function that will be called to get the result
def handler():
print("Handler function called")
# Calculate product
product = 1
for num in numbers:
product = multiply(product, num)
print("Final calculation completed")
return f"The product of {numbers} is {product}"
# This part won't be executed when the code is run through the MCP runner
if __name__ == "__main__":
print("Running directly, calling handler...")
result = handler()
print(f"Result: {result}")
```
执行这个示例将输出:
```
--- Logs ---
Handler function called
Multiplying 1 * 1
Multiplying 1 * 2
Multiplying 2 * 3
Multiplying 6 * 4
Multiplying 24 * 5
Final calculation completed
------------
Result: The product of [1, 2, 3, 4, 5] is 120
```
## 如何创建自己的示例
创建自己的示例代码时,需要遵循以下规则:
1. 必须包含一个名为`handler`的函数,作为执行结果的返回点
2. `handler`函数的返回值将作为执行结果
3. 使用`console.log`JavaScript或`print`Python输出日志
4. 可以定义其他函数和变量,它们将在执行过程中被使用
示例:
```javascript
// JavaScript示例
function handler() {
return "Hello from JavaScript!";
}
```
```python
# Python示例
def handler():
return "Hello from Python!"
```

View File

@@ -0,0 +1,98 @@
---
description:
globs:
alwaysApply: false
---
# 测试脚本示例指南
本项目包含多种语言的测试脚本示例,位于`fixtures`目录下。这些示例展示了如何编写可被本工具执行的JavaScript、TypeScript和Python脚本。
## JavaScript示例
### 基本执行
[test_js.js](mdc:fixtures/test_js.js) - 基本的JavaScript执行示例
- 展示了如何使用`console.log`输出日志
- 定义了`handler`函数作为执行结果的返回点
### 参数传递
[test_js_params.js](mdc:fixtures/test_js_params.js) - 带参数的JavaScript执行示例
- 展示了如何接收和处理传入的参数
- 演示了如何根据参数执行不同的逻辑
### 函数示例
[rfunction_test1.js](mdc:fixtures/rfunction_test1.js) - 函数式JavaScript示例
- 展示了如何定义和使用函数
- 演示了如何处理和返回复杂数据结构
## TypeScript示例
### 基本执行
[test_ts.ts](mdc:fixtures/test_ts.ts) - 基本的TypeScript执行示例
- 展示了TypeScript的类型定义
- 定义了带类型的`handler`函数
### 参数传递
[test_ts_params.ts](mdc:fixtures/test_ts_params.ts) - 带参数的TypeScript执行示例
- 展示了如何定义参数类型
- 演示了TypeScript中的类型安全参数处理
## Python示例
### 基本执行
[test_python.py](mdc:fixtures/test_python.py) - 基本的Python执行示例
- 使用`handler`函数作为执行结果的返回点
- 展示了如何使用Python进行基本计算和日志输出
### 参数传递
[test_python_params.py](mdc:fixtures/test_python_params.py) - 带参数的Python执行示例
- 展示了如何接收和处理传入的参数
- 演示了如何返回包含多个字段的结果
### 类型示例
[test_python_types.py](mdc:fixtures/test_python_types.py) - 不同返回类型的Python示例
- 展示了如何返回不同类型的值(字符串、数字、布尔值、列表、字典等)
- 演示了不同类型在JSON序列化中的处理方式
### 函数示例
[rfunction_python.py](mdc:fixtures/rfunction_python.py) - 函数式Python示例
- 展示了如何定义和使用Python函数
- 演示了如何处理和返回复杂数据结构
## 使用示例
以下是如何使用这些示例脚本的命令:
```bash
# 执行基本JavaScript示例
cargo run -- --show-logs js -f fixtures/test_js.js
# 执行带参数的JavaScript示例
cargo run -- --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}'
# 执行基本TypeScript示例
cargo run -- --show-logs ts -f fixtures/test_ts.ts
# 执行带参数的TypeScript示例
cargo run -- --show-logs ts -f fixtures/test_ts_params.ts -p '{"a":10, "b":20, "name":"User"}'
# 执行基本Python示例
cargo run -- --show-logs python -f fixtures/test_python.py
# 执行带参数的Python示例
cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
# 执行不同类型的Python示例
cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"string"}'
cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"number"}'
cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"list"}'
cargo run -- --show-logs python -f fixtures/test_python_types.py -p '{"type":"dict"}'
```

View File

@@ -0,0 +1,146 @@
---
description:
globs:
alwaysApply: false
---
# MCP SDK集成
本项目支持Model Context Protocol (MCP) SDK集成允许通过标准化协议执行代码。MCP相关功能主要在[mcp.rs](mdc:src/mcp.rs)中实现。
## 核心数据结构
```rust
#[derive(Serialize, Deserialize, Debug)]
pub struct CodeExecutionParams {
pub code: String,
pub language: String,
pub show_logs: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CodeExecutionResult {
pub logs: Vec<String>,
pub result: Option<String>,
pub error: Option<String>,
}
```
## JavaScript执行
MCP中的JavaScript执行使用以下包装代码
```javascript
// MCP SDK integration for JavaScript
// Note: MCP SDK is not required for basic functionality
// Log capture setup
const logs = [];
const originalConsoleLog = console.log;
console.log = function() {
const message = Array.from(arguments).map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
logs.push(message);
if (show_logs) {
originalConsoleLog.apply(console, arguments);
}
};
async function mcpExecute() {
try {
// User code here
// Execute handler
let result = null;
if (typeof handler === 'function') {
result = await handler();
}
return {
logs: logs,
result: result !== undefined ? String(result) : null,
error: null
};
} catch (error) {
return {
logs: logs,
result: null,
error: error.toString()
};
}
}
// Execute and return results
mcpExecute().then(result => {
originalConsoleLog(JSON.stringify(result));
});
```
## Python执行
MCP中的Python执行使用以下包装代码
```python
# MCP SDK integration for Python
# Note: MCP SDK is not required for basic functionality
import sys
import json
import io
from contextlib import redirect_stdout
# Store logs
logs = []
# Create custom stdout to capture logs
class LogCapture(io.StringIO):
def write(self, text):
if text.strip():
logs.append(text.rstrip())
if show_logs:
sys.__stdout__.write(text)
def mcp_execute():
try:
// User code here
// Execute handler function and capture result
result = None
with redirect_stdout(LogCapture()):
if 'handler' in globals() and callable(handler):
result = handler()
return {
"logs": logs,
"result": str(result) if result is not None else None,
"error": None
}
except Exception as e:
return {
"logs": logs,
"result": None,
"error": str(e)
}
# Execute and print results
result = mcp_execute()
print(json.dumps(result))
```
## 执行流程
MCP执行流程由[execute_with_mcp](mdc:src/mcp.rs#L138)函数处理:
1. 创建临时文件并写入包装后的代码
2. 根据语言类型选择执行命令Deno或uv
3. 执行代码并捕获输出
4. 解析JSON格式的执行结果
5. 如果无法解析JSON则返回原始输出
## 使用方法
在命令行中使用`--use-mcp`参数启用MCP SDK集成
```bash
cargo run -- --use-mcp js -c "function handler() { return 'Hello from MCP JS'; }"
cargo run -- --use-mcp python -f examples/test_python.py
```

View File

@@ -0,0 +1,50 @@
---
description:
globs:
alwaysApply: false
---
# 项目概述
这是一个使用Rust开发的命令行工具用于执行JavaScript、TypeScript和Python代码。该工具支持以下功能
1. 执行JavaScript代码使用Deno运行时
2. 执行TypeScript代码使用Deno运行时
3. 执行Python代码使用uv提供隔离环境
4. 捕获并区分脚本中的日志输出和执行结果
5. 支持JavaScript/TypeScript的handler函数和Python的handler/main函数作为执行结果的返回点
6. 通过环境变量传递参数给脚本
7. 可以通过参数控制是否显示日志输出
8. 支持MCP SDK集成
## 主要文件
- [src/main.rs](mdc:src/main.rs) - 应用程序入口点,处理命令行参数和执行流程
- [src/lib.rs](mdc:src/lib.rs) - 库入口点,导出模块和类型
- [src/model/code_run_model.rs](mdc:src/model/code_run_model.rs) - 代码执行模型和结果处理
- [src/deno_runner/js_runner.rs](mdc:src/deno_runner/js_runner.rs) - JavaScript代码执行器
- [src/deno_runner/ts_runner.rs](mdc:src/deno_runner/ts_runner.rs) - TypeScript代码执行器
- [src/python_runner/python_runner.rs](mdc:src/python_runner/python_runner.rs) - Python代码执行器
- [src/cache/](mdc:src/cache) - 代码缓存管理
- [fixtures/](mdc:fixtures) - 测试脚本示例
## 使用示例
```bash
# 执行JavaScript文件
cargo run -- --show-logs js -f fixtures/test_js.js
# 执行TypeScript文件
cargo run -- --show-logs ts -f fixtures/test_ts.ts
# 执行Python文件
cargo run -- --show-logs python -f fixtures/test_python.py
# 带参数执行Python文件
cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
# 直接执行JavaScript代码
cargo run -- js -c "function handler(input) { return 'Hello from JS: ' + input.name; }" -p '{"name":"User"}'
# 直接执行Python代码
cargo run -- python -c "def handler(args): return 'Hello from Python: ' + args.get('name', 'Guest')" -p '{"name":"User"}'
```

View File

@@ -0,0 +1,143 @@
---
description:
globs:
alwaysApply: false
---
# Python代码执行
本项目使用`uv`在隔离环境中执行Python代码提供安全可靠的执行环境。以下是Python代码执行的详细流程
## Python运行器
Python代码通过[PythonRunner](mdc:src/python_runner/python_runner.rs)执行,实现了`RunCode`特性:
```rust
pub struct PythonRunner;
impl RunCode for PythonRunner {
async fn run(&self, code: &str) -> Result<CodeScriptExecutionResult> {
// 实现代码...
}
async fn run_with_params(&self, code: &str, params: Option<serde_json::Value>) -> Result<CodeScriptExecutionResult> {
// 实现代码...
}
}
```
## 依赖解析
Python代码的依赖通过[python_dependencies.rs](mdc:src/python_runner/dependencies/python_dependencies.rs)解析:
1. 使用Pest解析器分析Python代码中的import语句
2. 支持常规import语句和importlib动态导入
3. 提取所有导入的模块名称
```rust
pub fn parse_import(python_code: &str) -> Result<Vec<String>> {
// 使用Pest解析器分析Python代码
let pairs = ImportParser::parse(Rule::file, &input)?;
// 提取模块名称
let mut imported_modules = Vec::new();
// 处理各种import语句...
Ok(imported_modules)
}
```
## 代码准备
Python代码在执行前需要进行包装添加日志捕获和函数执行逻辑
```rust
fn prepare_python_code(&self, code: &str, show_logs: bool) -> String {
// 创建包装代码,包括:
// 1. 日志捕获
// 2. 参数解析
// 3. 执行handler或main函数
// 4. 结果格式化
}
```
包装代码主要功能:
1. 捕获标准输出作为日志
2. 从环境变量`INPUT_JSON`解析参数
3. 优先执行`handler`函数,如果不存在则执行`main`函数
4. 将执行结果和日志格式化为JSON
## 参数传递
参数通过环境变量`INPUT_JSON`传递给Python脚本
```rust
// 将参数序列化为JSON字符串
let params_json = match params {
Some(p) => serde_json::to_string(&p)?,
None => "{}".to_string(),
};
// 通过环境变量传递参数
execute_command.env("INPUT_JSON", &params_json);
```
在Python脚本中参数通过以下方式获取
```python
# 从环境变量获取输入参数
args = {}
try:
input_json = os.environ.get('INPUT_JSON')
if input_json:
args = json.loads(input_json)
except Exception as e:
print(f"解析输入参数失败: {e}")
```
## 执行流程
1. 计算代码哈希值,检查缓存
2. 如果缓存存在,直接使用;否则:
- 分析代码依赖
- 准备包装后的代码
- 保存到缓存
3. 使用`uv`命令在隔离环境中执行代码:
```rust
let mut execute_command = Command::new("uv");
execute_command
.arg("run")
.arg("-s") // 明确指定作为脚本运行
.arg("--isolated") // 在隔离环境中运行
.arg("-p")
.arg("python3") // 指定Python解释器
.env("INPUT_JSON", &params_json) // 通过环境变量传递参数
.arg(&temp_path)
```
4. 解析执行结果,提取日志和返回值
## 结果处理
执行结果通过JSON格式返回包含三个字段
- `logs`: 执行过程中的日志数组
- `result`: 执行结果(可能是字符串、数字、布尔值、对象等)
- `error`: 执行错误(如果有)
```json
{
"logs": ["日志1", "日志2", ...],
"result": "执行结果",
"error": null
}
```
## 支持的返回类型
Python脚本可以返回多种类型的值
1. 字符串
2. 数字
3. 布尔值
4. 列表/数组
5. 字典/对象
6. None/null
所有复杂类型列表、字典都会被序列化为JSON字符串。

View File

@@ -0,0 +1,43 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
{
"name": "Rust",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
// "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
// 使用你自定义的Dockerfile
"build": {
"dockerfile": "../Dockerfile",
"context": ".."
},
"features": {
"ghcr.io/devcontainers/features/go:1": {},
"ghcr.io/devcontainers-community/features/deno:1": {},
"ghcr.io/va-h/devcontainers-features/uv:1": {},
"ghcr.io/devcontainers-extra/features/curl-apt-get:1": {},
"ghcr.io/devcontainers-extra/features/wget-apt-get:1": {}
}
// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
// "mounts": [
// {
// "source": "devcontainer-cargo-cache-${devcontainerId}",
// "target": "/usr/local/cargo",
// "type": "volume"
// }
// ]
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "rustc --version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

6
qiming-run_code_rmcp/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/target
.idea
.vscode
.DS_Store
temp/

View File

@@ -0,0 +1,108 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Rust-based MCP (Model Context Protocol) server that executes JavaScript, TypeScript, and Python code in isolated environments. The project provides both a command-line tool and a library for code execution with proper sandboxing and result capture.
## Key Commands
### Building and Running
```bash
# Build the project
cargo build
# Run with cargo
cargo run -- js -f fixtures/test_js.js
cargo run -- ts -f fixtures/test_ts.ts
cargo run -- python -f fixtures/test_python.py
# Install as binary
cargo install --path . --bin script_runner
# Run tests
cargo test
# Clear cache
cargo run -- clear-cache all
```
### Common Development Commands
```bash
# Execute JavaScript with logs
cargo run -- --show-logs js -f fixtures/test_js.js
# Execute with parameters
cargo run -- --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
# Execute inline code
cargo run -- js -c "function handler(input) { return 'Hello: ' + input.name; }" -p '{"name":"User"}'
# Use MCP integration
cargo run -- --use-mcp python -f fixtures/test_python.py
```
## Architecture
### Core Components
- **src/main.rs** - CLI entry point with command parsing using clap
- **src/lib.rs** - Library interface exporting public APIs
- **src/model/code_run_model.rs** - Core execution models and `CodeExecutor` struct
- **src/cache/** - Code caching system using blake3 hashing
- **src/mcp/** - MCP protocol server implementation
### Language Runners
- **src/deno_runner/js_runner.rs** - JavaScript execution via Deno
- **src/deno_runner/ts_runner.rs** - TypeScript execution via Deno
- **src/python_runner/python_runner.rs** - Python execution via uv with dependency analysis
- **src/python_runner/dependencies/** - Python dependency parsing and management
### Execution Flow
1. Code is processed and cached using blake3 hashing
2. Language-specific runner prepares the execution environment
3. Code is wrapped with logging capture and handler function execution
4. Results are captured and returned as `CodeScriptExecutionResult`
## Important Patterns
### Handler Function Convention
Scripts must expose a `handler` function (JS/TS) or `handler`/`main` function (Python) that returns the final result:
```javascript
function handler(input) {
return "Result: " + input.param;
}
```
### Parameter Passing
- JS/TS: Parameters passed directly to handler function
- Python: Parameters passed via `INPUT_JSON` environment variable
### Error Handling
- Uses `anyhow::Result` for error propagation
- Custom `app_error` module for application-specific errors
- Execution results include error information in `CodeScriptExecutionResult`
### Dependencies
- **Deno** for JavaScript/TypeScript execution
- **uv** for Python environment isolation
- **rmcp** crate for MCP protocol implementation
- **tokio** for async runtime
- **serde** for JSON serialization
## Development Notes
### Testing
- Test fixtures in `fixtures/` directory for all supported languages
- Integration tests in `src/tests/`
- Use `--show-logs` flag during development to see execution output
### Caching
- Code processing results are cached based on content hash
- Use `--clear-cache` to invalidate cache when debugging
- Cache files stored in system temp directory
### MCP Integration
- Can run as standalone MCP server via `script_runner` binary
- Supports SSE and HTTP transports
- Tools: `run_javascript`, `run_typescript`, `run_python`

1429
qiming-run_code_rmcp/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
[package]
name = "run_code_rmcp"
description = "云函数服务,执行JS/TS/Python语言代码,脚本必须有约定的函数名称(handler/main),会调用约定的函数名称结果和日志返回."
version = "0.0.35"
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/nuwax-ai/run_code_rmcp"
homepage = "https://nuwax.com/"
readme = "README.md"
default-run = "run_code_rmcp"
[features]
default = []
mcp = ["dep:rmcp"]
[dependencies]
tokio = { version = "1.47", features = [
"fs",
"macros",
"net",
"rt",
"rt-multi-thread",
"signal",
"io-util",
"process",
"time",
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
env_logger = "0.11"
anyhow = "1.0"
rmcp = { version = "0.12", features = [
"server",
"client",
"transport-io",
"transport-async-rw",
"macros",
], optional = true }
tempfile = "3.21"
regex = "1.11"
clap = { version = "4.5", features = ["derive"] }
thiserror = "2.0"
pest = { version = "2.8", features = ["pretty-print"] }
pest_derive = "2.8"
blake3 = "1.8"
pin-project = "1.1"
libc = "0.2"
once_cell = "1.21"
schemars = "1.0"
async-trait = "0.1"
[[bin]]
name = "script_runner"
path = "src/script_runner.rs"
required-features = ["mcp"]

View File

@@ -0,0 +1,80 @@
FROM rust:1.85
# 设置环境变量
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai
ENV PATH="/root/.deno/bin:/root/.cargo/bin:${PATH}"
# 安装基础依赖
RUN apt-get update && apt-get install -y \
python3.11 \
python3.11-venv \
python3.11-distutils \
python3.11-dev \
python3-pip \
curl \
vim \
net-tools \
gettext-base \
telnet \
wget \
&& rm -rf /var/lib/apt/lists/* \
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone
# 安装 Node.js
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g npm@latest
# 安装 go
# 设置 Go 环境变量
ENV GOPATH=/root/go
ENV PATH=$PATH:/usr/local/go/bin:/root/go/bin
# 创建 Go 工作目录
RUN mkdir -p $GOPATH/bin $GOPATH/src $GOPATH/pkg
# 安装 Go 1.24(根据架构自动选择下载链接)
ARG TARGETARCH
RUN echo "Target Architecture: $TARGETARCH" \
&& if [ "$TARGETARCH" = "amd64" ]; then \
GO_URL="https://go.dev/dl/go1.24.3.linux-amd64.tar.gz"; \
elif [ "$TARGETARCH" = "arm64" ]; then \
GO_URL="https://go.dev/dl/go1.24.3.linux-arm64.tar.gz"; \
else \
echo "Unsupported architecture: $TARGETARCH"; \
exit 1; \
fi \
&& echo "Downloading Go from: $GO_URL" \
&& curl -fsSL "$GO_URL" -o go.tar.gz \
&& tar -C /usr/local -xzf go.tar.gz \
&& rm go.tar.gz \
&& ln -s /usr/local/go/bin/go /usr/local/bin/go \
&& ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt
RUN go version
# 测试mcp用 go-mcp-mysql@latest
RUN go install -v github.com/Zhwt/go-mcp-mysql@latest
# 安装 Deno
RUN curl -fsSL https://deno.land/install.sh | sh
# 安装 uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
# 添加uv到PATH
ENV PATH="/root/.local/bin:${PATH}"
# 创建虚拟环境
RUN uv venv
# 设置工作目录
WORKDIR /app
# 暴露端口,实际端口一般是8080
EXPOSE 8080

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but not
limited to compiled object code, generated documentation, and
conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024-2025 Nuwax AI and contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but not
limited to compiled object code, generated documentation, and
conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024-2025 Nuwax AI and contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,16 @@
Copyright 2024-2025 Nuwax AI
This product includes software developed by Nuwax AI and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,335 @@
# MCP Code Execution Tool
A Rust-based tool that provides both a command-line interface and an MCP (Model Context Protocol) server for executing JavaScript, TypeScript, and Python code in isolated environments.
**Repository:** <https://github.com/nuwax-ai/run_code_rmcp>
English | [中文](./README_zh.md)
## Features
- Execute JavaScript, TypeScript, and Python code in isolated environments
- **JavaScript/TypeScript**: Powered by Deno runtime
- **Python**: Powered by `uv` with automatic dependency management
- Automatic code caching using blake3 hashing for faster repeated executions
- Support for ESM and CommonJS modules in JavaScript
- Automatic Python dependency parsing and installation
- Separate log capture and execution result handling
- Available as both CLI tool (`run_code_rmcp`) and MCP server (`script_runner`)
## Installation
### Install CLI Tool
```bash
# Clone the repository
git clone https://github.com/nuwax-ai/run_code_rmcp.git
cd run_code_rmcp
# Install the CLI tool (run_code_rmcp)
cargo install --path .
# Or install both CLI tool and MCP server
cargo install --path . --features mcp
cargo install --path . --bin script_runner --features mcp
```
### System Requirements
- **Rust**: 1.85 or higher (for building)
- **Deno**: Required for JavaScript/TypeScript execution
- **uv**: Required for Python execution and dependency management
Install Deno:
```bash
curl -fsSL https://deno.land/install.sh | sh
```
Install uv:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
## Usage
### CLI Tool: run_code_rmcp
The `run_code_rmcp` command allows you to execute code directly from the command line.
#### Basic Syntax
```bash
run_code_rmcp [OPTIONS] <COMMAND>
```
**Options:**
- `--show-logs`: Display execution logs
- `--clear-cache`: Clear cache before execution
- `--use-mcp`: Use MCP SDK integration (requires mcp feature)
**Commands:**
- `js`: Execute JavaScript code
- `ts`: Execute TypeScript code
- `python`: Execute Python code
- `clear-cache`: Clear cached files
#### Execute JavaScript/TypeScript
```bash
# Execute a JavaScript file
run_code_rmcp --show-logs js -f fixtures/test_js.js
# Execute with parameters
run_code_rmcp --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}'
# Execute TypeScript
run_code_rmcp --show-logs ts -f fixtures/test_ts.ts
# Execute inline code
run_code_rmcp js -c "function handler() { return 'Hello!'; }"
```
#### Execute Python
```bash
# Execute a Python file
run_code_rmcp --show-logs python -f fixtures/test_python.py
# Execute with parameters
run_code_rmcp --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
# Execute inline code
run_code_rmcp python -c "def handler(): return 'Hello from Python!'"
```
#### Cache Management
```bash
# Clear all cache
run_code_rmcp clear-cache --language all
# Clear specific language cache
run_code_rmcp clear-cache --language python
run_code_rmcp clear-cache --language js
run_code_rmcp clear-cache --language ts
# Clear cache before execution
run_code_rmcp --clear-cache js -f fixtures/test_js.js
```
### Using as a Rust Library
```toml
[dependencies]
run_code_rmcp = { git = "https://github.com/nuwax-ai/run_code_rmcp.git" }
```
```rust
use run_code_rmcp::{CodeExecutor, LanguageScript};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Execute JavaScript
let js_result = CodeExecutor::execute_with_params_compat(
"function handler() { return {success: true}; }",
LanguageScript::Js,
None
).await?;
// Execute Python
let py_result = CodeExecutor::execute_with_params_compat(
"def handler(): return {'success': True}",
LanguageScript::Python,
None
).await?;
Ok(())
}
```
### MCP Server: script_runner
The `script_runner` is an MCP server that provides tools for AI assistants.
#### Starting the Server
```bash
# Start MCP server
script_runner
# Start with verbose logging
script_runner --verbose
```
The server communicates via stdio and waits for MCP protocol JSON requests.
#### Available Tools
The MCP server provides the following tools:
1. **run_javascript** - Execute JavaScript code
2. **run_typescript** - Execute TypeScript code
3. **run_python** - Execute Python code
Each tool accepts:
- `code` (string): The code to execute
- `params` (object, optional): Parameters to pass to the handler function
#### Integration with AI Assistants
Add to your MCP client configuration (e.g., Claude Desktop):
```json
{
"name": "script-runner",
"command": "script_runner",
"transport": "stdio"
}
```
## Code Structure
### Handler Functions
Your scripts should define a handler function that returns the result.
#### JavaScript/TypeScript
Supports both `main` and `handler` functions (main takes priority):
```javascript
// This function takes priority
function main(input) {
console.log("Processing with main:", input);
return { result: "Hello from main!" };
}
// This function is used if main is not defined
function handler(input) {
console.log("Processing with handler:", input);
return { result: "Hello from handler!" };
}
```
ESM modules are also supported:
```javascript
import { serve } from "https://deno.land/std/http/server.ts";
function handler(input) {
return { message: "ESM works!" };
}
```
#### Python
Supports both `handler` and `main` functions (handler takes priority):
```python
import pandas as pd
# This function takes priority
def handler(args):
print(f"Processing: {args}")
data = pd.DataFrame({"a": [1, 2, 3]})
return {"result": data.to_dict()}
# This function is used if handler is not defined
def main(args):
return {"result": "Using main instead"}
```
### Parameter Passing
Parameters are passed to your handler function:
```bash
run_code_rmcp js -f script.js -p '{"name": "User", "count": 42}'
```
In your code:
```javascript
function handler(input) {
// input = { name: "User", count: 42 }
return `Hello ${input.name}, count: ${input.count}`;
}
```
### Python Dependencies
Python dependencies are automatically detected and installed:
```python
import requests
import pandas as pd
def handler(args):
# Dependencies are automatically installed via uv
response = requests.get("https://api.example.com")
data = pd.DataFrame(response.json())
return data.to_dict()
```
### Log Capture
All console output is captured separately from the return value:
```javascript
console.log("This goes to logs");
console.log("So does this");
function handler() {
return "This is the result";
}
```
Result:
```json
{
"logs": ["This goes to logs", "So does this"],
"result": "This is the result",
"error": null
}
```
## Development
```bash
# Build
cargo build
# Run with cargo
cargo run -- --show-logs js -f fixtures/test_js.js
# Run tests
cargo test
# Build with MCP feature
cargo build --features mcp
# Run MCP server with cargo
cargo run --bin script_runner --features mcp
```
## Environment Pre-warming
The project includes a warm-up function to cache common dependencies:
```rust
use run_code_rmcp::warm_up_all_envs;
#[tokio::main]
async fn main() -> Result<()> {
// Pre-install common Python and JS/TS dependencies
warm_up_all_envs(None, None, None, None).await?;
Ok(())
}
```
## TODO
- [ ] TTS (Text-to-Speech) functionality - Currently has known issues
## License
This project is licensed under the Apache License 2.0. See `LICENSE` and `NOTICE` files in the root directory for details.

View File

@@ -0,0 +1,335 @@
# MCP 代码执行工具
一个基于 Rust 的工具,提供命令行界面和 MCPModel Context Protocol服务器用于在隔离环境中执行 JavaScript、TypeScript 和 Python 代码。
**项目仓库:** <https://github.com/nuwax-ai/run_code_rmcp>
[English](./README.md) | 中文
## 功能特点
- 在隔离环境中执行 JavaScript、TypeScript 和 Python 代码
- **JavaScript/TypeScript**:由 Deno 运行时驱动
- **Python**:由 `uv` 驱动,支持自动依赖管理
- 使用 blake3 哈希自动缓存代码,加快重复执行速度
- 支持 JavaScript 的 ESM 和 CommonJS 模块
- 自动解析和安装 Python 依赖
- 独立的日志捕获和执行结果处理
- 提供命令行工具(`run_code_rmcp`)和 MCP 服务器(`script_runner`)两种使用方式
## 安装方法
### 安装命令行工具
```bash
# 克隆仓库
git clone https://github.com/nuwax-ai/run_code_rmcp.git
cd run_code_rmcp
# 安装命令行工具 (run_code_rmcp)
cargo install --path .
# 或同时安装命令行工具和 MCP 服务器
cargo install --path . --features mcp
cargo install --path . --bin script_runner --features mcp
```
### 系统要求
- **Rust**1.85 或更高版本(用于构建)
- **Deno**JavaScript/TypeScript 执行必需
- **uv**Python 执行和依赖管理必需
安装 Deno
```bash
curl -fsSL https://deno.land/install.sh | sh
```
安装 uv
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
## 使用方法
### 命令行工具run_code_rmcp
`run_code_rmcp` 命令允许直接从命令行执行代码。
#### 基本语法
```bash
run_code_rmcp [选项] <命令>
```
**选项:**
- `--show-logs`:显示执行日志
- `--clear-cache`:执行前清除缓存
- `--use-mcp`:使用 MCP SDK 集成(需要 mcp feature
**命令:**
- `js`:执行 JavaScript 代码
- `ts`:执行 TypeScript 代码
- `python`:执行 Python 代码
- `clear-cache`:清除缓存文件
#### 执行 JavaScript/TypeScript
```bash
# 执行 JavaScript 文件
run_code_rmcp --show-logs js -f fixtures/test_js.js
# 带参数执行
run_code_rmcp --show-logs js -f fixtures/test_js_params.js -p '{"name":"User"}'
# 执行 TypeScript
run_code_rmcp --show-logs ts -f fixtures/test_ts.ts
# 执行内联代码
run_code_rmcp js -c "function handler() { return 'Hello!'; }"
```
#### 执行 Python
```bash
# 执行 Python 文件
run_code_rmcp --show-logs python -f fixtures/test_python.py
# 带参数执行
run_code_rmcp --show-logs python -f fixtures/test_python_params.py -p '{"a":10, "b":20}'
# 执行内联代码
run_code_rmcp python -c "def handler(): return 'Hello from Python!'"
```
#### 缓存管理
```bash
# 清除所有缓存
run_code_rmcp clear-cache --language all
# 清除特定语言的缓存
run_code_rmcp clear-cache --language python
run_code_rmcp clear-cache --language js
run_code_rmcp clear-cache --language ts
# 执行前清除缓存
run_code_rmcp --clear-cache js -f fixtures/test_js.js
```
### 作为 Rust 库使用
```toml
[dependencies]
run_code_rmcp = { git = "https://github.com/nuwax-ai/run_code_rmcp.git" }
```
```rust
use run_code_rmcp::{CodeExecutor, LanguageScript};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 执行 JavaScript
let js_result = CodeExecutor::execute_with_params_compat(
"function handler() { return {success: true}; }",
LanguageScript::Js,
None
).await?;
// 执行 Python
let py_result = CodeExecutor::execute_with_params_compat(
"def handler(): return {'success': True}",
LanguageScript::Python,
None
).await?;
Ok(())
}
```
### MCP 服务器script_runner
`script_runner` 是一个 MCP 服务器,为 AI 助手提供代码执行工具。
#### 启动服务器
```bash
# 启动 MCP 服务器
script_runner
# 启用详细日志
script_runner --verbose
```
服务器通过 stdio 通信,等待 MCP 协议的 JSON 请求。
#### 可用工具
MCP 服务器提供以下工具:
1. **run_javascript** - 执行 JavaScript 代码
2. **run_typescript** - 执行 TypeScript 代码
3. **run_python** - 执行 Python 代码
每个工具接受:
- `code`(字符串):要执行的代码
- `params`(对象,可选):传递给 handler 函数的参数
#### 与 AI 助手集成
添加到你的 MCP 客户端配置(如 Claude Desktop
```json
{
"name": "script-runner",
"command": "script_runner",
"transport": "stdio"
}
```
## 代码结构
### Handler 函数
脚本应定义一个返回结果的 handler 函数。
#### JavaScript/TypeScript
支持 `main``handler` 函数main 优先):
```javascript
// 这个函数优先级更高
function main(input) {
console.log("使用 main 处理:", input);
return { result: "Hello from main!" };
}
// 如果没有定义 main则使用这个函数
function handler(input) {
console.log("使用 handler 处理:", input);
return { result: "Hello from handler!" };
}
```
也支持 ESM 模块:
```javascript
import { serve } from "https://deno.land/std/http/server.ts";
function handler(input) {
return { message: "ESM works!" };
}
```
#### Python
支持 `handler``main` 函数handler 优先):
```python
import pandas as pd
# 这个函数优先级更高
def handler(args):
print(f"处理中: {args}")
data = pd.DataFrame({"a": [1, 2, 3]})
return {"result": data.to_dict()}
# 如果没有定义 handler则使用这个函数
def main(args):
return {"result": "使用 main 代替"}
```
### 参数传递
参数会传递给 handler 函数:
```bash
run_code_rmcp js -f script.js -p '{"name": "User", "count": 42}'
```
在代码中:
```javascript
function handler(input) {
// input = { name: "User", count: 42 }
return `Hello ${input.name}, count: ${input.count}`;
}
```
### Python 依赖
Python 依赖会被自动检测并安装:
```python
import requests
import pandas as pd
def handler(args):
# 依赖会通过 uv 自动安装
response = requests.get("https://api.example.com")
data = pd.DataFrame(response.json())
return data.to_dict()
```
### 日志捕获
所有控制台输出都会与返回值分开捕获:
```javascript
console.log("这是日志");
console.log("这也是日志");
function handler() {
return "这是结果";
}
```
结果:
```json
{
"logs": ["这是日志", "这也是日志"],
"result": "这是结果",
"error": null
}
```
## 开发
```bash
# 构建
cargo build
# 使用 cargo 运行
cargo run -- --show-logs js -f fixtures/test_js.js
# 运行测试
cargo test
# 使用 mcp feature 构建
cargo build --features mcp
# 使用 cargo 运行 MCP 服务器
cargo run --bin script_runner --features mcp
```
## 环境预热
项目包含预热函数来缓存常用依赖:
```rust
use run_code_rmcp::warm_up_all_envs;
#[tokio::main]
async fn main() -> Result<()> {
// 预安装常用的 Python 和 JS/TS 依赖
warm_up_all_envs(None, None, None, None).await?;
Ok(())
}
```
## 待办事项
- [ ] TTS文本转语音功能 - 目前存在已知问题
## 许可证
本项目采用 Apache License 2.0 发布。详见根目录的 `LICENSE``NOTICE` 文件。

View File

@@ -0,0 +1,6 @@
{
"name": "Script Runner",
"description": "执行JavaScript、TypeScript和Python代码",
"path": "/path/to/script_runner",
"transport": "stdio"
}

View File

@@ -0,0 +1,151 @@
// MCP客户端示例用于与script_runner交互
// 使用方法node mcp_client.js
const { spawn } = require('node:child_process');
const readline = require('node:readline');
// 启动script_runner进程
const scriptRunner = spawn('script_runner', ['--verbose']);
// 创建读取标准输入的接口
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 处理script_runner的输出
scriptRunner.stdout.on('data', (data) => {
console.log(`接收到MCP服务器响应: ${data}`);
try {
const response = JSON.parse(data);
console.log('解析后的响应:', JSON.stringify(response, null, 2));
} catch (e) {
console.log('无法解析为JSON显示原始输出');
}
});
// 处理script_runner的错误
scriptRunner.stderr.on('data', (data) => {
console.error(`MCP服务器错误: ${data}`);
});
// 处理script_runner进程结束
scriptRunner.on('close', (code) => {
console.log(`MCP服务器进程退出退出码: ${code}`);
rl.close();
});
// 初始化MCP连接
const initializeRequest = {
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
client_info: {
name: 'mcp_client_example'
}
}
};
// 发送初始化请求
console.log('发送初始化请求...');
scriptRunner.stdin.write(JSON.stringify(initializeRequest) + '\n');
// 等待初始化响应后,发送工具列表请求
setTimeout(() => {
const listToolsRequest = {
jsonrpc: '2.0',
id: 2,
method: 'listTools'
};
console.log('发送工具列表请求...');
scriptRunner.stdin.write(JSON.stringify(listToolsRequest) + '\n');
}, 1000);
// 等待工具列表响应后执行JavaScript代码
setTimeout(() => {
const callToolRequest = {
jsonrpc: '2.0',
id: 3,
method: 'callTool',
params: {
name: 'run_javascript',
arguments: {
code: 'console.log("Hello from JavaScript!"); return { message: "Hello, World!", value: 42 };'
}
}
};
console.log('发送JavaScript代码执行请求...');
scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n');
}, 2000);
// 等待JavaScript执行响应后执行TypeScript代码
setTimeout(() => {
const callToolRequest = {
jsonrpc: '2.0',
id: 4,
method: 'callTool',
params: {
name: 'run_typescript',
arguments: {
code: 'const greeting: string = "Hello from TypeScript!"; console.log(greeting); return { message: greeting, timestamp: new Date().toISOString() };'
}
}
};
console.log('发送TypeScript代码执行请求...');
scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n');
}, 3000);
// 等待TypeScript执行响应后执行Python代码
setTimeout(() => {
const callToolRequest = {
jsonrpc: '2.0',
id: 5,
method: 'callTool',
params: {
name: 'run_python',
arguments: {
code: 'import math\nprint("Hello from Python!")\nresult = math.sqrt(16)\nprint(f"Square root of 16 is {result}")\n{"message": "Hello from Python", "result": result}'
}
}
};
console.log('发送Python代码执行请求...');
scriptRunner.stdin.write(JSON.stringify(callToolRequest) + '\n');
}, 4000);
// 处理用户输入,允许用户发送自定义请求
rl.on('line', (input) => {
if (input.toLowerCase() === 'exit') {
console.log('退出程序...');
scriptRunner.kill();
rl.close();
return;
}
try {
// 尝试解析用户输入为JSON
const request = JSON.parse(input);
console.log('发送自定义请求:', JSON.stringify(request, null, 2));
scriptRunner.stdin.write(JSON.stringify(request) + '\n');
} catch (e) {
console.error('输入不是有效的JSON:', e.message);
}
});
// 显示帮助信息
console.log('\n=== MCP客户端示例 ===');
console.log('已自动发送初始化、工具列表和代码执行请求');
console.log('您可以输入自定义JSON请求或输入"exit"退出程序');
console.log('示例请求:');
console.log(JSON.stringify({
jsonrpc: '2.0',
id: 100,
method: 'callTool',
params: {
name: 'run_javascript',
arguments: {
code: 'return "Hello, " + new Date().toISOString();'
}
}
}, null, 2));
console.log('====================\n');

View File

@@ -0,0 +1,78 @@
// 这是一个使用JavaScript调用MCP服务的示例
// 你可以使用任何支持MCP协议的客户端来调用我们的服务
// 以下是一个简单的示例,展示如何调用我们的服务
// 假设我们已经连接到了MCP服务
// 执行JavaScript代码示例
async function testRunJavaScript() {
const code = `
// 一些处理代码
console.log("Processing JavaScript...");
function handler(input) {
// 输入包含通过参数传递的数据
console.log("Received input:", input);
// 返回最终结果
return "Hello from JavaScript! Input: " + JSON.stringify(input);
}
`;
const params = { name: "JavaScript User", data: [1, 2, 3] };
// 在实际的MCP客户端中你会使用类似以下的代码调用服务
// const result = await client.run_javascript({ code, params });
// console.log(result);
}
// 执行TypeScript代码示例
async function testRunTypeScript() {
const code = `
// 一些处理代码
console.log("Processing TypeScript...");
function handler(input: any): string {
// 输入包含通过参数传递的数据
console.log("Received input:", input);
// 返回最终结果
return \`Hello from TypeScript! Input: \${JSON.stringify(input)}\`;
}
`;
const params = { name: "TypeScript User", count: 42 };
// 在实际的MCP客户端中你会使用类似以下的代码调用服务
// const result = await client.run_typescript({ code, params });
// console.log(result);
}
// 执行Python代码示例
async function testRunPython() {
const code = `
# 一些处理代码
print("Processing Python...")
def handler(args):
# args 包含通过参数传递的数据
print(f"Received args: {args}")
# 返回最终结果
return f"Hello from Python! Args: {args}"
`;
const params = { name: "Python User", values: [10, 20, 30] };
// 在实际的MCP客户端中你会使用类似以下的代码调用服务
// const result = await client.run_python({ code, params });
// console.log(result);
}
// 在实际的MCP客户端中你可以按顺序调用这些函数
// await testRunJavaScript();
// await testRunTypeScript();
// await testRunPython();
// 注意:这个文件只是一个示例,不能直接运行
// 你需要使用支持MCP协议的客户端如Cursor来调用我们的服务

View File

@@ -0,0 +1,21 @@
#!/bin/bash
# 测试script_runner的简单bash脚本
# 初始化请求
echo "发送初始化请求..."
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"client_info":{"name":"test_client"}}}' | script_runner
# 等待一下
sleep 1
# 列出工具请求
echo -e "\n发送列出工具请求..."
echo '{"jsonrpc":"2.0","id":2,"method":"listTools"}' | script_runner
# 等待一下
sleep 1
# 执行JavaScript代码
echo -e "\n发送JavaScript代码执行请求..."
echo '{"jsonrpc":"2.0","id":3,"method":"callTool","params":{"name":"run_javascript","arguments":{"code":"console.log(\"Hello from JavaScript!\"); return { message: \"Hello, World!\", value: 42 };"}}}' | script_runner

View File

@@ -0,0 +1,24 @@
import * as o from 'https://deno.land/x/cowsay/mod.ts'
// 使用类型注解时应确保使用的是支持该特性的环境或编译器(如 TypeScript
function add(input) {
console.log("test Add", input.a, "+", input.b);
let m = o.say({
text: 'hello every one'
})
console.log(m)
return input.a + input.b;
}
export default async function handler(input) {
let a = add(input)
console.log("计算结果=" + a)
return {
message: a,
};
}

View File

@@ -0,0 +1,30 @@
import axios from 'npm:axios';
// 使用axios发起HTTP请求的函数
async function fetchData(url) {
console.log(`正在请求: ${url}`);
try {
const response = await axios.get(url);
console.log(`请求成功,状态码: ${response.status}`);
return response.data;
} catch (error) {
console.error(`请求失败: ${error.message}`);
return { error: error.message };
}
}
export default async function handler(input) {
// 默认URL或从输入参数获取
const url = input.url || 'https://jsonplaceholder.typicode.com/todos/1';
console.log(`开始处理请求URL: ${url}`);
// 发起请求并获取数据
const data = await fetchData(url);
return {
message: '请求完成',
data: data,
timestamp: new Date().toISOString()
};
}

View File

@@ -0,0 +1,79 @@
import { join, basename } from "https://deno.land/std/path/mod.ts";
import { readLines } from "https://deno.land/std/io/mod.ts";
import { format } from "https://deno.land/std/datetime/mod.ts";
// 使用Deno标准库处理路径和文件
async function processPath(path) {
console.log(`处理路径: ${path}`);
// 使用path模块处理路径
const fileName = basename(path);
console.log(`文件名: ${fileName}`);
const fullPath = join("/tmp", fileName);
console.log(`完整路径: ${fullPath}`);
// 获取当前时间并格式化
const now = new Date();
const formattedDate = format(now, "yyyy-MM-dd HH:mm:ss");
console.log(`当前时间: ${formattedDate}`);
return {
originalPath: path,
fileName: fileName,
fullPath: fullPath,
timestamp: formattedDate
};
}
// 模拟读取文件内容
async function simulateFileReading() {
const text = `这是第一行
这是第二行
这是第三行
这是最后一行`;
// 使用TextEncoder和StringReader代替Deno.Buffer
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const data = encoder.encode(text);
// 创建一个可读流
const stream = new ReadableStream({
start(controller) {
controller.enqueue(data);
controller.close();
}
});
// 将流转换为Reader
const reader = stream.getReader();
console.log("开始读取文件内容:");
// 手动处理文本行
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
console.log(`${i+1}行: ${lines[i]}`);
}
return lines;
}
export default async function handler(input) {
console.log("开始处理");
// 处理路径
const path = input.path || "example.txt";
const pathInfo = await processPath(path);
// 模拟读取文件
const fileLines = await simulateFileReading();
return {
message: '处理完成',
pathInfo: pathInfo,
fileContent: fileLines,
linesCount: fileLines.length
};
}

View File

@@ -0,0 +1,59 @@
// 使用ES模块导入方式
import { createHash } from "node:crypto";
import { Buffer } from "node:buffer";
// 使用Node.js内置模块处理数据
function hashData(data, algorithm = 'sha256') {
console.log(`使用 ${algorithm} 算法处理数据`);
// 将数据转换为Buffer
const buffer = typeof data === 'string'
? Buffer.from(data)
: Buffer.from(JSON.stringify(data));
// 创建哈希
const hash = createHash(algorithm);
hash.update(buffer);
// 获取哈希结果
const digest = hash.digest('hex');
console.log(`哈希结果: ${digest}`);
return digest;
}
// 加密数据
function encryptData(data, salt = 'default-salt') {
console.log(`加密数据,使用盐值: ${salt}`);
// 组合数据和盐值
const combined = `${data}:${salt}:${Date.now()}`;
// 生成多种哈希
const sha256Hash = hashData(combined, 'sha256');
const md5Hash = hashData(combined, 'md5');
return {
original: data,
salt,
sha256: sha256Hash,
md5: md5Hash,
timestamp: new Date().toISOString()
};
}
export default async function handler(input) {
console.log("开始处理加密任务");
// 获取输入数据
const data = input.data || "这是需要加密的默认数据";
const salt = input.salt || "custom-salt-" + Math.floor(Math.random() * 1000);
// 加密数据
const encryptedResult = encryptData(data, salt);
return {
message: '加密处理完成',
result: encryptedResult
};
}

View File

@@ -0,0 +1,79 @@
import { assertEquals } from "jsr:@std/assert";
import { delay } from "jsr:@std/async";
import { bold, red, green } from "jsr:@std/fmt/colors";
// 使用JSR标准库中的工具函数
async function runTests(input) {
console.log(bold("开始运行测试..."));
// 使用delay函数模拟异步操作
console.log("等待1秒...");
await delay(1000);
// 测试结果数组
const results = [];
// 测试1: 加法运算
try {
const a = input.a || 5;
const b = input.b || 3;
const expected = input.expected || 8;
console.log(`测试加法: ${a} + ${b} = ${expected}`);
assertEquals(a + b, expected);
console.log(green("✓ 加法测试通过"));
results.push({ name: "加法测试", success: true });
} catch (error) {
console.log(red(`✗ 加法测试失败: ${error.message}`));
results.push({ name: "加法测试", success: false, error: error.message });
}
// 测试2: 字符串操作
try {
const str1 = input.str1 || "hello";
const str2 = input.str2 || "world";
const expectedStr = input.expectedStr || "hello world";
console.log(`测试字符串连接: "${str1}" + " " + "${str2}" = "${expectedStr}"`);
assertEquals(`${str1} ${str2}`, expectedStr);
console.log(green("✓ 字符串测试通过"));
results.push({ name: "字符串测试", success: true });
} catch (error) {
console.log(red(`✗ 字符串测试失败: ${error.message}`));
results.push({ name: "字符串测试", success: false, error: error.message });
}
// 等待再次展示结果
console.log("处理结果中...");
await delay(500);
return results;
}
export default async function handler(input) {
console.log(bold("JSR依赖示例"));
// 运行测试
const testResults = await runTests(input);
// 统计结果
const passed = testResults.filter(r => r.success).length;
const failed = testResults.filter(r => !r.success).length;
// 使用彩色输出显示结果
console.log(bold("\n测试结果摘要:"));
console.log(green(`通过: ${passed}`));
console.log(failed > 0 ? red(`失败: ${failed}`) : green(`失败: ${failed}`));
return {
message: '测试完成',
results: testResults,
summary: {
total: testResults.length,
passed,
failed
}
};
}

View File

@@ -0,0 +1,74 @@
// 首先创建一个本地模块,通过相对路径引入
// 注意这个示例假设有一个名为utils.js的本地模块
// 模拟本地utils.js模块的内容
const utils = {
formatNumber(num) {
return num.toLocaleString('zh-CN');
},
calculateTax(amount, rate = 0.1) {
return amount * rate;
},
generateId() {
return `id-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
},
formatDate(date = new Date()) {
return date.toLocaleDateString('zh-CN');
}
};
// 使用本地模块的函数
function processOrder(order) {
console.log(`处理订单: ${order.id || utils.generateId()}`);
// 计算订单总额
const total = order.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
console.log(`订单总额: ${utils.formatNumber(total)}`);
// 计算税费
const taxRate = order.taxRate || 0.13;
const tax = utils.calculateTax(total, taxRate);
console.log(`税费(${taxRate * 100}%): ${utils.formatNumber(tax)}`);
// 计算最终金额
const finalAmount = total + tax;
console.log(`最终金额: ${utils.formatNumber(finalAmount)}`);
// 生成订单日期
const orderDate = order.date ? new Date(order.date) : new Date();
console.log(`订单日期: ${utils.formatDate(orderDate)}`);
return {
id: order.id || utils.generateId(),
items: order.items,
subtotal: total,
tax: tax,
total: finalAmount,
date: utils.formatDate(orderDate)
};
}
export default async function handler(input) {
console.log("开始处理订单");
// 默认订单或从输入获取
const order = input.order || {
items: [
{ name: "产品A", price: 100, quantity: 2 },
{ name: "产品B", price: 50, quantity: 1 },
{ name: "产品C", price: 200, quantity: 3 }
],
taxRate: 0.13
};
// 处理订单
const processedOrder = processOrder(order);
return {
message: '订单处理完成',
order: processedOrder
};
}

View File

@@ -0,0 +1,43 @@
import _ from 'npm:lodash';
// 使用lodash处理数据的函数
function processData(input) {
console.log("输入数据:", input);
// 使用lodash的chunk方法将数组分块
if (Array.isArray(input.data)) {
const chunks = _.chunk(input.data, input.chunkSize || 2);
console.log("数据分块结果:", chunks);
// 使用lodash的map方法处理每个块
const processedChunks = _.map(chunks, (chunk) => {
return {
items: chunk,
sum: _.sum(chunk),
average: _.mean(chunk)
};
});
return processedChunks;
}
return { error: "输入数据不是数组" };
}
export default async function handler(input) {
console.log("开始处理数据");
// 从输入获取数据,或使用默认数据
const data = input.data || [1, 2, 3, 4, 5, 6, 7, 8, 9];
const chunkSize = input.chunkSize || 3;
// 处理数据
const result = processData({ data, chunkSize });
return {
message: '数据处理完成',
originalData: data,
processedData: result,
timestamp: new Date().toISOString()
};
}

View File

@@ -0,0 +1,13 @@
//引入js组件,必须是http(s)形式引入,可以去网站: https://deno.land/x 搜索自己所需组件
//下面是一个引入示例
//import * as o from https://deno.land/x/cowsay/mod.ts
//网络请求,可以直接使用fetch ,具体自行查阅 fetch 文档
//打印日志使用console,比如:console.log
// 入口函数不可修改否则无法执行args 为配置的入参
export default async function main(args) {
// 构建输出对象出参中的key需与配置的出参保持一致
return {
key: 'value',
};
}

View File

@@ -0,0 +1,26 @@
import requests
from bs4 import BeautifulSoup
# 发送请求到百度
url = 'https://www.baidu.com'
response = requests.get(url)
# 检查请求是否成功
if response.status_code == 200:
# 解析网页内容
soup = BeautifulSoup(response.text, 'html.parser')
# 提取你想要的信息,例如标题
title = soup.title.string
else:
print(f"请求失败,状态码:")
# 入口函数不可修改否则无法执行args 为配置的入参
def main(args: dict) -> dict:
params = args.get("params")
# 构建输出对象出参中的key需与配置的出参保持一致
ret = {
"key": "value"
}
return ret

View File

@@ -0,0 +1,20 @@
// 使用类型注解时应确保使用的是支持该特性的环境或编译器(如 TypeScript
function add(input) {
console.log("test Add", input.a, "+", input.b);
// 简单的字符串输出,不使用外部模块
console.log("Hello from JavaScript!");
return input.a + input.b;
}
function handler(input) {
console.log("输入参数:", JSON.stringify(input));
let a = add(input);
console.log("计算结果=" + a);
return {
message: a,
};
}

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
import pandas as pd
import logging
import json
# 创建一个简单的数据结构不使用pandas
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35]
}
df = pd.DataFrame(data)
# 记录数据信息
logging.info(f"Created data structure:\n{json.dumps(data, indent=2)}")
def main(args: dict) -> dict:
logging.info(f"input args: {args}")
params = args.get("params")
# 处理params为None的情况
if params is None:
logging.warning("params is None, using default values")
params = {"input": "default_value"}
elif not isinstance(params, dict):
logging.warning(f"params is not a dictionary: {type(params)}, using default values")
params = {"input": str(params)}
elif "input" not in params:
logging.warning("input key not found in params, using default value")
params["input"] = "default_value"
# 构建输出对象
ret = {
"key0": params['input'], # 使用input参数值
"key1": ["hello", "world"], # 输出一个数组
"key2": { # 输出一个Object
"key21": "hi"
},
}
return ret

View File

@@ -0,0 +1,25 @@
// Sample JavaScript file with handler function
// Log some debug information
console.log("Initializing JavaScript test...");
// Define a simple add function
function add(a, b) {
console.log(`Adding ${a} + ${b}`);
return a + b;
}
// Some processing
const numbers = [1, 2, 3, 4, 5];
console.log("Processing numbers:", numbers);
// Handler function that will be called to get the result
function handler() {
console.log("Handler function called");
// Calculate sum
const sum = numbers.reduce((acc, num) => add(acc, num), 0);
console.log("Final calculation completed");
return `The sum of [${numbers.join(", ")}] is ${sum}`;
}

View File

@@ -0,0 +1,75 @@
// Test JavaScript script with large text parameters to verify "Argument list too long" fix
// Generate a large text parameter (>2MB) to test the temporary file parameter passing solution
function generateLargeText(sizeInMB) {
const chunk = "This is a test chunk of text designed to generate large parameters. ".repeat(100);
const iterations = Math.ceil((sizeInMB * 1024 * 1024) / chunk.length);
let result = "";
for (let i = 0; i < iterations; i++) {
result += chunk + `Iteration ${i}\n`;
}
return result;
}
// Handler function that processes large text parameters
function handler(args) {
console.log("Handler function called with large parameters");
// Test that we received the large text parameter
if (!args || !args.largeText) {
console.log("ERROR: No largeText parameter found");
return { success: false, error: "Missing largeText parameter" };
}
const largeText = args.largeText;
console.log("Received large text parameter, length:", largeText.length);
console.log("Large text parameter size (MB):", (largeText.length / 1024 / 1024).toFixed(2));
// Verify the text contains expected content
if (largeText.includes("This is a test chunk of text designed to generate large parameters")) {
console.log("SUCCESS: Large text parameter contains expected content");
// Count occurrences of "Iteration" to verify data integrity
const iterationCount = (largeText.match(/Iteration \d+/g) || []).length;
console.log("Found", iterationCount, "iterations in the text");
// Return some statistics about the processed data
return {
success: true,
message: "Successfully processed large text parameter",
textSize: largeText.length,
sizeMB: (largeText.length / 1024 / 1024).toFixed(2),
iterationCount: iterationCount,
sampleText: largeText.substring(0, 100) + "..."
};
} else {
console.log("ERROR: Large text parameter does not contain expected content");
return { success: false, error: "Invalid content in largeText parameter" };
}
}
// This part won't be executed when the code is run through the MCP runner
if (typeof require !== 'undefined' && require.main === module) {
console.log("Running large parameter test directly...");
// Generate test data (3MB to ensure we exceed the 2MB threshold)
const largeText = generateLargeText(3);
console.log("Generated test data, size:", (largeText.length / 1024 / 1024).toFixed(2), "MB");
// Create test arguments
const testArgs = {
largeText: largeText,
additionalParam: "test value",
numberParam: 42
};
console.log("Calling handler with large parameters...");
const result = handler(testArgs);
console.log("Result:", JSON.stringify(result, null, 2));
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = { handler, generateLargeText };
}

View File

@@ -0,0 +1,39 @@
// JavaScript测试文件演示参数传递
// 打印一些调试信息
console.log("JavaScript脚本开始执行...");
// 定义一个加法函数
function add(a, b) {
console.log(`正在计算: ${a} + ${b}`);
return a + b;
}
// 处理一些数据
const numbers = [1, 2, 3, 4, 5];
console.log(`处理数字列表: ${numbers}`);
/**
* 处理函数,接收参数并返回结果
* 注意:这个函数必须存在,并且会被框架调用来获取结果
*/
function handler(input) {
console.log(`接收到的参数: ${JSON.stringify(input)}`);
// 从参数中获取值,提供默认值
const a = input.a || 0;
const b = input.b || 0;
const name = input.name || "用户";
// 计算结果
const result = add(a, b);
console.log(`计算完成: ${a} + ${b} = ${result}`);
// 返回结果
return {
sum: result,
numbers: numbers,
greeting: `你好,${name}`,
message: `成功计算 ${a} + ${b} 的结果`
};
}

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python3
# Sample Python file with handler function
import time
time.time()
# Debug print to confirm script execution
print("DEBUGGING: Script is being executed!")
# Log some debug information
print("Initializing Python test...")
# Define a simple function
def multiply(a, b):
print(f"Multiplying {a} * {b}")
return a * b
# Some processing
numbers = [1, 2, 3, 4, 5]
print(f"Processing numbers: {numbers}")
# Handler function that will be called to get the result
def handler(args):
print("Handler function called")
print(f"Received args: {args}")
# Calculate product (using the predefined numbers)
product = 1
for num in numbers:
product = multiply(product, num)
print("Final calculation completed")
return f"The product of {numbers} is {product}"
# This part won't be executed when the code is run through the MCP runner
if __name__ == "__main__":
print("Running directly, calling handler...")
# Create sample args for direct execution
sample_args = {"test": "direct_execution", "data": [1, 2, 3]}
result = handler(sample_args)
print(f"Result: {result}")

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# Test Python script with large text parameters to verify "Argument list too long" fix
import sys
import json
import time
# Generate a large text parameter (>2MB) to test the temporary file parameter passing solution
def generate_large_text(size_in_mb):
chunk = "This is a test chunk of text designed to generate large parameters for Python testing. " * 100
iterations = (size_in_mb * 1024 * 1024) // len(chunk)
result = ""
for i in range(iterations):
result += chunk + f"Iteration {i}\n"
return result
# Handler function that processes large text parameters
def handler(args):
print("Handler function called with large parameters")
# Test that we received the large text parameter
if not args or 'largeText' not in args:
print("ERROR: No largeText parameter found")
return {"success": False, "error": "Missing largeText parameter"}
large_text = args['largeText']
print(f"Received large text parameter, length: {len(large_text)}")
print(f"Large text parameter size (MB): {len(large_text) / 1024 / 1024:.2f}")
# Verify the text contains expected content
expected_content = "This is a test chunk of text designed to generate large parameters for Python testing"
if expected_content in large_text:
print("SUCCESS: Large text parameter contains expected content")
# Count occurrences of "Iteration" to verify data integrity
iteration_count = large_text.count("Iteration")
print(f"Found {iteration_count} iterations in the text")
return {
"success": True,
"message": "Successfully processed large text parameter",
"textSize": len(large_text),
"sizeMB": round(len(large_text) / 1024 / 1024, 2),
"iterationCount": iteration_count,
"sampleText": large_text[:100] + "..."
}
else:
print("ERROR: Large text parameter does not contain expected content")
return {"success": False, "error": "Invalid content in largeText parameter"}
# This part won't be executed when the code is run through the MCP runner
if __name__ == "__main__":
print("Running large parameter test directly...")
# Generate test data (3MB to ensure we exceed the 2MB threshold)
large_text = generate_large_text(3)
print(f"Generated test data, size: {len(large_text) / 1024 / 1024:.2f} MB")
# Create test arguments
test_args = {
"largeText": large_text,
"additionalParam": "test value",
"numberParam": 42
}
print("Calling handler with large parameters...")
result = handler(test_args)
print("Result:", json.dumps(result, indent=2))

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
# 测试logging模块的日志捕获功能
import logging
import json
# 配置日志
logging.basicConfig(level=logging.DEBUG)
# 使用不同级别的日志
def log_messages():
logging.debug("这是一条DEBUG级别的日志")
logging.info("这是一条INFO级别的日志")
logging.warning("这是一条WARNING级别的日志")
logging.error("这是一条ERROR级别的日志")
logging.critical("这是一条CRITICAL级别的日志")
# 使用格式化
data = {"name": "测试", "value": 42}
logging.info(f"格式化的JSON数据: {json.dumps(data, ensure_ascii=False)}")
return "日志测试完成"
def handler(args):
# 记录输入参数
logging.info(f"收到参数: {args}")
# 生成一些日志
result = log_messages()
# 返回结果
return {
"message": result,
"log_count": 6, # 总共记录了6条日志
"args": args
}

View File

@@ -0,0 +1,33 @@
import json
import os
# 打印一些调试信息
print("Python脚本开始执行...")
# 定义一个简单的加法函数
def add(a, b):
print("正在计算: " + str(a) + " + " + str(b))
return a + b
# 处理一些数据
numbers = [1, 2, 3, 4, 5]
print("处理数字列表: " + str(numbers))
# 入口函数,接收参数
def main(args):
print("接收到的参数: " + str(args))
# 从参数中获取值
a = args.get("a", 0)
b = args.get("b", 0)
# 计算结果
result = add(a, b)
print("计算完成: " + str(a) + " + " + str(b) + " = " + str(result))
# 返回结果
return {
"sum": result,
"numbers": numbers,
"message": "成功计算 " + str(a) + " + " + str(b) + " 的结果"
}

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# 简单的Python测试脚本用于测试参数传递机制
def main(args: dict) -> dict:
print(f"接收到的参数: {args}")
# 尝试通过两种方式获取参数
direct_params = args.get("input", "direct_default")
nested_params = None
params = args.get("params")
if params:
if isinstance(params, dict):
nested_params = params.get("input", "nested_default")
else:
nested_params = str(params)
# 返回结果,展示两种方式获取的参数
return {
"direct_access": direct_params,
"nested_access": nested_params,
"args_structure": args
}

View File

@@ -0,0 +1,46 @@
import json
import os
# 打印一些调试信息
print("Python类型测试脚本开始执行...")
# 入口函数,接收参数并返回不同类型的值
def main(args):
print("接收到的参数:", args)
# 获取要测试的类型
test_type = args.get("type", "string")
# 根据参数返回不同类型的值
if test_type == "string":
print("返回字符串类型")
return "这是一个字符串"
elif test_type == "number":
print("返回数字类型")
return 12345
elif test_type == "boolean":
print("返回布尔类型")
return True
elif test_type == "null":
print("返回None类型")
return None
elif test_type == "list":
print("返回列表类型")
return [1, 2, 3, "", "", True]
elif test_type == "dict":
print("返回字典类型")
return {
"name": "测试用户",
"age": 30,
"is_active": True,
"tags": ["python", "rust", "json"]
}
else:
print("未知类型,返回默认字符串")
return "未知类型"

View File

@@ -0,0 +1,41 @@
// Sample TypeScript file with handler function
// Log some debug information
console.log("Initializing TypeScript test...");
// Define a simple add function with type annotations
function add(a: number, b: number): number {
console.log(`Adding ${a} + ${b}`);
return a + b;
}
// Some processing with type annotations
const numbers: number[] = [1, 2, 3, 4, 5];
console.log("Processing numbers:", numbers);
// Interface for a person object
interface Person {
name: string;
age: number;
}
// Create a person object
const person: Person = {
name: "TypeScript User",
age: 30
};
console.log("Person object:", person);
/**
* Handler function that will be called to get the result
* 注意:这个函数必须存在,并且会被框架调用来获取结果
*/
function handler(): string {
console.log("Handler function called");
// Calculate sum
const sum = numbers.reduce((acc, num) => add(acc, num), 0);
console.log("Final calculation completed");
return `Hello ${person.name}! The sum of [${numbers.join(", ")}] is ${sum}`;
}

View File

@@ -0,0 +1,46 @@
// TypeScript测试文件演示参数传递
// 定义参数接口
interface InputParams {
a: number;
b: number;
name?: string;
}
// 打印一些调试信息
console.log("TypeScript脚本开始执行...");
// 定义一个带类型的加法函数
function add(a: number, b: number): number {
console.log(`正在计算: ${a} + ${b}`);
return a + b;
}
// 处理一些数据
const numbers: number[] = [1, 2, 3, 4, 5];
console.log(`处理数字列表: ${numbers}`);
/**
* 处理函数,接收参数并返回结果
* 注意:这个函数必须存在,并且会被框架调用来获取结果
*/
function handler(input: InputParams): object {
console.log(`接收到的参数: ${JSON.stringify(input)}`);
// 从参数中获取值,提供默认值
const a = input.a || 0;
const b = input.b || 0;
const name = input.name || "用户";
// 计算结果
const result = add(a, b);
console.log(`计算完成: ${a} + ${b} = ${result}`);
// 返回结果
return {
sum: result,
numbers: numbers,
greeting: `你好,${name}`,
message: `成功计算 ${a} + ${b} 的结果`
};
}

View File

@@ -0,0 +1,10 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Failed to create temporary file: {0}")]
IoError(#[from] std::io::Error),
#[error("Failed to parse JSON: {0}")]
JsonError(#[from] serde_json::Error),
}

View File

@@ -0,0 +1,131 @@
use anyhow::{Context, Result};
use log::info;
use std::path::{Path, PathBuf};
use tokio::fs::{self, File, create_dir_all};
use tokio::io::AsyncWriteExt;
use crate::model::LanguageScript;
///针对用的代码,进行检测和缓存
pub struct CodeFileCache;
impl CodeFileCache {
/// 检查代码文件缓存
pub fn obtain_code_hash(code: &str) -> String {
// 使用BLAKE3计算哈希更快速且安全
let hash = blake3::hash(code.as_bytes());
let hash_str = hash.to_hex().to_string();
info!("计算代码hash值: {}", &hash_str);
hash_str
}
/// 根据代码的hash检查是否存在缓存
pub async fn check_code_file_cache_exisht(hash: &str, language: &LanguageScript) -> bool {
fs::try_exists(Self::get_cache_file_path(hash, language))
.await
.unwrap_or(false)
}
/// 获取代码文件缓存
pub async fn get_code_file_cache(
hash: &str,
language: &LanguageScript,
) -> Result<(File, PathBuf)> {
let file_path = Self::get_cache_file_path(hash, language);
let file = File::open(&file_path)
.await
.with_context(|| format!("无法打开缓存文件: {}", file_path.display()))?;
Ok((file, file_path))
}
/// 保存代码文件缓存
pub async fn save_code_file_cache(
hash: &str,
code: &str,
language: &LanguageScript,
) -> Result<(File, PathBuf)> {
// 确保缓存目录存在
let cache_dir = Self::get_cache_dir();
//检查目录是否存在
if !fs::try_exists(&cache_dir).await.unwrap_or(false) {
create_dir_all(&cache_dir)
.await
.with_context(|| format!("无法创建缓存目录: {}", cache_dir.display()))?;
}
// 构建文件路径并保存代码
let file_path = Self::get_cache_file_path(hash, language);
let mut file = File::create(&file_path)
.await
.with_context(|| format!("无法创建缓存文件: {}", file_path.display()))?;
file.write_all(code.as_bytes())
.await
.with_context(|| "写入代码到缓存文件失败")?;
// 设置文件权限
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&file_path).await?.permissions();
perms.set_mode(0o755); // rwxr-xr-x
fs::set_permissions(&file_path, perms).await?;
}
let file_path = Self::get_cache_file_path(hash, language);
Ok((file, file_path))
}
/// 清除指定语言的缓存文件
pub async fn clear_cache_by_language(language: &LanguageScript) -> Result<()> {
let cache_dir = Self::get_cache_dir();
if !fs::try_exists(&cache_dir).await.unwrap_or(false) {
return Ok(());
}
let suffix = language.get_file_suffix();
let mut entries = fs::read_dir(cache_dir).await?;
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if let Some(file_name) = path.file_name() {
if let Some(name_str) = file_name.to_str() {
if name_str.ends_with(suffix) {
fs::remove_file(&path)
.await
.with_context(|| format!("无法删除缓存文件: {}", path.display()))?;
}
}
}
}
Ok(())
}
/// 清除所有缓存文件
pub async fn clear_all_cache() -> Result<()> {
let cache_dir = Self::get_cache_dir();
if fs::try_exists(&cache_dir).await.unwrap_or(false) {
fs::remove_dir_all(&cache_dir)
.await
.with_context(|| format!("无法删除缓存目录: {}", cache_dir.display()))?;
}
Ok(())
}
/// 获取缓存目录路径
fn get_cache_dir() -> PathBuf {
// 在容器环境中使用固定路径
Path::new("/tmp/code_cache").to_path_buf()
}
/// 获取缓存文件路径
fn get_cache_file_path(hash: &str, language: &LanguageScript) -> PathBuf {
let mut path = Self::get_cache_dir();
// 根据语言类型添加对应的文件扩展名
let file_name = format!("{}{}", hash, language.get_file_suffix());
path.push(file_name);
path
}
}

3
qiming-run_code_rmcp/src/cache/mod.rs vendored Normal file
View File

@@ -0,0 +1,3 @@
mod code_file_cache;
pub use code_file_cache::CodeFileCache;

View File

@@ -0,0 +1,117 @@
use crate::cache::CodeFileCache;
use crate::model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript};
use anyhow::Result;
use log::{debug, error, info};
use serde_json::Value;
use std::io::Write;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
/// 通用的 Deno 脚本执行逻辑,供 JS/TS Runner 复用
pub async fn run_deno_script_with_params<F>(
code: &str,
params: Option<Value>,
timeout_seconds: Option<u64>,
lang: LanguageScript,
prepare_code_fn: F,
) -> Result<CodeScriptExecutionResult>
where
F: Fn(&str, bool) -> String,
{
debug!("开始执行{lang:?}脚本...,执行参数: {params:?}");
let hash = CodeFileCache::obtain_code_hash(code);
let cache_exist = CodeFileCache::check_code_file_cache_exisht(&hash, &lang).await;
let run_code_script_file_tuple = if cache_exist {
let cache_code = CodeFileCache::get_code_file_cache(&hash, &lang).await;
debug!("从缓存中读取代码:hash值 {:?}", &hash);
cache_code?
} else {
let wrapped_code = prepare_code_fn(code, true);
CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &lang).await?;
let code_script_file_tuple = CodeFileCache::get_code_file_cache(&hash, &lang).await?;
debug!("创建脚本缓存:hash值 {:?}", &hash);
code_script_file_tuple
};
let temp_path = run_code_script_file_tuple.1;
let mut execute_command = Command::new("deno");
execute_command
.arg("run")
.arg("--allow-net")
.arg("--allow-env")
.arg("--allow-read")
.arg("--no-check")
.arg("--v8-flags=--max-heap-size=512")
.arg(&temp_path)
.kill_on_drop(true);
// 处理参数:统一使用临时文件传递
let temp_input_path = if let Some(params) = params {
let params_json = serde_json::to_string(&params)?;
// 创建临时文件写入参数
let temp_dir = tempfile::TempDir::new()?;
let temp_file_path = temp_dir.path().join("input_params.json");
// 写入参数到临时文件
std::fs::write(&temp_file_path, params_json.as_bytes())?;
// 保持TempDir存在这样文件就不会被删除
let temp_dir_path = temp_dir.path().to_path_buf();
std::mem::forget(temp_dir);
// 设置环境变量指向临时文件
execute_command.env("INPUT_JSON_FILE", &temp_file_path);
debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path);
Some(temp_file_path)
} else {
// 没有参数时设置空对象
execute_command.env("INPUT_JSON", "{}");
None
};
debug!("Deno命令[{:?}]: {:?}", lang, &execute_command);
let executor = match timeout_seconds {
Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout),
None => CommandExecutor::default(execute_command.output()),
};
info!("执行命令: {:?}", &execute_command);
let executor_result = executor.await;
let output = match executor_result {
Ok(cmd_result) => match cmd_result {
Ok(output) => output,
Err(e) => {
error!("Deno命令执行失败 [{lang:?}]: {e:?}");
return Err(e.into());
}
},
Err(e) => {
error!("Deno任务执行异常 [{lang:?}]: {e:?}");
return Err(e.into());
}
};
debug!("标准输出:\n{}", String::from_utf8_lossy(&output.stdout));
debug!("错误输出:\n{}", String::from_utf8_lossy(&output.stderr));
// 执行完成后删除临时文件和目录
if let Some(temp_file_path) = temp_input_path {
// 删除文件
let _ = fs::remove_file(&temp_file_path).await;
// 尝试删除父目录(如果为空)
if let Some(parent) = temp_file_path.parent() {
let _ = fs::remove_dir(parent).await;
}
debug!("已删除临时文件: {:?}", temp_file_path);
}
CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await
}

View File

@@ -0,0 +1,61 @@
// deno 运行js脚本
use crate::deno_runner::common_runner::run_deno_script_with_params;
use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode};
use anyhow::Result;
#[derive(Default)]
pub struct JsRunner;
impl RunCode for JsRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
run_deno_script_with_params(
code,
params,
timeout_seconds,
LanguageScript::Js,
|c, show_logs| self.prepare_js_code(c, show_logs),
)
.await
}
}
impl JsRunner {
/// 准备JavaScript代码添加日志捕获和handler函数执行逻辑
fn prepare_js_code(&self, code: &str, show_logs: bool) -> String {
// 检查代码中是否包含ES模块特征
let is_esm = {
// 检查正式的 import/export 语句
let has_import_export = code.contains("import ")
|| code.contains("export ")
|| code.contains("import{")
|| code.contains("export{");
// 检查动态 import
let has_dynamic_import = code.contains("import(");
// 检查是否有 require - CommonJS 的标志
let has_require = code.contains("require(");
// 如果有 import/export 特征,或者动态 import但没有 require则判定为 ESM
(has_import_export || has_dynamic_import) && !has_require
};
// 根据代码特征选择合适的模板
let template = if is_esm {
include_str!("../templates/js_template_es.js")
} else {
include_str!("../templates/js_template_normal.js")
};
// 替换模板中的占位符
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", &show_logs.to_string())
}
}

View File

@@ -0,0 +1,6 @@
mod common_runner;
mod js_runner;
mod ts_runner;
pub use js_runner::JsRunner;
pub use ts_runner::TsRunner;

View File

@@ -0,0 +1,37 @@
// deno 运行ts脚本
use crate::model::{CodeScriptExecutionResult, LanguageScript, RunCode};
use anyhow::Result;
use crate::deno_runner::common_runner::run_deno_script_with_params;
#[derive(Default)]
pub struct TsRunner;
impl RunCode for TsRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
run_deno_script_with_params(
code,
params,
timeout_seconds,
LanguageScript::Ts,
|c, show_logs| self.prepare_ts_code(c, show_logs),
).await
}
}
impl TsRunner {
/// 准备TypeScript代码添加日志捕获和handler函数执行逻辑
fn prepare_ts_code(&self, code: &str, show_logs: bool) -> String {
let template = include_str!("../templates/ts_template.ts");
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", &show_logs.to_string())
}
}

View File

@@ -0,0 +1,19 @@
mod app_error;
mod cache;
mod deno_runner;
#[cfg(feature = "mcp")]
mod mcp;
mod model;
mod python_runner;
#[cfg(test)]
mod tests;
mod warm_up;
pub use cache::*;
pub use deno_runner::*;
#[cfg(feature = "mcp")]
pub use mcp::CodeRunRequest;
pub use model::RunCodeHttpResult;
pub use model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
pub use python_runner::*;
pub use warm_up::warm_up_all_envs;

View File

@@ -0,0 +1,194 @@
use anyhow::{Context, Result};
use clap::{Args, Parser, Subcommand};
use log::{error, info};
use serde_json::Value;
use std::{fs, path::PathBuf};
mod app_error;
mod cache;
mod deno_runner;
#[cfg(feature = "mcp")]
mod mcp;
mod model;
mod python_runner;
use crate::cache::CodeFileCache;
use crate::model::{CodeExecutor, CodeScriptExecutionResult, LanguageScript};
#[derive(Parser)]
#[command(name = "run_code_rmcp")]
#[command(about = "Execute JavaScript and Python code using MCP SDK", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
/// Show execution logs
#[arg(short, long)]
show_logs: bool,
/// Use MCP SDK integration
#[cfg(feature = "mcp")]
#[arg(short, long)]
use_mcp: bool,
/// Clear cache before execution
#[arg(short = 'c', long)]
clear_cache: bool,
}
#[derive(Subcommand)]
enum Commands {
/// Execute JavaScript code
Js(CodeArgs),
/// Execute TypeScript code
Ts(CodeArgs),
/// Execute Python code
Python(CodeArgs),
/// Clear cache files
ClearCache {
/// Language to clear cache for (js, ts, python, or all)
#[arg(short, long)]
language: String,
},
}
#[derive(Args)]
struct CodeArgs {
/// Path to the code file
#[arg(short, long)]
file: Option<PathBuf>,
/// Code content as string
#[arg(short, long)]
code: Option<String>,
/// Parameters to pass to the script (JSON format)
#[arg(short, long)]
params: Option<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
// 解析命令行参数
let cli = Cli::parse();
// 初始化日志
let mut builder = env_logger::Builder::from_default_env();
if cli.show_logs {
// 如果--show-logs参数为true则至少显示info级别的日志
builder.filter_level(log::LevelFilter::Info);
}
builder.init();
if let Commands::ClearCache { language } = &cli.command {
match language.to_lowercase().as_str() {
"js" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Js).await?;
info!("已清除 JavaScript 缓存");
}
"ts" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Ts).await?;
info!("已清除 TypeScript 缓存");
}
"python" => {
CodeFileCache::clear_cache_by_language(&LanguageScript::Python).await?;
info!("已清除 Python 缓存");
}
"all" => {
CodeFileCache::clear_all_cache().await?;
info!("已清除所有缓存");
}
_ => {
info!("无效的语言类型,可选项: js, ts, python, all");
return Ok(());
}
}
return Ok(());
}
// 解析传递给脚本的参数
let params = match &cli.command {
Commands::Js(args) => parse_params(&args.params)?,
Commands::Ts(args) => parse_params(&args.params)?,
Commands::Python(args) => parse_params(&args.params)?,
_ => None,
};
// 从参数获取代码
let (code, language) = match &cli.command {
Commands::Js(args) => (get_code(args)?, LanguageScript::Js),
Commands::Ts(args) => (get_code(args)?, LanguageScript::Ts),
Commands::Python(args) => (get_code(args)?, LanguageScript::Python),
_ => unreachable!(),
};
// 如果指定了清除缓存选项,则清除对应语言的缓存
if cli.clear_cache {
CodeFileCache::clear_cache_by_language(&language).await?;
info!("已清除 {language:?} 缓存");
}
// 执行代码
#[cfg(feature = "mcp")]
let result = if cli.use_mcp {
// 使用MCP SDK集成
CodeExecutor::execute_with_params_compat(&code, language, params).await?
} else {
// 直接执行
CodeExecutor::execute_with_params_compat(&code, language, params).await?
};
#[cfg(not(feature = "mcp"))]
let result = CodeExecutor::execute_with_params_compat(&code, language, params).await?;
// 打印结果
print_result(result);
Ok(())
}
fn get_code(args: &CodeArgs) -> Result<String> {
if let Some(file) = &args.file {
fs::read_to_string(file).context("Failed to read code file")
} else if let Some(code) = &args.code {
Ok(code.clone())
} else {
anyhow::bail!("Either file or code must be provided")
}
}
fn parse_params(params_str: &Option<String>) -> Result<Option<Value>> {
match params_str {
Some(s) if !s.is_empty() => {
let parsed: Value =
serde_json::from_str(s).context("Failed to parse parameters as JSON")?;
Ok(Some(parsed))
}
_ => Ok(None),
}
}
fn print_result(result: CodeScriptExecutionResult) {
if !result.logs.is_empty() {
info!("--- Logs ---");
for log in result.logs {
info!("{log}");
}
info!("------------");
}
if let Some(result_val) = result.result {
// 使用 serde_json 序列化结果,确保所有类型都能正确显示
match serde_json::to_string_pretty(&result_val) {
Ok(json_str) => info!("Result: {json_str}"),
Err(_) => info!("Result: {result_val}"),
}
}
if let Some(error) = result.error {
error!("Error: {error}");
}
}

View File

@@ -0,0 +1,164 @@
use anyhow::Result;
use rmcp::{
ErrorData as McpError, ServerHandler,
handler::server::wrapper::Parameters,
model::{
CallToolResult, Content, Implementation, ProtocolVersion, ServerCapabilities, ServerInfo,
},
tool, tool_handler, tool_router,
};
use serde::Deserialize;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
/// 代码执行请求参数
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CodeRunRequest {
#[schemars(description = "要执行的代码")]
pub code: String,
#[schemars(description = "可选的执行参数")]
pub params: Option<serde_json::Value>,
}
/// 代码执行工具服务
#[derive(Debug, Clone, Default)]
pub struct CodeRunnerService;
#[tool_router]
impl CodeRunnerService {
#[tool(description = "执行JavaScript代码并返回结果")]
async fn run_javascript(
&self,
request: Parameters<CodeRunRequest>,
) -> Result<CallToolResult, McpError> {
let request = request.0;
match CodeExecutor::execute_with_params_compat(
&request.code,
LanguageScript::Js,
request.params,
)
.await
{
Ok(result) => {
if result.success {
let content = Content::json(json!({
"result": result.result,
"logs": result.logs,
"success": true
}))?;
Ok(CallToolResult::success(vec![content]))
} else {
let content = Content::json(json!({
"success": false,
"error": result.error,
"logs": result.logs
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
Err(err) => {
let content = Content::json(json!({
"success": false,
"error": err.to_string(),
"logs": []
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
}
#[tool(description = "执行TypeScript代码并返回结果")]
async fn run_typescript(
&self,
request: Parameters<CodeRunRequest>,
) -> Result<CallToolResult, McpError> {
let request = request.0;
match CodeExecutor::execute_with_params_compat(
&request.code,
LanguageScript::Ts,
request.params,
)
.await
{
Ok(result) => {
if result.success {
let content = Content::json(json!({
"result": result.result,
"logs": result.logs,
"success": true
}))?;
Ok(CallToolResult::success(vec![content]))
} else {
let content = Content::json(json!({
"success": false,
"error": result.error,
"logs": result.logs
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
Err(err) => {
let content = Content::json(json!({
"success": false,
"error": err.to_string(),
"logs": []
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
}
#[rmcp::tool(description = "执行Python代码并返回结果")]
async fn run_python(
&self,
request: Parameters<CodeRunRequest>,
) -> Result<CallToolResult, McpError> {
let request = request.0;
match CodeExecutor::execute_with_params_compat(
&request.code,
LanguageScript::Python,
request.params,
)
.await
{
Ok(result) => {
if result.success {
let content = Content::json(json!({
"result": result.result,
"logs": result.logs,
"success": true
}))?;
Ok(CallToolResult::success(vec![content]))
} else {
let content = Content::json(json!({
"success": false,
"error": result.error,
"logs": result.logs
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
Err(err) => {
let content = Content::json(json!({
"success": false,
"error": err.to_string(),
"logs": []
}))?;
Ok(CallToolResult::success(vec![content]))
}
}
}
}
impl ServerHandler for CodeRunnerService {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: ProtocolVersion::V_2024_11_05,
capabilities: ServerCapabilities::builder().enable_tools().build(),
server_info: Implementation::from_build_env(),
instructions: Some("一个支持执行JavaScript、TypeScript和Python代码的服务".to_string()),
}
}
}

View File

@@ -0,0 +1,3 @@
mod mcp_server;
pub use mcp_server::{CodeRunnerService,CodeRunRequest};

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>,
}

View File

@@ -0,0 +1,25 @@
WHITESPACE = _{ " " | "\t" }
alpha = { 'a'..'z' | 'A'..'Z' }
digit = { '0'..'9' }
module_name = @{ alpha ~ (alpha | digit | "_")* }
module_name_alias = @{ alpha ~ (alpha | digit | "_")* }
import_statement = ${ "import" ~ WHITESPACE+ ~ module_name}
from_import_statement = ${ "from" ~ WHITESPACE+ ~ module_name ~ WHITESPACE+ ~ ("import" ~ WHITESPACE+ ~ module_name_alias)? }
quote = {( "'" | "\"")}
importlib_statement = { module_name_alias? ~ "="? ~ "importlib.import_module" ~ "(" ~ WHITESPACE* ~ quote ~ module_name ~ quote ~ WHITESPACE* ~ ")" }
empty_line = _{ WHITESPACE* ~ ("\r\n" | "\n") }
line = _{ (!("\r\n" | "\n") ~ ANY)* ~ ("\r\n" | "\n") }
record = { from_import_statement| import_statement | importlib_statement | empty_line | line }
file = { SOI ~ record* ~ EOI }

View File

@@ -0,0 +1,3 @@
mod python_dependencies;
pub use python_dependencies::*;

View File

@@ -0,0 +1,406 @@
use anyhow::Result;
use log::{debug, info};
use pest::Parser;
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "python_imports.pest"] // 这里是你的pest语法文件
struct ImportParser;
// Python标准库模块列表使用静态变量定义
static PYTHON_STDLIB_MODULES: &[&str] = &[
"abc",
"argparse",
"array",
"ast",
"asyncio",
"atexit",
"base64",
"bdb",
"binascii",
"bisect",
"builtins",
"bz2",
"calendar",
"cmath",
"cmd",
"code",
"codecs",
"codeop",
"collections",
"colorsys",
"compileall",
"concurrent",
"configparser",
"contextlib",
"copy",
"copyreg",
"cProfile",
"csv",
"ctypes",
"curses",
"dataclasses",
"datetime",
"dbm",
"decimal",
"difflib",
"dis",
"doctest",
"email",
"encodings",
"ensurepip",
"enum",
"errno",
"faulthandler",
"fcntl",
"filecmp",
"fileinput",
"fnmatch",
"fractions",
"ftplib",
"functools",
"gc",
"getopt",
"getpass",
"gettext",
"glob",
"graphlib",
"grp",
"gzip",
"hashlib",
"heapq",
"hmac",
"html",
"http",
"idlelib",
"imaplib",
"importlib",
"inspect",
"io",
"ipaddress",
"itertools",
"json",
"keyword",
"linecache",
"locale",
"logging",
"lzma",
"mailbox",
"marshal",
"math",
"mimetypes",
"mmap",
"modulefinder",
"msvcrt",
"multiprocessing",
"netrc",
"numbers",
"operator",
"optparse",
"os",
"pathlib",
"pdb",
"pickle",
"pickletools",
"pkgutil",
"platform",
"plistlib",
"poplib",
"posix",
"pprint",
"profile",
"pstats",
"pty",
"pwd",
"py_compile",
"pyclbr",
"pydoc",
"queue",
"quopri",
"random",
"re",
"readline",
"reprlib",
"resource",
"rlcompleter",
"runpy",
"sched",
"secrets",
"select",
"selectors",
"shelve",
"shlex",
"shutil",
"signal",
"site",
"smtplib",
"socket",
"socketserver",
"sqlite3",
"ssl",
"stat",
"statistics",
"string",
"stringprep",
"struct",
"subprocess",
"sys",
"sysconfig",
"syslog",
"tabnanny",
"tarfile",
"tempfile",
"termios",
"test",
"textwrap",
"threading",
"time",
"timeit",
"tkinter",
"token",
"tokenize",
"tomllib",
"trace",
"traceback",
"tracemalloc",
"tty",
"turtle",
"turtledemo",
"types",
"typing",
"unicodedata",
"unittest",
"urllib",
"uuid",
"venv",
"warnings",
"wave",
"weakref",
"webbrowser",
"winreg",
"winsound",
"wsgiref",
"xml",
"xmlrpc",
"zipapp",
"zipfile",
"zipimport",
"zlib",
"zoneinfo",
];
pub fn parse_import(code: &str) -> Result<Vec<String>> {
let modules = parse_python_imports(code)?;
// 在非测试环境中,过滤标准库模块
let mut imports = Vec::new();
for module in modules {
if !is_standard_library(&module) {
imports.push(module);
}
}
Ok(imports)
}
// 解析Python代码中的import语句
fn parse_python_imports(python_code: &str) -> Result<Vec<String>> {
let input = if python_code.ends_with('\n') {
python_code.to_string()
} else {
format!("{python_code}\n")
};
debug!("Processing input: {input:?}");
let pairs = ImportParser::parse(Rule::file, &input)
.map_err(|e| anyhow::anyhow!("Failed to parse input: {}", e))?;
debug!("Initial parse successful");
let mut imported_modules = Vec::new();
for pair in pairs {
debug!("Top level rule: {:?}", pair.as_rule());
for record in pair.into_inner() {
debug!(
"Record rule: {:?}, text: {:?}",
record.as_rule(),
record.as_str()
);
// 遍历 record 的内部规则
for inner in record.into_inner() {
debug!(
"Inner rule: {:?}, text: {:?}",
inner.as_rule(),
inner.as_str()
);
match inner.as_rule() {
Rule::from_import_statement => {
for module in inner.into_inner() {
if let Rule::module_name = module.as_rule() {
imported_modules.push(module.as_str().to_string());
}
}
}
Rule::import_statement => {
// 遍历 import_statement 的内部规则找到 module_name
for module in inner.into_inner() {
if let Rule::module_name = module.as_rule() {
imported_modules.push(module.as_str().to_string());
}
}
}
Rule::importlib_statement => {
// 遍历 importlib_statement 的内部规则找到 module_name
for module in inner.into_inner() {
if let Rule::module_name = module.as_rule() {
imported_modules.push(module.as_str().to_string());
}
}
}
_ => {
info!("Unknown rule: {:?}", inner.as_rule());
}
}
}
}
}
debug!("Final result: {imported_modules:?}");
Ok(imported_modules)
}
// 判断是否为Python标准库模块
fn is_standard_library(module: &str) -> bool {
// 检查模块名是否在标准库列表中
PYTHON_STDLIB_MODULES.contains(&module)
}
#[cfg(test)]
mod tests {
use super::parse_import;
use anyhow::Result;
use log::{LevelFilter, info};
use std::sync::Once;
// 使用 Once 确保 env_logger 只初始化一次
static INIT: Once = Once::new();
fn setup() {
INIT.call_once(|| {
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
});
}
#[test]
fn test_line() -> Result<()> {
setup();
let python_code = "
import pandas as pd\n";
info!("Testing with input: {python_code:?}");
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["pandas"]);
Ok(())
}
#[test]
fn test_line2() -> Result<()> {
setup();
let python_code = "
numpy_alias = importlib.import_module('numpy')";
info!("Testing with input: {python_code:?}");
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["numpy"]);
Ok(())
}
#[test]
fn test_parse_import_bs() -> Result<()> {
setup();
let python_code = r#"
import requests
from bs4 import BeautifulSoup
# 发送请求到百度
url = 'https://www.baidu.com'
response = requests.get(url)
# 检查请求是否成功
if response.status_code == 200:
# 解析网页内容
soup = BeautifulSoup(response.text, 'html.parser')
# 提取你想要的信息,例如标题
title = soup.title.string
print(f"网页标题: {title}")
else:
print(f"请求失败,状态码: {response.status_code}")
"#;
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["requests", "bs4"]);
Ok(())
}
#[test]
fn test_parse_import() -> Result<()> {
setup();
let python_code = r#"
import pandas as pd
import logging
# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
import importlib
# 使用 importlib 动态导入 numpy
numpy = importlib.import_module('numpy')
# 创建一个简单的 numpy 数组
arr = numpy.array([1, 2, 3, 4, 5])
# 记录数组信息
logging.info(f"Created numpy array: {arr}")
# 进行一些简单的操作
sum_arr = numpy.sum(arr)
logging.info(f"Sum of array: {sum_arr}")
# 创建一个简单的 DataFrame
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35]
}
df = pd.DataFrame(data)
# 记录 DataFrame 信息
logging.info(f"Created DataFrame:\n{df}")
def main(args: dict) -> dict:
logging.info(f"input args: {args}")
params = args.get("params")
# 构建输出对象
ret = {
"key0": params['input'], # 拼接两次入参 input 的值
"key1": ["hello", "world"], # 输出一个数组
"key2": { # 输出一个Object
"key21": "hi"
},
}
return ret
"#;
let imported_modules = parse_import(python_code)?;
info!("Parsed modules: {imported_modules:?}");
assert_eq!(imported_modules, vec!["pandas", "numpy"]);
Ok(())
}
}

View File

@@ -0,0 +1,5 @@
mod dependencies;
mod python_runner;
pub use dependencies::parse_import;
pub use python_runner::PythonRunner;

View File

@@ -0,0 +1,200 @@
//通过 uv 命令,来运行 python脚本
use crate::{
cache::CodeFileCache,
model::{CodeExecutor, CodeScriptExecutionResult, CommandExecutor, LanguageScript, RunCode},
python_runner::parse_import,
};
use anyhow::{Context, Result};
use log::{debug, error, info, warn};
use std::io::Write;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
#[derive(Default)]
pub struct PythonRunner;
//定义国内python加速地址: https://mirrors.aliyun.com/pypi/simple
const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple";
impl RunCode for PythonRunner {
async fn run_with_params(
&self,
code: &str,
params: Option<serde_json::Value>,
timeout_seconds: Option<u64>,
) -> Result<CodeScriptExecutionResult> {
debug!("开始执行Python脚本...,执行参数: {params:?}");
// 根据 code ,获取对应的hash, 对用户脚本代码,使用胶水代码处理后,缓存到文件系统里,下次使用如果hash相同,直接使用
let hash = CodeFileCache::obtain_code_hash(code);
let cache_exist =
CodeFileCache::check_code_file_cache_exisht(&hash, &LanguageScript::Python).await;
let run_code_script_file_tuple = if cache_exist {
// 从缓存中读取代码
let cache_code =
CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await;
debug!("从缓存中读取代码:hash值 {:?}", &hash);
cache_code?
} else {
// 分析用户python代码依赖
let dependencies = parse_import(code)?;
// 准备Python代码添加日志捕获和handler函数执行逻辑
let wrapped_code = self.prepare_python_code(code, true);
// 缓存代码
CodeFileCache::save_code_file_cache(&hash, &wrapped_code, &LanguageScript::Python)
.await?;
//保存后,获取文件
let code_script_file_tuple =
CodeFileCache::get_code_file_cache(&hash, &LanguageScript::Python).await?;
let run_code_script_file_path = code_script_file_tuple.1.clone();
// 按照 uv命令的规范,添加用户脚本所需的依赖
// uv add --script example.py 'requests<3' 'rich' 这是添加依赖的参考命令,dependencies 是解析出来的依赖列表
// 执行添加依赖命令
if !dependencies.is_empty() {
info!("正在添加依赖: {dependencies:?}");
let mut cmd = Command::new("uv");
cmd.arg("add")
.arg("--script")
.arg(&run_code_script_file_path);
//添加 python加速地址
cmd.arg("--default-index").arg(PYTHON_ACCELERATION_ADDRESS);
// 为每个依赖添加一个参数
for dep in &dependencies {
cmd.arg(dep);
}
// 打印 cmd 命令,可以直接复制执行的命令字符串
let cmd_str = format!("{:?}", &cmd);
info!("uv命令字符串: {cmd_str}");
let cmd_output = match cmd.kill_on_drop(true).output().await {
Ok(output) => output,
Err(e) => {
error!("安装Python依赖失败: {e:?}");
error!("失败的命令: {cmd:?}");
return Err(e).context("Failed to add dependencies with uv");
}
};
let stdout = String::from_utf8_lossy(&cmd_output.stdout).to_string();
let stderr = String::from_utf8_lossy(&cmd_output.stderr).to_string();
info!("添加依赖结果 - stdout: {stdout}");
info!("添加依赖结果 - stderr: {stderr}");
if !cmd_output.status.success() {
warn!("添加依赖失败,状态码: {}", cmd_output.status);
}
}
debug!("创建脚本缓存:hash值 {:?}", &hash);
code_script_file_tuple
};
let temp_path = run_code_script_file_tuple.1;
// 使用uv run命令执行Python脚本提供隔离环境
let mut execute_command = Command::new("uv");
//还需要指定国内镜像地址,参考示例: uv run -s -p 3.13 d5ebe48b7d9da8cb835af6ef77b212921f9a44881fb232837b4dcc6ebecf9401.py --default-index https://mirrors.aliyun.com/pypi/simple
execute_command
.arg("run")
.arg("-s") // 明确指定作为脚本运行
// .arg("-p")
// .arg("3.13") // 指定Python解释器版本3.13
.arg("--default-index")
.arg(PYTHON_ACCELERATION_ADDRESS)
.arg(&temp_path)
.kill_on_drop(true);
// 处理参数:统一使用临时文件传递
let temp_input_path = if let Some(params) = params {
let params_json = serde_json::to_string(&params)?;
// 创建临时文件写入参数
let temp_dir = tempfile::TempDir::new()?;
let temp_file_path = temp_dir.path().join("input_params.json");
// 写入参数到临时文件
std::fs::write(&temp_file_path, params_json.as_bytes())?;
// 保持TempDir存在这样文件就不会被删除
let temp_dir_path = temp_dir.path().to_path_buf();
std::mem::forget(temp_dir);
// 设置环境变量指向临时文件
execute_command.env("INPUT_JSON_FILE", &temp_file_path);
debug!("使用临时文件传递参数,文件路径: {:?}", temp_file_path);
Some(temp_file_path)
} else {
// 没有参数时设置空对象
execute_command.env("INPUT_JSON", "{}");
None
};
info!("执行命令: {:?}", &execute_command);
// let tokio_child_command = TokioHeapSize::default();
// // 设置堆大小限制
// tokio_child_command
// .with_heap_limit(&mut execute_command)
// .await;
//限制command 的执行超时时间
let executor = match timeout_seconds {
Some(timeout) => CommandExecutor::with_timeout(execute_command.output(), timeout),
None => CommandExecutor::default(execute_command.output()),
};
let executor_result = executor.await;
let output = match executor_result {
Ok(cmd_result) => match cmd_result {
Ok(output) => output,
Err(e) => {
error!("Python命令执行失败: {e:?}");
return Err(e.into());
}
},
Err(e) => {
error!("Python任务执行异常: {e:?}");
return Err(e.into());
}
};
// 调试输出
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
debug!("Python stdout: {stdout}");
debug!("Python stderr: {stderr}");
// 执行完成后删除临时文件和目录
if let Some(temp_file_path) = temp_input_path {
// 删除文件
let _ = fs::remove_file(&temp_file_path).await;
// 尝试删除父目录(如果为空)
if let Some(parent) = temp_file_path.parent() {
let _ = fs::remove_dir(parent).await;
}
debug!("已删除临时文件: {:?}", temp_file_path);
}
// 解析输出
CodeExecutor::parse_execution_output(&output.stdout, &output.stderr).await
}
}
impl PythonRunner {
/// 准备Python代码添加日志捕获和handler函数执行逻辑
fn prepare_python_code(&self, code: &str, show_logs: bool) -> String {
let show_logs_value = if show_logs { "True" } else { "False" };
let template = include_str!("../templates/python_template.py");
template
.replace("{{USER_CODE}}", code)
.replace("{{SHOW_LOGS}}", show_logs_value)
}
}

View File

@@ -0,0 +1,166 @@
use anyhow::{Context, Result};
use clap::Parser;
use log::{error, info};
use rmcp::ServiceExt;
use tokio::io::{stdin, stdout};
mod app_error;
mod cache;
mod deno_runner;
mod mcp;
mod model;
mod python_runner;
use mcp::CodeRunnerService;
/// MCP脚本运行器 - 通过MCP协议执行JavaScript、TypeScript和Python代码
#[derive(Parser)]
#[command(name = "script_runner")]
#[command(author = "MCP Script Runner")]
#[command(version = "0.1.0")]
#[command(about = "通过MCP协议执行JavaScript、TypeScript和Python代码", long_about = None)]
struct Cli {
/// 启用详细日志输出
#[arg(short, long)]
verbose: bool,
}
/// 启动MCP服务器
async fn start_mcp_server(verbose: bool) -> Result<()> {
if verbose {
info!("初始化 MCP 服务...");
}
// 创建服务实例
let service = CodeRunnerService::default();
// 使用标准输入输出作为传输方式
let transport = (stdin(), stdout());
if verbose {
info!("MCP 服务已启动,等待连接...");
}
// 启动服务
let server = service.serve(transport).await.context("启动MCP服务失败")?;
// 等待服务结束
let result = server.waiting().await;
if verbose {
match &result {
Ok(reason) => error!("MCP 服务已停止: {:?}", reason),
Err(err) => error!("MCP 服务出错: {}", err),
}
}
result.map(|_| ()).map_err(Into::into)
}
#[tokio::main]
async fn main() -> Result<()> {
// 解析命令行参数
let cli = Cli::parse();
// 启动MCP服务
start_mcp_server(cli.verbose).await
}
#[cfg(test)]
mod tests {
use super::*;
use rmcp::{model::CallToolRequestParam, ServiceExt};
use tokio::sync::oneshot;
use tokio::time::timeout;
use std::time::Duration;
#[tokio::test]
#[ignore = "MCP client test requires proper service implementation - TODO: fix later"]
async fn test_start_mcp_server() -> Result<()> {
// 创建管道,模拟标准输入输出
let (client_stream, server_stream) = tokio::io::duplex(8192);
// 分割成读写部分
let (server_read, server_write) = tokio::io::split(server_stream);
let (client_read, client_write) = tokio::io::split(client_stream);
// 使用通道控制服务器生命周期
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
// 在单独的任务中启动服务器
let server_task = tokio::spawn(async move {
// 创建服务实例
let service = CodeRunnerService::default();
// 创建服务Future并pin
let service_fut = service.serve((server_read, server_write));
tokio::pin!(service_fut);
// 等待关闭信号或服务器完成
tokio::select! {
res = &mut service_fut => {
match res {
Ok(server) => {
match server.waiting().await {
Ok(reason) => println!("服务正常结束: {:?}", reason),
Err(e) => println!("服务错误: {}", e),
}
}
Err(e) => println!("启动服务失败: {}", e),
}
}
_ = shutdown_rx => {
println!("收到关闭信号");
}
}
});
// 给服务器一点时间启动
tokio::time::sleep(Duration::from_millis(200)).await;
// 使用RMCP客户端SDK连接到服务器
let client = ().serve((client_read, client_write)).await?;
// 获取服务器信息
let server_info = client.peer_info();
println!("服务器信息: {:?}", server_info);
// 验证服务器信息
assert!(!server_info.unwrap().instructions.is_none(), "服务器应该提供说明");
// 测试执行JavaScript代码
let js_code = "function handler(input) { return {success: true, message: 'JavaScript测试成功'}; }";
let result = client.call_tool(CallToolRequestParam {
name: "run_javascript".into(),
arguments: serde_json::json!({
"code": js_code,
}).as_object().cloned(),
}).await?;
// 验证JavaScript结果这里只简单验证调用成功
println!("JavaScript执行结果: {:?}", result);
// 测试执行Python代码
let py_code = "def handler(input):\n return {'success': True, 'message': 'Python测试成功'}";
let result = client.call_tool(CallToolRequestParam {
name: "run_python".into(),
arguments: serde_json::json!({
"code": py_code,
}).as_object().cloned(),
}).await?;
// 验证Python结果这里只简单验证调用成功
println!("Python执行结果: {:?}", result);
// 关闭客户端
client.cancel().await?;
// 发送服务器关闭信号
let _ = shutdown_tx.send(());
// 等待服务器任务结束
let _ = timeout(Duration::from_secs(5), server_task).await;
Ok(())
}
}

View File

@@ -0,0 +1,92 @@
// @ts-nocheck
// ES模块格式支持import/export语句
// 保存原始console.log
const originalConsoleLog = console.log;
let logs = [];
// 替换console.log以捕获日志
console.log = function() {
// 将参数转换为字符串并连接它们
const message = Array.from(arguments).map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
// 存储日志
logs.push(message);
// 如果显示日志,也输出到原始控制台
if ({{SHOW_LOGS}}) {
originalConsoleLog.apply(console, arguments);
}
};
// 异步读取输入参数的函数
async function readInputParams() {
let input = {};
try {
const inputFile = Deno.env.get("INPUT_JSON_FILE");
if (inputFile) {
const inputJson = await Deno.readTextFile(inputFile);
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
} else {
// 兼容旧的环境变量方式
const inputJson = Deno.env.get("INPUT_JSON");
if (inputJson) {
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
}
}
} catch (error) {
console.error("解析输入参数失败:", error);
}
return input;
}
// 用户代码
{{USER_CODE}}
// 执行handler函数并获取结果
let result = null;
// 异步立即执行函数
(async () => {
// 读取输入参数
const input = await readInputParams();
try {
// 优先检查main函数
if (typeof main === 'function') {
// 检查main是否是异步函数
if (main.constructor.name === 'AsyncFunction') {
result = await main(input);
} else {
result = main(input);
}
} else if (typeof handler === 'function') {
// 如果没有main函数检查handler
if (handler.constructor.name === 'AsyncFunction') {
result = await handler(input);
} else {
result = handler(input);
}
}else{
throw new Error("没有找到main或handler函数");
}
// 打印最终输出为JSON
originalConsoleLog(JSON.stringify({
logs: logs,
result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null,
error: null
}));
} catch (error) {
// 处理错误
originalConsoleLog(JSON.stringify({
logs: logs,
result: null,
error: error.toString()
}));
}
})();

View File

@@ -0,0 +1,93 @@
// @ts-nocheck
// 普通脚本格式
// 保存原始console.log
const originalConsoleLog = console.log;
let logs = [];
// 替换console.log以捕获日志
console.log = function() {
// 将参数转换为字符串并连接它们
const message = Array.from(arguments).map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
// 存储日志
logs.push(message);
// 如果显示日志,也输出到原始控制台
if ({{SHOW_LOGS}}) {
originalConsoleLog.apply(console, arguments);
}
};
// 异步读取输入参数的函数
async function readInputParams() {
let input = {};
try {
const inputFile = Deno.env.get("INPUT_JSON_FILE");
if (inputFile) {
const inputJson = await Deno.readTextFile(inputFile);
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
} else {
// 兼容旧的环境变量方式
const inputJson = Deno.env.get("INPUT_JSON");
if (inputJson) {
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
}
}
} catch (error) {
console.error("解析输入参数失败:", error);
}
return input;
}
// 异步立即执行函数
(async () => {
// 读取输入参数
const input = await readInputParams();
try {
// 用户代码开始
{{USER_CODE}}
// 用户代码结束
// 执行函数并获取结果
let result = null;
// 优先检查main函数
if (typeof main === 'function') {
// 检查main是否是异步函数
if (main.constructor.name === 'AsyncFunction') {
result = await main(input);
} else {
result = main(input);
}
} else if (typeof handler === 'function') {
// 如果没有main函数检查handler
if (handler.constructor.name === 'AsyncFunction') {
result = await handler(input);
} else {
result = handler(input);
}
} else {
throw new Error("没有找到main或handler函数");
}
// 打印最终输出为JSON
originalConsoleLog(JSON.stringify({
logs: logs,
result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null,
error: null
}));
} catch (error) {
// 处理错误
originalConsoleLog(JSON.stringify({
logs: logs,
result: null,
error: error.toString()
}));
}
})();

View File

@@ -0,0 +1,134 @@
import sys
import json
import os
import logging
from io import StringIO
# 保存原始的stdout
original_stdout = sys.stdout
logs = []
# 创建一个StringIO对象来捕获输出
class LogCapture:
def __init__(self, show_logs=False):
self.buffer = StringIO()
self.show_logs = show_logs
def write(self, text):
if text.strip(): # 忽略空行
logs.append(text.rstrip())
if self.show_logs:
original_stdout.write(text)
def flush(self):
self.buffer.flush()
if self.show_logs:
original_stdout.flush()
# 替换sys.stdout为我们的捕获器
sys.stdout = LogCapture(show_logs={{SHOW_LOGS}})
# 配置logging将日志输出重定向到我们的捕获器
class LoggingHandler(logging.Handler):
def emit(self, record):
msg = self.format(record)
print(f"[{record.levelname}] {msg}")
# 配置根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
# 移除所有现有的处理程序
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# 添加我们自定义的处理程序
handler = LoggingHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
root_logger.addHandler(handler)
# 从环境变量获取输入参数
args = {}
has_input = False
try:
# 优先从临时文件读取参数
input_file = os.environ.get('INPUT_JSON_FILE')
if input_file:
with open(input_file, 'r', encoding='utf-8') as f:
input_json = f.read()
args = json.loads(input_json)
has_input = True
print(f"接收到的参数: {args}")
else:
# 兼容旧的环境变量方式
input_json = os.environ.get('INPUT_JSON')
if input_json:
args = json.loads(input_json)
has_input = True
print(f"接收到的参数: {args}")
# 确保参数同时可以通过args直接访问也可以通过args.get("params")访问
# 如果args中没有params键但有其他键则将整个args作为params的值
if has_input and "params" not in args and len(args) > 0:
args["params"] = args.copy()
# 如果params存在但为None则初始化为空字典
elif has_input and args.get("params") is None:
args["params"] = {}
except Exception as e:
print(f"解析输入参数失败: {e}")
# 用户代码开始
{{USER_CODE}}
try:
# 执行handler函数或main函数并获取结果
result = None
if 'handler' in globals() and callable(globals()['handler']):
# 优先使用 handler 函数
try:
if has_input:
result = handler(args)
else:
# handler 函数不接受参数,无参数调用
result = handler()
# 确保结果不为 None
if result is None:
print("警告: handler 函数返回了 None")
except Exception as e:
print(f"执行 handler 函数时出错: {e}")
elif 'main' in globals() and callable(globals()['main']):
# 如果没有 handler 函数,尝试使用 main 函数
try:
result = main(args)
except Exception as e:
print(f"执行 main 函数时出错: {e}")
# 打印最终输出为JSON
sys.stdout = original_stdout
# 根据结果类型选择合适的处理方式
result_json = None
if result is not None:
if isinstance(result, (dict, list)):
# 如果是字典或列表,直接使用 json.dumps 序列化
result_json = json.dumps(result)
elif isinstance(result, (int, float, bool)) or result is None:
# 如果是基本类型,直接传递给外层 JSON
result_json = result
else:
# 其他类型(如字符串)转换为字符串
result_json = str(result)
print(json.dumps({
'logs': logs,
'result': result_json,
'error': None
}))
except Exception as e:
# 处理错误
import traceback
error_msg = f"{str(e)}\n{traceback.format_exc()}"
# 处理错误
sys.stdout = original_stdout
print(json.dumps({
'logs': logs,
'result': None,
'error': error_msg
}))

View File

@@ -0,0 +1,96 @@
// TypeScript类型声明
// @ts-nocheck
type LogFunction = (...args: any[]) => void;
type Handler = (input: any) => any;
// Save original console.log
const originalConsoleLog: LogFunction = console.log;
let logs: string[] = [];
// Replace console.log to capture logs
console.log = function(...args: any[]): void {
// Convert arguments to string and join them
const message = args.map(arg =>
typeof arg === 'object' && arg !== null ? JSON.stringify(arg) : String(arg)
).join(' ');
// Store log
logs.push(message);
// Also log to original console if showing logs
if ({{SHOW_LOGS}}) {
originalConsoleLog.apply(console, args);
}
};
// 异步读取输入参数的函数
async function readInputParams(): Promise<any> {
let input: any = {};
try {
const inputFile = Deno.env.get("INPUT_JSON_FILE");
if (inputFile) {
const inputJson = await Deno.readTextFile(inputFile);
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
} else {
// 兼容旧的环境变量方式
const inputJson = Deno.env.get("INPUT_JSON");
if (inputJson) {
input = JSON.parse(inputJson);
console.log("接收到的参数:", JSON.stringify(input));
}
}
} catch (error) {
console.error("解析输入参数失败:", error);
}
return input;
}
async function executeHandler() {
try {
// Add the original code
{{USER_CODE}}
// 读取输入参数
const input = await readInputParams();
// Execute handler function and get result
let result: any = null;
// 优先检查main函数
if (typeof main === 'function') {
// 检查main是否是异步函数
if (main.constructor.name === 'AsyncFunction') {
result = await (main as (input: any) => Promise<any>)(input);
} else {
result = (main as Handler)(input);
}
} else if (typeof handler === 'function') {
// 如果没有main函数检查handler
if (handler.constructor.name === 'AsyncFunction') {
result = await (handler as (input: any) => Promise<any>)(input);
} else {
result = (handler as Handler)(input);
}
} else {
throw new Error("没有找到main或handler函数");
}
// Print final output as JSON
originalConsoleLog(JSON.stringify({
logs: logs,
result: result !== undefined ? (typeof result === 'object' ? JSON.stringify(result) : String(result)) : null,
error: null
}));
} catch (error) {
// Handle errors
originalConsoleLog(JSON.stringify({
logs: logs,
result: null,
error: String(error)
}));
}
}
// 执行并等待结果
executeHandler();

View File

@@ -0,0 +1,740 @@
#[cfg(test)]
mod js_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_js_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js.js")?;
info!("读取测试脚本: test_js.js");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
Ok(())
}
#[tokio::test]
async fn test_js_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js_params.js")?;
info!("读取测试脚本: test_js_params.js");
// 准备参数
let params = json!({
"a": 10,
"b": 20,
"name": "测试用户"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("sum"), "结果应包含 sum 字段");
assert!(json_str.contains("greeting"), "结果应包含 greeting 字段");
assert!(json_str.contains("message"), "结果应包含 message 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_object_result() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_test1.js")?;
info!("读取测试脚本: rfunction_test1.js");
// 准备参数
let params = json!({
"a": 1,
"b": 2
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("3"), "message 字段应为 3");
}
Ok(())
}
#[tokio::test]
async fn test_js_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的JavaScript脚本
let code = r#"
// 创建一个长时间运行的脚本
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function handler(input) {
console.log("开始执行耗时操作");
// 这个操作会运行10秒钟
for (let i = 0; i < 10; i++) {
console.log(`已经执行了 ${i+1} 秒`);
await sleep(1000); // 等待1秒
}
console.log("操作完成");
return { result: "完成" };
}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置2秒超时并执行脚本
info!("开始执行JavaScript脚本设置2秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Js, Some(params), Some(2))
.await;
let elapsed = start_time.elapsed();
// 检查是否在2-3秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒");
assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_js_cow_say_hello() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/cow_say_hello.js")?;
info!("读取测试脚本: cow_say_hello.js");
// 准备参数
let params = json!({
"a": 5,
"b": 7
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params(&code, LanguageScript::Js, Some(params), None)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
// 检查计算结果是否正确 (5 + 7 = 12)
if let Some(obj) = result_val.as_object() {
if let Some(message) = obj.get("message") {
assert_eq!(message, 12, "message 字段的值应为 12");
}
}
}
Ok(())
}
#[tokio::test]
async fn test_js_import_deno_std() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_deno_std_example.js")?;
info!("读取测试脚本: import_deno_std_example.js");
// 准备参数
let params = json!({
"path": "test.txt"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("pathInfo"), "结果应包含 pathInfo 字段");
assert!(
json_str.contains("fileContent"),
"结果应包含 fileContent 字段"
);
}
Ok(())
}
#[tokio::test]
async fn test_js_import_jsr() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_jsr_example.js")?;
info!("读取测试脚本: import_jsr_example.js");
// 准备参数
let params = json!({
"a": 10,
"b": 5,
"expected": 15
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("results"), "结果应包含 results 字段");
assert!(json_str.contains("summary"), "结果应包含 summary 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_local_module() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_local_module_example.js")?;
info!("读取测试脚本: import_local_module_example.js");
// 准备参数
let params = json!({
"order": {
"items": [
{ "name": "测试产品", "price": 200, "quantity": 2 }
],
"taxRate": 0.05
}
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("order"), "结果应包含 order 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_axios() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_axios_example.js")?;
info!("读取测试脚本: import_axios_example.js");
// 准备参数
let params = json!({
"url": "https://jsonplaceholder.typicode.com/todos/1"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("data"), "结果应包含 data 字段");
assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_esm() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_esm_module_example.js")?;
info!("读取测试脚本: import_esm_module_example.js");
// 准备参数
let params = json!({
"data": "测试数据",
"salt": "test-salt-123"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(json_str.contains("result"), "结果应包含 result 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_import_lodash() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/import_lodash_example.js")?;
info!("读取测试脚本: import_lodash_example.js");
// 准备参数
let params = json!({
"data": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"chunkSize": 3
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("message"), "结果应包含 message 字段");
assert!(
json_str.contains("originalData"),
"结果应包含 originalData 字段"
);
assert!(
json_str.contains("processedData"),
"结果应包含 processedData 字段"
);
assert!(json_str.contains("timestamp"), "结果应包含 timestamp 字段");
}
Ok(())
}
#[tokio::test]
async fn test_js_main_function() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_js_test3.js")?;
info!("读取测试脚本: rfunction_js_test3.js");
// 准备参数
let params = json!({
"testKey": "测试值"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行JavaScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值
if let Some(result_val) = result.result {
// 检查结果是否包含预期的字段
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("key"), "结果应包含 key 字段");
assert!(json_str.contains("value"), "key 字段的值应为 value");
// 检查JSON结构
if let Some(obj) = result_val.as_object() {
if let Some(key_value) = obj.get("key") {
assert_eq!(key_value, "value", "key 字段的值应为 value");
}
}
}
Ok(())
}
#[tokio::test]
async fn test_js_large_parameters() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_js_large_params.js")?;
info!("读取测试脚本: test_js_large_params.js");
// 生成大于2MB的文本参数
fn generate_large_text(size_in_mb: usize) -> String {
let chunk = "This is a test chunk of text designed to generate large parameters for testing the Argument list too long fix. ".repeat(100);
let iterations = (size_in_mb * 1024 * 1024) / chunk.len();
let mut result = String::new();
for i in 0..iterations {
result.push_str(&chunk);
result.push_str(&format!("Iteration {}\n", i));
}
result
}
let large_text = generate_large_text(3); // 3MB
info!("生成了3MB大小的文本参数实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0);
// 准备参数
let params = json!({
"largeText": large_text
});
info!("准备测试参数JSON大小: {:.2} MB", serde_json::to_string(&params).unwrap().len() as f64 / 1024.0 / 1024.0);
// 执行脚本
info!("开始执行JavaScript脚本测试大参数处理...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Js, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含成功处理大参数的信息
if let Some(result_val) = result.result {
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("success"), "结果应包含 success 字段");
assert!(json_str.contains("true"), "success 应为 true");
assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息");
assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段");
// 验证返回值是对象格式
if let Some(obj) = result_val.as_object() {
if let Some(success) = obj.get("success") {
assert_eq!(success, true, "success 字段的值应为 true");
}
if let Some(text_size) = obj.get("textSize") {
assert!(text_size.is_number(), "textSize 应为数字");
let size = text_size.as_u64().unwrap();
assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB");
}
}
}
info!("大参数测试通过!临时文件解决方案有效。");
Ok(())
}
}

View File

@@ -0,0 +1,90 @@
#[cfg(test)]
pub mod test_utils {
use log::LevelFilter;
use log::info;
use std::fs;
use std::path::Path;
use std::sync::Once;
// 使用 Once 确保 env_logger 只初始化一次
static INIT: Once = Once::new();
// 控制是否清理缓存的环境变量名
const CLEAN_CACHE_ENV: &str = "CLEAN_TEST_CACHE";
pub fn setup() {
INIT.call_once(|| {
// 尝试初始化日志,如果失败则忽略
let _ = std::panic::catch_unwind(|| {
env_logger::builder().filter_level(LevelFilter::Info).init();
});
});
// 只有当环境变量设置为"1"时才清理缓存
if let Ok(clean_cache) = std::env::var(CLEAN_CACHE_ENV) {
if clean_cache == "1" {
clean_cache_dir();
}
}
}
// 清理缓存目录函数
fn clean_cache_dir() {
let cache_dir = Path::new("/tmp/code_cache");
// 如果缓存目录存在,清理其中的文件
if cache_dir.exists() {
info!("正在清理缓存目录: {cache_dir:?}");
match fs::read_dir(cache_dir) {
Ok(entries) => {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.is_file() {
if let Err(e) = fs::remove_file(&path) {
info!("删除文件失败 {path:?}: {e}");
} else {
info!("已删除缓存文件: {path:?}");
}
}
}
}
info!("缓存目录清理完成");
}
Err(e) => {
info!("读取缓存目录失败: {e}");
}
}
} else {
// 如果缓存目录不存在,创建它
info!("缓存目录不存在,创建目录: {cache_dir:?}");
if let Err(e) = fs::create_dir_all(cache_dir) {
info!("创建缓存目录失败: {e}");
} else {
info!("缓存目录创建成功");
}
}
// 确保缓存目录存在且可写
if !cache_dir.exists() {
if let Err(e) = fs::create_dir_all(cache_dir) {
info!("创建缓存目录失败: {e}");
}
}
// 设置目录权限为777所有用户可读写执行
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = fs::set_permissions(cache_dir, fs::Permissions::from_mode(0o775)) {
info!("设置缓存目录权限失败: {e}");
} else {
info!("设置缓存目录权限成功");
}
}
}
}
pub mod js_tests;
pub mod python_tests;
pub mod ts_tests;

View File

@@ -0,0 +1,591 @@
#[cfg(test)]
mod python_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_python_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python.py")?;
info!("读取测试脚本: test_python.py");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
// 检查日志中是否包含预期的输出
let logs_str = result.logs.join(" ");
assert!(
logs_str.contains("Handler function called"),
"日志应包含 'Handler function called'"
);
assert!(
logs_str.contains("Final calculation completed"),
"日志应包含 'Final calculation completed'"
);
assert!(
logs_str.contains("The product of [1, 2, 3, 4, 5] is 120"),
"日志应包含计算结果"
);
Ok(())
}
#[tokio::test]
async fn test_python_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_params.py")?;
info!("读取测试脚本: test_python_params.py");
// 准备参数
let params = json!({
"a": 10,
"b": 20
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证返回值包含预期的字段
if let Some(result_val) = result.result {
// 结果应该是 JSON 对象Python 字典被正确解析)
assert!(result_val.get("sum").is_some(), "结果应包含 sum 字段");
assert!(result_val.get("numbers").is_some(), "结果应包含 numbers 字段");
assert!(result_val.get("message").is_some(), "结果应包含 message 字段");
}
Ok(())
}
#[tokio::test]
async fn test_python_different_return_types() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_types.py")?;
info!("读取测试脚本: test_python_types.py");
// 测试字符串类型
let params = json!({"type": "string"});
info!("测试字符串类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
assert!(result_val.is_string(), "结果应为字符串类型");
}
// 测试数字类型
let params = json!({"type": "number"});
info!("测试数字类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
assert!(result_val.is_number(), "结果应为数字类型");
assert_eq!(result_val.as_i64().unwrap(), 12345, "结果应为 12345");
}
// 测试布尔类型
let params = json!({"type": "boolean"});
info!("测试布尔类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
assert!(result_val.is_boolean(), "结果应为布尔类型");
assert!(result_val.as_bool().unwrap(), "结果应为 true");
}
// 测试列表类型
let params = json!({"type": "list"});
info!("测试列表类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
// 结果应该是数组类型Python列表被正确解析
assert!(result_val.is_array(), "结果应为数组类型");
assert_eq!(result_val.as_array().unwrap().len(), 6, "数组长度应为 6");
}
// 测试字典类型
let params = json!({"type": "dict"});
info!("测试字典类型, 参数: {params:?}");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
if let Some(result_val) = &result.result {
// 结果应该是对象类型Python字典被正确解析
assert!(result_val.is_object(), "结果应为对象类型");
assert!(result_val.get("name").is_some(), "结果应包含 name 字段");
assert!(result_val.get("age").is_some(), "结果应包含 age 字段");
assert!(result_val.get("tags").is_some(), "结果应包含 tags 字段");
}
// 测试 None 类型
let params = json!({"type": "null"});
info!("测试 None 类型, 参数: {params:?}");
let _result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
Ok(())
}
// 此测试使用标准库替代pandas不再需要忽略
#[tokio::test]
async fn test_python_with_pandas() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/rfunction_test2.py")?;
info!("读取测试脚本: rfunction_test2.py");
// 检查依赖识别
let dependencies = crate::python_runner::parse_import(&code)?;
info!("识别到的依赖: {dependencies:?}");
// 验证依赖识别结果 - 应该包含pandas依赖
assert!(!dependencies.is_empty(), "依赖列表不应为空");
assert!(
dependencies.contains(&"pandas".to_string()),
"依赖列表应包含pandas"
);
// 准备参数
let params = json!({
"params": {
"input": "测试数据"
}
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
// 如果有执行错误,我们只验证依赖识别是否正确,不再检查执行结果
// 这是因为pandas可能未安装或环境问题导致执行失败
return Ok(());
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 如果脚本执行成功,才验证结果
// 验证日志和结果
if result.logs.is_empty() {
info!("警告: 日志为空,但脚本执行成功");
} else {
info!("捕获到的日志数量: {}", result.logs.len());
// 逐条打印日志
for (i, log) in result.logs.iter().enumerate() {
info!("日志[{i}]: {log}");
}
// 检查日志中是否包含logging.info的输出
let logs_str = result.logs.join(" ");
info!("合并后的日志字符串: {logs_str}");
assert!(
logs_str.contains("Created data structure"),
"日志应包含'Created data structure'"
);
assert!(logs_str.contains("input args"), "日志应包含'input args'");
}
// 如果有结果,验证返回值包含预期的字段
if let Some(result_val) = result.result {
// 结果应该是JSON对象Python字典被正确解析
assert!(result_val.get("key0").is_some(), "结果应包含 key0 字段");
assert_eq!(
result_val["key0"].as_str().unwrap(),
"测试数据",
"key0 应等于输入参数"
);
assert!(result_val.get("key1").is_some(), "结果应包含 key1 字段");
assert!(result_val["key1"].is_array(), "key1 应为数组");
assert!(result_val.get("key2").is_some(), "结果应包含 key2 字段");
assert!(result_val["key2"].is_object(), "key2 应为对象");
assert!(result_val["key2"].get("key21").is_some(), "key2.key21 应存在");
}
Ok(())
}
#[tokio::test]
async fn test_python_params_access() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_simple.py")?;
info!("读取测试脚本: test_python_simple.py");
// 准备参数 - 直接提供input参数
let params = json!({
"input": "直接提供的参数"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result = CodeExecutor::execute_with_params_compat(
&code,
LanguageScript::Python,
Some(params.clone()),
)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
panic!("执行出错: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
if let Some(result_str) = result_val.as_str() {
let json_val = serde_json::from_str::<serde_json::Value>(result_str)?;
// 验证两种访问方式都能获取到参数
assert_eq!(
json_val["direct_access"], "直接提供的参数",
"直接访问应该能获取到参数"
);
assert_eq!(
json_val["nested_access"], "直接提供的参数",
"嵌套访问也应该能获取到参数"
);
// 验证args结构中同时包含直接参数和params嵌套参数
assert!(
json_val["args_structure"].get("input").is_some(),
"args结构应包含直接参数"
);
assert!(
json_val["args_structure"].get("params").is_some(),
"args结构应包含params参数"
);
}
}
// 准备参数 - 通过params嵌套提供
let nested_params = json!({
"params": {
"input": "嵌套提供的参数"
}
});
info!("准备嵌套测试参数: {nested_params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result = CodeExecutor::execute_with_params_compat(
&code,
LanguageScript::Python,
Some(nested_params),
)
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
panic!("执行出错: {error}");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
if let Some(result_str) = result_val.as_str() {
let json_val = serde_json::from_str::<serde_json::Value>(result_str)?;
// 验证嵌套参数可以被访问
assert_eq!(
json_val["nested_access"], "嵌套提供的参数",
"应该能通过params.input访问到参数"
);
// 验证args结构
assert!(
json_val["args_structure"].get("params").is_some(),
"args结构应包含params参数"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_python_logging() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_logging.py")?;
info!("读取测试脚本: test_python_logging.py");
// 准备参数
let params = json!({
"test": "日志测试"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行Python脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
panic!("执行出错: {error}");
} else {
info!("脚本执行成功");
}
// 验证日志捕获
assert!(!result.logs.is_empty(), "日志不应为空");
info!("捕获到的日志数量: {}", result.logs.len());
// 逐条打印日志
for (i, log) in result.logs.iter().enumerate() {
info!("日志[{i}]: {log}");
}
// 检查是否捕获了所有级别的日志
let logs_str = result.logs.join(" ");
// DEBUG级别的日志可能不会被捕获因为Python胶水代码中可能设置了最低级别为INFO
// assert!(logs_str.contains("DEBUG级别"), "应捕获DEBUG级别的日志");
assert!(logs_str.contains("INFO级别"), "应捕获INFO级别的日志");
assert!(logs_str.contains("WARNING级别"), "应捕获WARNING级别的日志");
assert!(logs_str.contains("ERROR级别"), "应捕获ERROR级别的日志");
assert!(
logs_str.contains("CRITICAL级别"),
"应捕获CRITICAL级别的日志"
);
assert!(
logs_str.contains("格式化的JSON数据"),
"应捕获格式化的JSON数据"
);
// 验证返回值
if let Some(result_val) = result.result {
// 结果应该是JSON对象Python字典被正确解析
assert_eq!(
result_val["message"], "日志测试完成",
"返回的message字段不正确"
);
assert_eq!(result_val["log_count"], 6, "返回的log_count字段不正确");
} else {
panic!("应有返回结果");
}
Ok(())
}
// 这个测试使用execute_with_params并指定超时时间
#[tokio::test]
async fn test_python_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的Python脚本
let code = r#"
import time
import logging
def main(args: dict) -> dict:
logging.info("开始执行耗时操作")
# 这个操作会运行10秒钟
for i in range(10):
logging.info(f"已经执行了 {i+1} 秒")
time.sleep(1)
logging.info("操作完成")
return {"result": "完成"}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置3秒超时并执行脚本
info!("开始执行Python脚本设置3秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Python, Some(params), Some(3))
.await;
let elapsed = start_time.elapsed();
// 检查是否在3-4秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 3, "脚本应该至少运行3秒");
assert!(elapsed.as_secs() < 5, "脚本应该在5秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
#[tokio::test]
async fn test_python_large_parameters() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_python_large_params.py")?;
info!("读取测试脚本: test_python_large_params.py");
// 生成大于2MB的文本参数
fn generate_large_text(size_in_mb: usize) -> String {
let chunk = "This is a test chunk of text designed to generate large parameters for Python testing. ".repeat(100);
let iterations = (size_in_mb * 1024 * 1024) / chunk.len();
let mut result = String::new();
for i in 0..iterations {
result.push_str(&chunk);
result.push_str(&format!("Iteration {}\n", i));
}
result
}
let large_text = generate_large_text(3); // 3MB
info!("生成了3MB大小的文本参数实际大小: {:.2} MB", large_text.len() as f64 / 1024.0 / 1024.0);
// 准备参数
let params = json!({
"largeText": large_text
});
info!("准备测试参数JSON大小: {:.2} MB", serde_json::to_string(&params).unwrap().len() as f64 / 1024.0 / 1024.0);
// 执行脚本
info!("开始执行Python脚本测试大参数处理...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Python, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误,这表明大参数处理成功");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含成功处理大参数的信息
if let Some(result_val) = result.result {
let json_str = serde_json::to_string(&result_val)?;
assert!(json_str.contains("success"), "结果应包含 success 字段");
assert!(json_str.contains("true"), "success 应为 true");
assert!(json_str.contains("Successfully processed large text parameter"), "结果应包含成功消息");
assert!(json_str.contains("sizeMB"), "结果应包含 sizeMB 字段");
// 验证返回值是对象格式
if let Some(obj) = result_val.as_object() {
if let Some(success) = obj.get("success") {
assert_eq!(success, true, "success 字段的值应为 true");
}
if let Some(text_size) = obj.get("textSize") {
assert!(text_size.is_number(), "textSize 应为数字");
let size = text_size.as_u64().unwrap();
assert!(size > 2 * 1024 * 1024, "文本大小应大于2MB");
}
}
}
info!("Python大参数测试通过临时文件解决方案有效。");
Ok(())
}
}

View File

@@ -0,0 +1,202 @@
#[cfg(test)]
mod ts_tests {
use anyhow::Result;
use log::info;
use serde_json::json;
use crate::model::{CodeExecutor, LanguageScript};
use crate::tests::test_utils::setup;
#[tokio::test]
async fn test_ts_basic_execution() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_ts.ts")?;
info!("读取测试脚本: test_ts.ts");
// 执行脚本
info!("开始执行TypeScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, None).await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
Ok(())
}
#[tokio::test]
async fn test_ts_with_params() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_ts_params.ts")?;
info!("读取测试脚本: test_ts_params.ts");
// 准备参数
let params = json!({
"a": 10,
"b": 20,
"name": "测试用户"
});
info!("准备测试参数: {params:?}");
// 执行脚本
info!("开始执行TypeScript脚本...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, Some(params))
.await?;
info!("脚本执行完成, 日志: {:?}", result.logs);
if let Some(error) = &result.error {
info!("执行错误: {error}");
} else {
info!("脚本执行成功");
}
if let Some(result_val) = &result.result {
info!("执行结果: {result_val:?}");
} else {
info!("无执行结果");
}
// 验证结果
assert!(!result.logs.is_empty(), "日志不应为空");
assert!(result.error.is_none(), "不应有错误");
assert!(result.result.is_some(), "应有返回结果");
// 验证返回值包含预期的字段
if let Some(result_val) = result.result {
// 结果应该是JSON对象TypeScript对象被正确解析
assert!(result_val.get("sum").is_some(), "结果应包含 sum 字段");
assert!(
result_val.get("greeting").is_some(),
"结果应包含 greeting 字段"
);
assert!(result_val.get("message").is_some(), "结果应包含 message 字段");
assert!(result_val.get("numbers").is_some(), "结果应包含 numbers 字段");
}
Ok(())
}
#[tokio::test]
async fn test_ts_type_checking() -> Result<()> {
// 初始化日志
setup();
// 从 fixtures 目录读取测试脚本
let code = std::fs::read_to_string("fixtures/test_ts.ts")?;
info!("读取测试脚本: test_ts.ts");
// 执行脚本
info!("开始执行TypeScript类型检查...");
let result =
CodeExecutor::execute_with_params_compat(&code, LanguageScript::Ts, None).await?;
info!("类型检查完成");
if let Some(error) = &result.error {
info!("类型检查错误: {error}");
} else {
info!("类型检查通过");
}
// 验证结果
assert!(result.error.is_none(), "TypeScript 类型检查应通过");
Ok(())
}
#[tokio::test]
async fn test_ts_with_timeout() -> Result<()> {
// 初始化日志
setup();
// 创建一个会超时的TypeScript脚本
let code = r#"
// 创建一个长时间运行的脚本
function sleep(ms: number): Promise<void> {
return new Promise<void>(resolve => setTimeout(resolve, ms));
}
async function handler(input: any): Promise<{result: string}> {
console.log("开始执行耗时操作");
// 这个操作会运行10秒钟
for (let i = 0; i < 10; i++) {
console.log(`已经执行了 ${i+1} 秒`);
await sleep(1000); // 等待1秒
}
console.log("操作完成");
return { result: "完成" };
}
"#;
info!("创建了一个会运行10秒的测试脚本");
// 准备参数
let params = json!({
"test": "超时测试"
});
info!("准备测试参数: {params:?}");
// 设置2秒超时并执行脚本
info!("开始执行TypeScript脚本设置2秒超时...");
let start_time = std::time::Instant::now();
let result =
CodeExecutor::execute_with_params(code, LanguageScript::Ts, Some(params), Some(2))
.await;
let elapsed = start_time.elapsed();
// 检查是否在2-3秒内超时给一点缓冲时间
info!("脚本执行耗时: {elapsed:?}");
assert!(elapsed.as_secs() >= 2, "脚本应该至少运行2秒");
assert!(elapsed.as_secs() < 4, "脚本应该在4秒内超时");
// 检查是否返回超时错误
match result {
Ok(exec_result) => {
if let Some(error) = exec_result.error {
info!("正确捕获到超时错误: {error}");
assert!(
error.contains("timed out") ||
error.contains("TimedOut") ||
error.contains("executor await error"),
"错误信息应该包含超时相关信息"
);
} else {
panic!("应该捕获到超时错误,但脚本执行成功了");
}
}
Err(e) => {
// 使用特殊格式化获取完整错误链
let full_error = format!("{e:#}");
info!("捕获到错误: {full_error}");
assert!(
full_error.contains("timed out"),
"完整错误链中应该包含'timed out'超时信息"
);
}
}
Ok(())
}
}

View File

@@ -0,0 +1,366 @@
use crate::model::CommandExecutor;
use anyhow::Result;
use log::{info, warn};
use std::path::Path;
use tokio::process::Command;
//定义国内python加速地址: https://mirrors.aliyun.com/pypi/simple
const PYTHON_ACCELERATION_ADDRESS: &str = "https://mirrors.aliyun.com/pypi/simple";
//使用 uv安装 python 3.13,比如: uv python install 3.13
async fn install_python_3_13() -> Result<()> {
let mut cmd = Command::new("uv");
cmd.arg("python")
.arg("install")
.arg("3.13")
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("安装Python 3.13失败");
return Err(anyhow::anyhow!("安装Python 3.13失败"));
}
info!("安装Python 3.13成功");
Ok(())
}
Ok(Err(e)) => {
warn!("命令执行失败: {e}");
Err(anyhow::anyhow!("命令执行失败: {}", e))
}
Err(e) => {
warn!("执行超时或系统错误: {e}");
Err(anyhow::anyhow!("执行超时或系统错误: {}", e))
}
}
}
// 检查 uv 虚拟环境是否存在
async fn check_and_create_uv_venv() -> Result<()> {
info!("检查 uv 虚拟环境...");
// 检查 .venv 目录是否存在
if Path::new(".venv").exists() {
info!("uv 虚拟环境已存在");
return Ok(());
}
info!("未检测到 uv 虚拟环境,开始创建...");
let mut cmd = Command::new("uv");
cmd.arg("venv").kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("创建 uv 虚拟环境失败");
return Err(anyhow::anyhow!("创建 uv 虚拟环境失败"));
}
info!("创建 uv 虚拟环境成功");
Ok(())
}
Ok(Err(e)) => {
warn!("命令执行失败: {e}");
Err(anyhow::anyhow!("命令执行失败: {}", e))
}
Err(e) => {
warn!("执行超时或系统错误: {e}");
Err(anyhow::anyhow!("执行超时或系统错误: {}", e))
}
}
}
/// 预热Python环境安装常用依赖
async fn warm_up_python_env(custom_deps: Option<Vec<String>>) -> Result<()> {
info!("开始预热Python环境...");
// 默认Python依赖列表
let default_deps = [
"requests",
"pandas",
"numpy",
"matplotlib",
"scikit-learn",
"pytest",
"pydantic",
"fastapi",
"uvicorn",
"sqlalchemy",
"opencv-python",
"python-docx",
];
// 使用自定义依赖或默认依赖
let deps_to_install = if let Some(deps) = custom_deps {
info!("使用自定义Python依赖列表");
deps
} else {
info!("使用默认Python依赖列表");
default_deps.iter().map(|&s| s.to_string()).collect()
};
let total_deps = deps_to_install.len();
info!("总共需要预热 {total_deps} 个Python依赖");
// 使用uv安装依赖
for (index, dep) in deps_to_install.iter().enumerate() {
let progress = ((index + 1) as f32 / total_deps as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在安装Python依赖: {dep}");
let mut cmd = Command::new("uv");
cmd.arg("pip")
.arg("install")
.arg("--default-index")
.arg(PYTHON_ACCELERATION_ADDRESS)
.arg(dep)
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("安装依赖 {dep} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 依赖: {dep}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 依赖: {dep}");
continue;
}
}
}
info!("Python环境预热完成 (100%)");
Ok(())
}
/// 预热JavaScript/TypeScript环境缓存常用模块
async fn warm_up_js_env(
custom_npm_packages: Option<Vec<String>>,
custom_jsr_packages: Option<Vec<String>>,
custom_node_modules: Option<Vec<String>>,
) -> Result<()> {
info!("开始预热JavaScript/TypeScript环境...");
// 默认npm包列表
let default_npm_packages = [
"lodash",
"axios",
"moment",
"uuid",
"express",
"react",
"react-dom",
"typescript",
"jest",
"webpack",
];
// 默认JSR包列表
let default_jsr_packages = [
"@std/testing",
"@std/http",
"@std/path",
"@std/fs",
"@std/encoding/json",
];
// 默认Node.js内置模块列表
let default_node_modules = [
"crypto", "buffer", "fs", "path", "http", "https", "url", "util", "stream", "events",
];
// 使用自定义npm包或默认包
let npm_packages = if let Some(packages) = custom_npm_packages {
info!("使用自定义npm包列表");
packages
} else {
info!("使用默认npm包列表");
default_npm_packages
.iter()
.map(|&s| s.to_string())
.collect()
};
// 使用自定义JSR包或默认包
let jsr_packages = if let Some(packages) = custom_jsr_packages {
info!("使用自定义JSR包列表");
packages
} else {
info!("使用默认JSR包列表");
default_jsr_packages
.iter()
.map(|&s| s.to_string())
.collect()
};
// 使用自定义Node.js模块或默认模块
let node_modules = if let Some(modules) = custom_node_modules {
info!("使用自定义Node.js模块列表");
modules
} else {
info!("使用默认Node.js模块列表");
default_node_modules
.iter()
.map(|&s| s.to_string())
.collect()
};
// 计算总任务数
let total_tasks = npm_packages.len() + jsr_packages.len() + node_modules.len();
info!("总共需要预热 {total_tasks} 个JavaScript/TypeScript模块");
let mut completed_tasks = 0;
// 预热npm包
for pkg in npm_packages.iter() {
completed_tasks += 1;
let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在缓存npm包: {pkg}");
let mut cmd = Command::new("deno");
cmd.args(["cache", "--reload", &format!("npm:{pkg}")])
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("缓存npm包 {pkg} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 包: {pkg}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 包: {pkg}");
continue;
}
}
}
// 预热JSR包
for pkg in jsr_packages.iter() {
completed_tasks += 1;
let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在缓存JSR包: {pkg}");
let mut cmd = Command::new("deno");
cmd.args(["cache", "--reload", &format!("jsr:{pkg}")])
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("缓存JSR包 {pkg} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 包: {pkg}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 包: {pkg}");
continue;
}
}
}
// 预热Node.js内置模块
for module in node_modules.iter() {
completed_tasks += 1;
let progress = (completed_tasks as f32 / total_tasks as f32 * 100.0) as u32;
info!("预热进度: {progress}% - 正在缓存Node.js模块: {module}");
let mut cmd = Command::new("deno");
cmd.args(["cache", "--reload", &format!("node:{module}")])
.kill_on_drop(true);
match CommandExecutor::with_timeout(cmd.status(), 600).await {
Ok(Ok(status)) => {
if !status.success() {
warn!("缓存Node.js模块 {module} 失败");
}
}
Ok(Err(e)) => {
warn!("命令执行失败: {e} - 模块: {module}");
continue;
}
Err(e) => {
warn!("执行超时或系统错误: {e} - 包: {module}");
continue;
}
}
}
info!("JavaScript/TypeScript环境预热完成 (100%)");
Ok(())
}
/// 预热所有脚本执行环境
pub async fn warm_up_all_envs(
custom_python_deps: Option<Vec<String>>,
custom_npm_packages: Option<Vec<String>>,
custom_jsr_packages: Option<Vec<String>>,
custom_node_modules: Option<Vec<String>>,
) -> Result<()> {
info!("开始预热所有脚本执行环境...");
// 检查并创建 uv 虚拟环境
if let Err(e) = check_and_create_uv_venv().await {
warn!("检查或创建 uv 虚拟环境失败: {e}");
}
// 安装Python 3.13
if let Err(e) = install_python_3_13().await {
warn!("安装Python 3.13失败: {e}");
}
// 预热Python环境
if let Err(e) = warm_up_python_env(custom_python_deps).await {
warn!("预热Python环境失败: {e}");
}
// 预热JavaScript/TypeScript环境
if let Err(e) = warm_up_js_env(
custom_npm_packages,
custom_jsr_packages,
custom_node_modules,
)
.await
{
warn!("预热JavaScript/TypeScript环境失败: {e}");
}
info!("所有脚本执行环境预热完成");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_warm_up_python_env() {
// 测试默认依赖
warm_up_python_env(None).await.unwrap();
// 测试自定义依赖
let custom_deps = vec!["requests".to_string(), "pandas".to_string()];
warm_up_python_env(Some(custom_deps)).await.unwrap();
}
#[tokio::test]
async fn test_warm_up_js_env() {
// 测试默认依赖
warm_up_js_env(None, None, None).await.unwrap();
// 测试自定义依赖
let custom_npm = vec!["lodash".to_string(), "axios".to_string()];
let custom_jsr = vec!["@std/testing".to_string()];
let custom_node = vec!["crypto".to_string(), "buffer".to_string()];
warm_up_js_env(Some(custom_npm), Some(custom_jsr), Some(custom_node))
.await
.unwrap();
}
}