添加qiming-rcoder模块

This commit is contained in:
Codex
2026-06-01 13:54:52 +08:00
parent 8092c4b1f8
commit 4b1a580132
539 changed files with 151650 additions and 0 deletions

View File

@@ -0,0 +1,954 @@
use std::env;
use std::fs;
use std::path::PathBuf;
use clap::Parser;
use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};
/// 命令行参数
#[derive(Parser, Debug)]
#[command(name = "rcoder")]
#[command(about = "AI-powered development platform")]
#[command(version)]
pub struct CliArgs {
/// Service port
#[arg(short, long, help = "Service port")]
pub port: Option<u16>,
/// Project workspace directory
#[arg(short = 'd', long, help = "Root directory for project workspace")]
pub projects_dir: Option<PathBuf>,
/// Enable port-based reverse proxy
#[arg(long, help = "Enable port-based reverse proxy")]
pub enable_proxy: bool,
/// Proxy listener port
#[arg(long, help = "Proxy service listener port")]
pub proxy_port: Option<u16>,
/// Default backend port
#[arg(long, help = "Default backend service port")]
pub default_backend_port: Option<u16>,
}
/// 应用配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
/// 默认使用的 Agent ID
#[serde(default = "default_agent_id")]
pub default_agent_id: String,
/// 项目工作的根目录,根据启动命令的当前目录来确定
pub projects_dir: PathBuf,
/// 服务端口
pub port: u16,
/// 代理配置
pub proxy_config: Option<ProxyConfig>,
/// Agent 清理配置
#[serde(default)]
pub agent_cleanup: Option<AgentCleanupConfig>,
/// gRPC 超时配置
#[serde(default)]
pub grpc_timeouts: Option<GrpcTimeoutConfig>,
/// Agent 并发配置
#[serde(default)]
pub agent_concurrency: Option<AgentConcurrencyConfig>,
/// mcp-proxy 日志目录(可选)
/// 当设置此值且日志级别为 debug 时mcp-proxy convert 命令会自动追加
/// --diagnostic 和 --log-dir 参数
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mcp_proxy_log_dir: Option<String>,
}
fn default_agent_id() -> String {
"claude-code-acp-ts".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheckConfig {
pub enabled: bool,
pub interval_seconds: u64,
pub timeout_seconds: u64,
pub healthy_threshold: u32,
pub unhealthy_threshold: u32,
}
/// 代理配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
/// 代理监听端口
pub listen_port: u16,
/// 默认后端端口
pub default_backend_port: u16,
/// 后端服务主机
pub backend_host: String,
/// URL 中端口参数的名称
pub port_param: String,
/// 健康检查配置
pub health_check: HealthCheckConfig,
}
/// Agent cleanup configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCleanupConfig {
/// Idle timeout (seconds), default 300 (5 minutes)
#[serde(default = "default_idle_timeout")]
pub idle_timeout_secs: u64,
/// Cleanup check interval (seconds), default 30
#[serde(default = "default_cleanup_interval")]
pub cleanup_interval_secs: u64,
}
/// Agent 并发配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConcurrencyConfig {
/// 并发会话槽位数量,默认 10
#[serde(default = "default_concurrency_limit")]
pub concurrency_limit: usize,
}
/// Agent 并发配置常量
impl AgentConcurrencyConfig {
/// 最小并发限制
pub const MIN_CONCURRENCY_LIMIT: usize = 1;
/// 验证配置值是否在有效范围内
pub fn validate(&self) -> Result<(), String> {
if self.concurrency_limit < Self::MIN_CONCURRENCY_LIMIT {
return Err(format!(
"concurrency_limit must be >= {}, current value: {}",
Self::MIN_CONCURRENCY_LIMIT,
self.concurrency_limit
));
}
Ok(())
}
}
fn default_concurrency_limit() -> usize {
10
}
impl Default for AgentConcurrencyConfig {
fn default() -> Self {
Self {
concurrency_limit: default_concurrency_limit(),
}
}
}
/// gRPC timeout configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrpcTimeoutConfig {
/// Cancel session timeout (seconds), default 30
#[serde(default = "default_cancel_timeout")]
pub cancel_session_timeout_secs: u64,
/// ACP session creation timeout (seconds), default 100
#[serde(default = "default_acp_session_timeout")]
pub acp_session_create_timeout_secs: u64,
/// Agent cancel call timeout (seconds), default 10
#[serde(default = "default_agent_cancel_timeout")]
pub agent_cancel_timeout_secs: u64,
/// Port check timeout (milliseconds), default 500
#[serde(default = "default_port_check_timeout")]
pub port_check_timeout_millis: u64,
}
/// gRPC timeout configuration constants
impl GrpcTimeoutConfig {
/// Minimum cancel session timeout (5 seconds)
pub const MIN_CANCEL_TIMEOUT: u64 = 5;
/// Maximum cancel session timeout (300 seconds = 5 minutes)
pub const MAX_CANCEL_TIMEOUT: u64 = 300;
/// Minimum ACP session creation timeout (10 seconds)
pub const MIN_ACP_SESSION_TIMEOUT: u64 = 10;
/// Maximum ACP session creation timeout (300 seconds = 5 minutes)
pub const MAX_ACP_SESSION_TIMEOUT: u64 = 300;
/// Minimum Agent cancel call timeout (5 seconds)
pub const MIN_AGENT_CANCEL_TIMEOUT: u64 = 5;
/// Maximum Agent cancel call timeout (60 seconds)
pub const MAX_AGENT_CANCEL_TIMEOUT: u64 = 60;
/// Minimum port check timeout (100 milliseconds)
pub const MIN_PORT_CHECK_TIMEOUT: u64 = 100;
/// Maximum port check timeout (10000 milliseconds = 10 seconds)
pub const MAX_PORT_CHECK_TIMEOUT: u64 = 10000;
/// Validate that configuration values are within valid ranges
pub fn validate(&self) -> Result<(), String> {
if self.cancel_session_timeout_secs < Self::MIN_CANCEL_TIMEOUT
|| self.cancel_session_timeout_secs > Self::MAX_CANCEL_TIMEOUT
{
return Err(format!(
"cancel_session_timeout_secs must be between {} and {}, current: {}",
Self::MIN_CANCEL_TIMEOUT,
Self::MAX_CANCEL_TIMEOUT,
self.cancel_session_timeout_secs
));
}
if self.acp_session_create_timeout_secs < Self::MIN_ACP_SESSION_TIMEOUT
|| self.acp_session_create_timeout_secs > Self::MAX_ACP_SESSION_TIMEOUT
{
return Err(format!(
"acp_session_create_timeout_secs must be between {} and {}, current: {}",
Self::MIN_ACP_SESSION_TIMEOUT,
Self::MAX_ACP_SESSION_TIMEOUT,
self.acp_session_create_timeout_secs
));
}
if self.agent_cancel_timeout_secs < Self::MIN_AGENT_CANCEL_TIMEOUT
|| self.agent_cancel_timeout_secs > Self::MAX_AGENT_CANCEL_TIMEOUT
{
return Err(format!(
"agent_cancel_timeout_secs must be between {} and {}, current: {}",
Self::MIN_AGENT_CANCEL_TIMEOUT,
Self::MAX_AGENT_CANCEL_TIMEOUT,
self.agent_cancel_timeout_secs
));
}
if self.port_check_timeout_millis < Self::MIN_PORT_CHECK_TIMEOUT
|| self.port_check_timeout_millis > Self::MAX_PORT_CHECK_TIMEOUT
{
return Err(format!(
"port_check_timeout_millis must be between {} and {}, current: {}",
Self::MIN_PORT_CHECK_TIMEOUT,
Self::MAX_PORT_CHECK_TIMEOUT,
self.port_check_timeout_millis
));
}
Ok(())
}
}
/// Agent 清理配置常量
impl AgentCleanupConfig {
/// 最小闲置超时时间10 秒)
pub const MIN_IDLE_TIMEOUT: u64 = 10;
/// 最大闲置超时时间24 小时)
pub const MAX_IDLE_TIMEOUT: u64 = 24 * 60 * 60;
/// 最小清理检查间隔5 秒)
pub const MIN_CLEANUP_INTERVAL: u64 = 5;
/// 最大清理检查间隔1 小时)
pub const MAX_CLEANUP_INTERVAL: u64 = 60 * 60;
/// 验证配置值是否在有效范围内
pub fn validate(&self) -> Result<(), String> {
if self.idle_timeout_secs < Self::MIN_IDLE_TIMEOUT
|| self.idle_timeout_secs > Self::MAX_IDLE_TIMEOUT
{
return Err(format!(
"idle_timeout_secs must be between {} and {}, current value: {}",
Self::MIN_IDLE_TIMEOUT,
Self::MAX_IDLE_TIMEOUT,
self.idle_timeout_secs
));
}
if self.cleanup_interval_secs < Self::MIN_CLEANUP_INTERVAL
|| self.cleanup_interval_secs > Self::MAX_CLEANUP_INTERVAL
{
return Err(format!(
"cleanup_interval_secs must be between {} and {}, current value: {}",
Self::MIN_CLEANUP_INTERVAL,
Self::MAX_CLEANUP_INTERVAL,
self.cleanup_interval_secs
));
}
Ok(())
}
}
#[cfg(feature = "http-server")]
fn default_idle_timeout() -> u64 {
24 * 60 * 60 // 24 小时Tauri 客户端模式)
}
#[cfg(not(feature = "http-server"))]
fn default_idle_timeout() -> u64 {
300 // 5 分钟CLI 模式)
}
fn default_cleanup_interval() -> u64 {
30 // 30 秒
}
fn default_cancel_timeout() -> u64 {
30 // 30 秒
}
fn default_acp_session_timeout() -> u64 {
100 // 100 秒
}
fn default_agent_cancel_timeout() -> u64 {
10 // 10 秒
}
fn default_port_check_timeout() -> u64 {
500 // 500 毫秒
}
impl Default for AgentCleanupConfig {
fn default() -> Self {
Self {
idle_timeout_secs: default_idle_timeout(),
cleanup_interval_secs: default_cleanup_interval(),
}
}
}
impl Default for GrpcTimeoutConfig {
fn default() -> Self {
Self {
cancel_session_timeout_secs: default_cancel_timeout(),
acp_session_create_timeout_secs: default_acp_session_timeout(),
agent_cancel_timeout_secs: default_agent_cancel_timeout(),
port_check_timeout_millis: default_port_check_timeout(),
}
}
}
/// 配置文件路径
const CONFIG_FILE: &str = "config.yml";
impl Default for AppConfig {
fn default() -> Self {
Self {
default_agent_id: default_agent_id(),
projects_dir: PathBuf::from("./project_workspace"),
port: 8086,
proxy_config: Some(ProxyConfig::default()),
agent_cleanup: Some(AgentCleanupConfig::default()),
grpc_timeouts: Some(GrpcTimeoutConfig::default()),
agent_concurrency: Some(AgentConcurrencyConfig::default()),
mcp_proxy_log_dir: None,
}
}
}
impl Default for ProxyConfig {
fn default() -> Self {
Self {
listen_port: 8088,
default_backend_port: 8086,
backend_host: "127.0.0.1".to_string(),
port_param: "port".to_string(),
health_check: HealthCheckConfig {
enabled: true,
interval_seconds: 5,
timeout_seconds: 1,
healthy_threshold: 2,
unhealthy_threshold: 3,
},
}
}
}
/// 加载配置
/// 配置优先级:命令行参数 > 环境变量 > 配置文件 > 默认配置
pub fn load_config_with_args(cli_args: CliArgs) -> AppConfig {
// 1. 首先加载默认配置
let mut config = AppConfig::default();
// 2. 尝试从当前目录读取配置文件
match load_config_from_file() {
Ok(file_config) => {
config = file_config;
info!("Loaded config from {}", CONFIG_FILE);
}
Err(e) => {
warn!(
"Failed to read config file {}: {}, using defaults",
CONFIG_FILE, e
);
// 创建默认配置文件
if let Err(create_err) = create_default_config_file(&config) {
error!("Failed to create default config file: {}", create_err);
} else {
info!("Created default config file: {}", CONFIG_FILE);
}
}
}
// 3. 环境变量覆盖配置
if let Ok(port) = env::var("RCODER_PORT") {
match port.parse::<u16>() {
Ok(p) => {
config.port = p;
info!("Set port from env RCODER_PORT: {}", p);
}
Err(_) => {
warn!(
"Invalid RCODER_PORT env value: {}, keeping config port: {}",
port, config.port
);
}
}
}
// 🆕 Agent 清理配置:支持环境变量覆盖
if let Ok(idle_timeout) = env::var("RCODER_AGENT_IDLE_TIMEOUT_SECS") {
match idle_timeout.parse::<u64>() {
Ok(timeout) => {
// 🔒 验证范围
if (AgentCleanupConfig::MIN_IDLE_TIMEOUT..=AgentCleanupConfig::MAX_IDLE_TIMEOUT)
.contains(&timeout)
{
config
.agent_cleanup
.get_or_insert_with(Default::default)
.idle_timeout_secs = timeout;
info!(
"Set idle timeout from env RCODER_AGENT_IDLE_TIMEOUT_SECS: {} seconds",
timeout
);
} else {
warn!(
"Invalid RCODER_AGENT_IDLE_TIMEOUT_SECS: {} seconds, out of range [{}, {}], keeping config value",
timeout,
AgentCleanupConfig::MIN_IDLE_TIMEOUT,
AgentCleanupConfig::MAX_IDLE_TIMEOUT
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_AGENT_IDLE_TIMEOUT_SECS format: {}, keeping config value",
idle_timeout
);
}
}
}
if let Ok(cleanup_interval) = env::var("RCODER_AGENT_CLEANUP_INTERVAL_SECS") {
match cleanup_interval.parse::<u64>() {
Ok(interval) => {
// 🔒 验证范围
if (AgentCleanupConfig::MIN_CLEANUP_INTERVAL
..=AgentCleanupConfig::MAX_CLEANUP_INTERVAL)
.contains(&interval)
{
config
.agent_cleanup
.get_or_insert_with(Default::default)
.cleanup_interval_secs = interval;
info!(
"Set cleanup interval from env RCODER_AGENT_CLEANUP_INTERVAL_SECS: {} seconds",
interval
);
} else {
warn!(
"Invalid RCODER_AGENT_CLEANUP_INTERVAL_SECS: {} seconds, out of range [{}, {}], keeping config value",
interval,
AgentCleanupConfig::MIN_CLEANUP_INTERVAL,
AgentCleanupConfig::MAX_CLEANUP_INTERVAL
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_AGENT_CLEANUP_INTERVAL_SECS format: {}, keeping config value",
cleanup_interval
);
}
}
}
// 🆕 Agent 并发配置:支持环境变量覆盖
if let Ok(concurrency_limit) = env::var("RCODER_AGENT_CONCURRENCY_LIMIT") {
match concurrency_limit.parse::<usize>() {
Ok(limit) => {
if limit >= AgentConcurrencyConfig::MIN_CONCURRENCY_LIMIT {
config
.agent_concurrency
.get_or_insert_with(Default::default)
.concurrency_limit = limit;
info!(
"Set concurrency limit from env RCODER_AGENT_CONCURRENCY_LIMIT: {}",
limit
);
} else {
warn!(
"Invalid RCODER_AGENT_CONCURRENCY_LIMIT: {}, must be >= {}, keeping config value",
limit,
AgentConcurrencyConfig::MIN_CONCURRENCY_LIMIT
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_AGENT_CONCURRENCY_LIMIT format: {}, keeping config value",
concurrency_limit
);
}
}
}
// 🆕 gRPC 超时配置:支持环境变量覆盖
if let Ok(cancel_timeout) = env::var("RCODER_CANCEL_SESSION_TIMEOUT_SECS") {
match cancel_timeout.parse::<u64>() {
Ok(timeout) => {
if (GrpcTimeoutConfig::MIN_CANCEL_TIMEOUT..=GrpcTimeoutConfig::MAX_CANCEL_TIMEOUT)
.contains(&timeout)
{
config
.grpc_timeouts
.get_or_insert_with(Default::default)
.cancel_session_timeout_secs = timeout;
info!(
"Set cancel-session timeout from env RCODER_CANCEL_SESSION_TIMEOUT_SECS: {} seconds",
timeout
);
} else {
warn!(
"Invalid RCODER_CANCEL_SESSION_TIMEOUT_SECS: {} seconds, out of range [{}, {}]",
timeout,
GrpcTimeoutConfig::MIN_CANCEL_TIMEOUT,
GrpcTimeoutConfig::MAX_CANCEL_TIMEOUT
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_CANCEL_SESSION_TIMEOUT_SECS format: {}",
cancel_timeout
);
}
}
}
if let Ok(acp_timeout) = env::var("RCODER_ACP_SESSION_CREATE_TIMEOUT_SECS") {
match acp_timeout.parse::<u64>() {
Ok(timeout) => {
if (GrpcTimeoutConfig::MIN_ACP_SESSION_TIMEOUT
..=GrpcTimeoutConfig::MAX_ACP_SESSION_TIMEOUT)
.contains(&timeout)
{
config
.grpc_timeouts
.get_or_insert_with(Default::default)
.acp_session_create_timeout_secs = timeout;
info!(
"Set ACP session-create timeout from env RCODER_ACP_SESSION_CREATE_TIMEOUT_SECS: {} seconds",
timeout
);
} else {
warn!(
"Invalid RCODER_ACP_SESSION_CREATE_TIMEOUT_SECS: {} seconds, out of range [{}, {}]",
timeout,
GrpcTimeoutConfig::MIN_ACP_SESSION_TIMEOUT,
GrpcTimeoutConfig::MAX_ACP_SESSION_TIMEOUT
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_ACP_SESSION_CREATE_TIMEOUT_SECS format: {}",
acp_timeout
);
}
}
}
if let Ok(agent_cancel_timeout) = env::var("RCODER_AGENT_CANCEL_TIMEOUT_SECS") {
match agent_cancel_timeout.parse::<u64>() {
Ok(timeout) => {
if (GrpcTimeoutConfig::MIN_AGENT_CANCEL_TIMEOUT
..=GrpcTimeoutConfig::MAX_AGENT_CANCEL_TIMEOUT)
.contains(&timeout)
{
config
.grpc_timeouts
.get_or_insert_with(Default::default)
.agent_cancel_timeout_secs = timeout;
info!(
"Set agent-cancel timeout from env RCODER_AGENT_CANCEL_TIMEOUT_SECS: {} seconds",
timeout
);
} else {
warn!(
"Invalid RCODER_AGENT_CANCEL_TIMEOUT_SECS: {} seconds, out of range [{}, {}]",
timeout,
GrpcTimeoutConfig::MIN_AGENT_CANCEL_TIMEOUT,
GrpcTimeoutConfig::MAX_AGENT_CANCEL_TIMEOUT
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_AGENT_CANCEL_TIMEOUT_SECS format: {}",
agent_cancel_timeout
);
}
}
}
if let Ok(port_check_timeout) = env::var("RCODER_PORT_CHECK_TIMEOUT_MILLIS") {
match port_check_timeout.parse::<u64>() {
Ok(timeout) => {
if (GrpcTimeoutConfig::MIN_PORT_CHECK_TIMEOUT
..=GrpcTimeoutConfig::MAX_PORT_CHECK_TIMEOUT)
.contains(&timeout)
{
config
.grpc_timeouts
.get_or_insert_with(Default::default)
.port_check_timeout_millis = timeout;
info!(
"Set port-check timeout from env RCODER_PORT_CHECK_TIMEOUT_MILLIS: {} ms",
timeout
);
} else {
warn!(
"Invalid RCODER_PORT_CHECK_TIMEOUT_MILLIS: {} ms, out of range [{}, {}]",
timeout,
GrpcTimeoutConfig::MIN_PORT_CHECK_TIMEOUT,
GrpcTimeoutConfig::MAX_PORT_CHECK_TIMEOUT
);
}
}
Err(_) => {
warn!(
"Invalid RCODER_PORT_CHECK_TIMEOUT_MILLIS format: {}",
port_check_timeout
);
}
}
}
// 🆕 验证最终配置的有效性
if let Some(ref cleanup_config) = config.agent_cleanup
&& let Err(e) = cleanup_config.validate()
{
warn!(
"Agent cleanup config validation failed: {}, using defaults",
e
);
config.agent_cleanup = Some(AgentCleanupConfig::default());
}
// 🆕 验证 Agent 并发配置的有效性
if let Some(ref concurrency_config) = config.agent_concurrency
&& let Err(e) = concurrency_config.validate()
{
warn!(
"Agent concurrency config validation failed: {}, using defaults",
e
);
config.agent_concurrency = Some(AgentConcurrencyConfig::default());
}
// 4. 处理代理配置
if cli_args.enable_proxy {
let proxy_config = ProxyConfig {
listen_port: cli_args.proxy_port.unwrap_or(8080),
default_backend_port: cli_args.default_backend_port.unwrap_or(config.port),
backend_host: "127.0.0.1".to_string(),
port_param: "port".to_string(),
health_check: HealthCheckConfig {
enabled: true,
interval_seconds: 5,
timeout_seconds: 1,
healthy_threshold: 2,
unhealthy_threshold: 3,
},
};
info!(
"Reverse proxy enabled, listening on port: {}",
proxy_config.listen_port
);
config.proxy_config = Some(proxy_config);
}
// 5. 命令行参数覆盖配置(优先级最高)
if let Some(port) = cli_args.port {
config.port = port;
info!("Set port from CLI arg: {}", port);
}
if let Some(projects_dir) = cli_args.projects_dir {
config.projects_dir = projects_dir.clone();
info!("Set projects directory from CLI arg: {:?}", projects_dir);
}
info!(
"最终配置: port={}, projects_dir={:?}, default_agent_id={}, proxy_enabled={}",
config.port,
config.projects_dir,
config.default_agent_id,
config.proxy_config.is_some()
);
// 🆕 验证 gRPC 超时配置的有效性
if let Some(ref grpc_timeouts) = config.grpc_timeouts
&& let Err(e) = grpc_timeouts.validate()
{
warn!(
"gRPC timeout config validation failed: {}, using defaults",
e
);
config.grpc_timeouts = Some(GrpcTimeoutConfig::default());
}
config
}
/// 加载配置(保留旧接口以保持兼容性)
pub fn load_config() -> AppConfig {
let cli_args = CliArgs {
port: None,
projects_dir: None,
enable_proxy: false,
proxy_port: None,
default_backend_port: None,
};
load_config_with_args(cli_args)
}
/// 从文件加载配置
fn load_config_from_file() -> anyhow::Result<AppConfig> {
let config_content = fs::read_to_string(CONFIG_FILE)
.map_err(|e| anyhow::anyhow!("Failed to read config file: {}", e))?;
let config: AppConfig = serde_yaml::from_str(&config_content)
.map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?;
Ok(config)
}
/// 创建默认配置文件
fn create_default_config_file(config: &AppConfig) -> anyhow::Result<()> {
// 获取 proxy_config如果不存在则使用默认值
let proxy_config = config.proxy_config.as_ref().cloned().unwrap_or_default();
// 获取 agent_cleanup 配置,如果不存在则使用默认值
let agent_cleanup = config.agent_cleanup.as_ref().cloned().unwrap_or_default();
// 获取 grpc_timeouts 配置,如果不存在则使用默认值
let grpc_timeouts = config.grpc_timeouts.as_ref().cloned().unwrap_or_default();
// 获取 agent_concurrency 配置,如果不存在则使用默认值
let agent_concurrency = config
.agent_concurrency
.as_ref()
.cloned()
.unwrap_or_default();
// 手动构建带注释的 YAML 内容
let content_with_comments = format!(
r#"# rcoder 配置文件
# 该文件在首次启动时自动生成
# 默认使用的 Agent ID
default_agent_id: {}
# 项目工作目录
projects_dir: {}
# 主服务端口
port: {}
# Pingora 反向代理配置
proxy_config:
# 代理服务监听端口 (用于接收外部请求)
listen_port: {}
# 默认后端服务端口 (当请求未指定端口时使用)
default_backend_port: {}
# 后端服务主机地址
backend_host: "{}"
# URL 中端口参数的名称 (用于从路径中提取端口号)
port_param: "{}"
# 健康检查配置
health_check:
enabled: {}
interval_seconds: {}
timeout_seconds: {}
healthy_threshold: {}
unhealthy_threshold: {}
# Agent 清理配置
# 如果省略此配置块,将使用以下默认值:
# - idle_timeout_secs: 300 (5分钟)
# - cleanup_interval_secs: 30 (30秒)
agent_cleanup:
# Agent 闲置超时时间(秒)
# Agent 在闲置超过此时间后会被自动清理以释放资源
# 有效范围: 10 - 86400 秒10秒 - 24小时
# 可通过环境变量 RCODER_AGENT_IDLE_TIMEOUT_SECS 覆盖
idle_timeout_secs: {}
# 清理检查间隔(秒)
# 系统每隔此时间检查一次是否有闲置的 Agent 需要清理
# 有效范围: 5 - 3600 秒5秒 - 1小时
# 可通过环境变量 RCODER_AGENT_CLEANUP_INTERVAL_SECS 覆盖
cleanup_interval_secs: {}
# gRPC 超时配置
# 如果省略此配置块,将使用以下默认值:
# - cancel_session_timeout_secs: 30 (30秒)
# - acp_session_create_timeout_secs: 100 (100秒)
# - agent_cancel_timeout_secs: 10 (10秒)
# - port_check_timeout_millis: 500 (500毫秒)
grpc_timeouts:
# 取消会话超时(秒)
# gRPC 取消会话请求的最大等待时间
# 有效范围: 5 - 300 秒
# 可通过环境变量 RCODER_CANCEL_SESSION_TIMEOUT_SECS 覆盖
cancel_session_timeout_secs: {}
# ACP 会话创建超时(秒)
# Agent 创建新会话的最大等待时间MCP 工具较多时可能需要更长时间)
# 有效范围: 10 - 300 秒
# 可通过环境变量 RCODER_ACP_SESSION_CREATE_TIMEOUT_SECS 覆盖
acp_session_create_timeout_secs: {}
# Agent 取消调用超时(秒)
# Agent 内部取消操作的最大等待时间
# 有效范围: 5 - 60 秒
# 可通过环境变量 RCODER_AGENT_CANCEL_TIMEOUT_SECS 覆盖
agent_cancel_timeout_secs: {}
# 端口检查超时(毫秒)
# 检查端口可用性的最大等待时间
# 有效范围: 100 - 10000 毫秒
# 可通过环境变量 RCODER_PORT_CHECK_TIMEOUT_MILLIS 覆盖
port_check_timeout_millis: {}
# Agent 并发配置
# 如果省略此配置块,将使用以下默认值:
# - concurrency_limit: 10 (10个并发会话)
agent_concurrency:
# Agent 并发会话槽位数量
# 决定可以同时处理的 Agent 会话数量
# 有效范围: >= 1
# 可通过环境变量 RCODER_AGENT_CONCURRENCY_LIMIT 覆盖
concurrency_limit: {}
"#,
config.default_agent_id,
config.projects_dir.display(),
config.port,
proxy_config.listen_port,
proxy_config.default_backend_port,
proxy_config.backend_host,
proxy_config.port_param,
proxy_config.health_check.enabled,
proxy_config.health_check.interval_seconds,
proxy_config.health_check.timeout_seconds,
proxy_config.health_check.healthy_threshold,
proxy_config.health_check.unhealthy_threshold,
agent_cleanup.idle_timeout_secs,
agent_cleanup.cleanup_interval_secs,
grpc_timeouts.cancel_session_timeout_secs,
grpc_timeouts.acp_session_create_timeout_secs,
grpc_timeouts.agent_cancel_timeout_secs,
grpc_timeouts.port_check_timeout_millis,
agent_concurrency.concurrency_limit
);
fs::write(CONFIG_FILE, content_with_comments)
.map_err(|e| anyhow::anyhow!("Failed to write config file: {}", e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_cleanup_default_values() {
let config = AgentCleanupConfig::default();
assert_eq!(config.idle_timeout_secs, 300);
assert_eq!(config.cleanup_interval_secs, 30);
}
#[test]
fn test_agent_cleanup_validate_valid_range() {
let config = AgentCleanupConfig {
idle_timeout_secs: 600, // 10 分钟
cleanup_interval_secs: 60, // 1 分钟
};
assert!(config.validate().is_ok());
}
#[test]
fn test_agent_cleanup_validate_min_boundaries() {
// 测试最小边界值
let config = AgentCleanupConfig {
idle_timeout_secs: AgentCleanupConfig::MIN_IDLE_TIMEOUT,
cleanup_interval_secs: AgentCleanupConfig::MIN_CLEANUP_INTERVAL,
};
assert!(config.validate().is_ok());
}
#[test]
fn test_agent_cleanup_validate_max_boundaries() {
// 测试最大边界值
let config = AgentCleanupConfig {
idle_timeout_secs: AgentCleanupConfig::MAX_IDLE_TIMEOUT,
cleanup_interval_secs: AgentCleanupConfig::MAX_CLEANUP_INTERVAL,
};
assert!(config.validate().is_ok());
}
#[test]
fn test_agent_cleanup_validate_idle_timeout_too_small() {
let config = AgentCleanupConfig {
idle_timeout_secs: 5, // 小于最小值 10
cleanup_interval_secs: 30,
};
assert!(config.validate().is_err());
let err = config.validate().unwrap_err();
assert!(err.contains("idle_timeout_secs"));
}
#[test]
fn test_agent_cleanup_validate_idle_timeout_too_large() {
let config = AgentCleanupConfig {
idle_timeout_secs: 100000, // 大于最大值 86400
cleanup_interval_secs: 30,
};
assert!(config.validate().is_err());
let err = config.validate().unwrap_err();
assert!(err.contains("idle_timeout_secs"));
}
#[test]
fn test_agent_cleanup_validate_cleanup_interval_too_small() {
let config = AgentCleanupConfig {
idle_timeout_secs: 180,
cleanup_interval_secs: 2, // 小于最小值 5
};
assert!(config.validate().is_err());
let err = config.validate().unwrap_err();
assert!(err.contains("cleanup_interval_secs"));
}
#[test]
fn test_agent_cleanup_validate_cleanup_interval_too_large() {
let config = AgentCleanupConfig {
idle_timeout_secs: 180,
cleanup_interval_secs: 5000, // 大于最大值 3600
};
assert!(config.validate().is_err());
let err = config.validate().unwrap_err();
assert!(err.contains("cleanup_interval_secs"));
}
#[test]
fn test_agent_cleanup_validate_both_invalid() {
let config = AgentCleanupConfig {
idle_timeout_secs: 0,
cleanup_interval_secs: 0,
};
assert!(config.validate().is_err());
// 应该先检测到 idle_timeout_secs 的错误
let err = config.validate().unwrap_err();
assert!(err.contains("idle_timeout_secs"));
}
}