添加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,678 @@
use std::fs;
use std::path::PathBuf;
use clap::Parser;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
/// 命令行参数
#[derive(Parser, Debug)]
#[command(name = "rcoder")]
#[command(about = "RCoder - Rust-based AI Agent Framework")]
#[command(version)]
pub struct CliArgs {
/// 主服务端口
#[arg(short = 'p', long)]
pub port: Option<u16>,
/// 项目工作目录
#[arg(short = 'd', long, default_value = "./project_workspace")]
pub projects_dir: Option<String>,
/// 启用反向代理
#[arg(short, long)]
pub enable_proxy: bool,
/// 代理服务端口
#[arg(long = "proxy-port")]
pub proxy_port: Option<u16>,
/// 默认后端服务端口
#[arg(long = "backend-port")]
pub default_backend_port: Option<u16>,
}
// 从 shared_types 导入 API Key 鉴权配置
pub use shared_types::ApiKeyAuthConfig;
/// 应用程序配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
/// 默认使用的 Agent ID
#[serde(default = "default_agent_id", alias = "default_agent")]
pub default_agent_id: String,
/// 项目工作目录
pub projects_dir: PathBuf,
/// 主服务端口
pub port: u16,
/// 反向代理配置
pub proxy_config: Option<ProxyConfig>,
/// Docker 配置
pub docker_config: Option<DockerConfig>,
/// 容器清理配置
#[serde(default)]
pub cleanup_config: CleanupConfigSettings,
/// API Key 鉴权配置
#[serde(default)]
pub api_key_auth: ApiKeyAuthConfig,
}
fn default_agent_id() -> String {
"claude-code-acp-ts".to_string()
}
/// 生成随机 API Key
/// 使用 UUID v4 生成随机密钥格式sk-{uuid}
fn generate_random_api_key() -> String {
use uuid::Uuid;
let uuid = Uuid::new_v4();
format!("sk-{}", uuid.simple())
}
/// 健康检查配置
#[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,
/// 端口参数名称
pub port_param: String,
/// 健康检查配置
pub health_check: HealthCheckConfig,
}
/// 日志清理配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogCleanupConfig {
/// 日志目录路径
#[serde(default = "default_log_dir")]
pub log_dir: String,
/// 日志保留天数默认10天
#[serde(default = "default_log_retention_days")]
pub log_retention_days: u64,
}
fn default_log_dir() -> String {
"/app/logs/container".to_string()
}
fn default_log_retention_days() -> u64 {
10
}
impl Default for LogCleanupConfig {
fn default() -> Self {
Self {
log_dir: default_log_dir(),
log_retention_days: default_log_retention_days(),
}
}
}
/// 容器清理配置(配置文件格式,使用秒作为单位)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CleanupConfigSettings {
/// 是否启用容器清理功能(默认禁用,更安全)
#[serde(default = "default_cleanup_enabled")]
pub enabled: bool,
/// 闲置超时时间默认600秒10分钟
#[serde(default = "default_idle_timeout_seconds")]
pub idle_timeout_seconds: u64,
/// 清理检查间隔默认300秒5分钟
#[serde(default = "default_cleanup_interval_seconds")]
pub cleanup_interval_seconds: u64,
/// Docker容器停止超时时间默认30秒
#[serde(default = "default_docker_stop_timeout_seconds")]
pub docker_stop_timeout_seconds: u64,
/// 容器最小保护时间默认300秒5分钟
#[serde(default = "default_container_protection_seconds")]
pub container_protection_seconds: u64,
/// 日志清理配置
#[serde(default)]
pub log_cleanup: LogCleanupConfig,
}
fn default_cleanup_enabled() -> bool {
true // 默认启用容器清理功能
}
fn default_idle_timeout_seconds() -> u64 {
600 // 10分钟
}
fn default_cleanup_interval_seconds() -> u64 {
300 // 5分钟
}
fn default_docker_stop_timeout_seconds() -> u64 {
30
}
fn default_container_protection_seconds() -> u64 {
300 // 5分钟
}
impl Default for CleanupConfigSettings {
fn default() -> Self {
Self {
enabled: default_cleanup_enabled(),
idle_timeout_seconds: default_idle_timeout_seconds(),
cleanup_interval_seconds: default_cleanup_interval_seconds(),
docker_stop_timeout_seconds: default_docker_stop_timeout_seconds(),
container_protection_seconds: default_container_protection_seconds(),
log_cleanup: LogCleanupConfig::default(),
}
}
}
/// Docker 配置
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DockerConfig {
/// 多镜像配置
pub multi_image_config: Option<shared_types::MultiImageConfig>,
/// 网络模式
pub network_mode: Option<String>,
/// 工作目录
pub work_dir: Option<String>,
/// 自动清理
pub auto_cleanup: Option<bool>,
/// 容器存活时间(秒)
pub container_ttl_seconds: Option<u64>,
/// 网络基础名称(不含 project name 前缀)
/// Docker Compose 会自动添加 project name 前缀,实际网络名称为 {project_name}_{network_base_name}
/// 例如: network_base_name="agent-network" 时,实际网络为 "rcoder_agent-network"
pub network_base_name: Option<String>,
/// 🔧 Docker API 调用超时时间(秒)
pub api_timeout_seconds: Option<u64>,
/// 🔧 快速操作超时时间(秒)
pub api_timeout_quick_seconds: Option<u64>,
/// 🔧 状态缓存 TTL
pub cache_status_ttl_seconds: Option<u64>,
/// 🔧 网络缓存 TTL
pub cache_network_ttl_seconds: Option<u64>,
/// 🔧 缓存最大容量
pub cache_max_capacity: Option<u64>,
}
pub 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: 8087,
proxy_config: Some(ProxyConfig::default()),
docker_config: Some(DockerConfig::default()),
cleanup_config: CleanupConfigSettings::default(),
api_key_auth: ApiKeyAuthConfig {
enabled: false,
api_key: generate_random_api_key(),
},
}
}
}
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::default(),
}
}
}
impl Default for HealthCheckConfig {
fn default() -> Self {
Self {
enabled: true,
interval_seconds: 5,
timeout_seconds: 1,
healthy_threshold: 2,
unhealthy_threshold: 3,
}
}
}
impl Default for DockerConfig {
fn default() -> Self {
Self {
multi_image_config: Some(shared_types::create_default_multi_image_config()),
network_mode: Some("bridge".to_string()),
work_dir: Some("/app".to_string()),
auto_cleanup: Some(true),
container_ttl_seconds: Some(3600),
network_base_name: Some("agent-network".to_string()),
// 🔧 新增字段默认值
api_timeout_seconds: Some(10),
api_timeout_quick_seconds: Some(5),
cache_status_ttl_seconds: Some(10),
cache_network_ttl_seconds: Some(15),
cache_max_capacity: Some(10000),
}
}
}
impl DockerConfig {
/// 获取多镜像配置,如果没有配置多镜像配置,会从传统配置自动转换
pub fn get_multi_image_config(&self) -> shared_types::MultiImageConfig {
if let Some(ref multi_config) = self.multi_image_config {
multi_config.clone()
} else {
// 从传统配置创建多镜像配置
self.create_legacy_multi_config()
}
}
/// 从传统配置创建多镜像配置
fn create_legacy_multi_config(&self) -> shared_types::MultiImageConfig {
info!("Config created from legacy config");
// 创建基于传统配置的多镜像配置
let mut services = std::collections::HashMap::new();
// 为 RCoder 服务使用默认配置
let rcoder_service = {
info!("Using default config");
shared_types::service_config::default_rcoder_service_config()
};
services.insert("rcoder".to_string(), rcoder_service);
// 为 AgentRunner 服务使用默认配置
services.insert(
"agent-runner".to_string(),
shared_types::service_config::default_agent_runner_service_config(),
);
shared_types::MultiImageConfig {
services,
global_defaults: shared_types::GlobalImageDefaults {
image: None,
arm64_image: None,
amd64_image: None,
default_image: None,
registry_prefix: None,
},
selection_strategy: shared_types::ImageSelectionStrategy::ServiceOnly,
cache_config: shared_types::ImageCacheConfig {
enabled: true,
ttl_seconds: 3600,
max_entries: 100,
},
}
}
/// 验证多镜像配置
pub fn validate_multi_image_config(&self) -> Result<(), String> {
let multi_config = self.get_multi_image_config();
match multi_config.validate() {
Ok(()) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
/// 检查是否使用多镜像配置
pub fn is_using_multi_image_config(&self) -> bool {
self.multi_image_config.is_some()
}
/// 应用环境变量覆盖
pub fn apply_env_overrides(&mut self) -> anyhow::Result<()> {
// 应用网络模式
if let Ok(val) = std::env::var("RCODER_NETWORK_MODE") {
info!("RCODER_NETWORK_MODE overridden");
self.network_mode = Some(val);
}
// 应用网络基础名称
if let Ok(val) = std::env::var("RCODER_NETWORK_BASE_NAME") {
info!("RCODER_NETWORK_BASE_NAME: {}", val);
self.network_base_name = Some(val);
}
// 应用工作目录
if let Ok(val) = std::env::var("RCODER_WORK_DIR") {
info!("RCODER_WORK_DIR overridden");
self.work_dir = Some(val);
}
// 应用自动清理
if let Ok(val) = std::env::var("RCODER_AUTO_CLEANUP") {
info!("RCODER_AUTO_CLEANUP overridden");
self.auto_cleanup = Some(val.parse().unwrap_or(true));
}
// 应用容器存活时间
if let Ok(val) = std::env::var("RCODER_CONTAINER_TTL") {
info!("RCODER_CONTAINER_TTL overridden");
match val.parse() {
Ok(seconds) => self.container_ttl_seconds = Some(seconds),
Err(e) => {
tracing::warn!(
"⚠️ [CONFIG] Failed to parse RCODER_CONTAINER_TTL '{}': {}, using default",
val,
e
);
}
}
}
// 🔧 应用 API 超时配置
if let Ok(val) = std::env::var("RCODER_API_TIMEOUT_SECONDS") {
info!("RCODER_API_TIMEOUT_SECONDS overridden");
match val.parse() {
Ok(seconds) => self.api_timeout_seconds = Some(seconds),
Err(e) => {
tracing::warn!(
"⚠️ [CONFIG] Failed to parse RCODER_API_TIMEOUT_SECONDS '{}': {}, using default",
val,
e
);
}
}
}
// 🔧 应用快速操作超时配置
if let Ok(val) = std::env::var("RCODER_API_TIMEOUT_QUICK_SECONDS") {
info!("RCODER_API_TIMEOUT_QUICK_SECONDS overridden");
match val.parse() {
Ok(seconds) => self.api_timeout_quick_seconds = Some(seconds),
Err(e) => {
tracing::warn!(
"⚠️ [CONFIG] Failed to parse RCODER_API_TIMEOUT_QUICK_SECONDS '{}': {}, using default",
val,
e
);
}
}
}
// 🔧 应用状态缓存 TTL 配置
if let Ok(val) = std::env::var("RCODER_CACHE_STATUS_TTL_SECONDS") {
info!("RCODER_CACHE_STATUS_TTL_SECONDS overridden");
match val.parse() {
Ok(seconds) => self.cache_status_ttl_seconds = Some(seconds),
Err(e) => {
tracing::warn!(
"⚠️ [CONFIG] Failed to parse RCODER_CACHE_STATUS_TTL_SECONDS '{}': {}, using default",
val,
e
);
}
}
}
// 🔧 应用网络缓存 TTL 配置
if let Ok(val) = std::env::var("RCODER_CACHE_NETWORK_TTL_SECONDS") {
info!("RCODER_CACHE_NETWORK_TTL_SECONDS overridden");
match val.parse() {
Ok(seconds) => self.cache_network_ttl_seconds = Some(seconds),
Err(e) => {
tracing::warn!(
"⚠️ [CONFIG] Failed to parse RCODER_CACHE_NETWORK_TTL_SECONDS '{}': {}, using default",
val,
e
);
}
}
}
// 🔧 应用缓存最大容量配置
if let Ok(val) = std::env::var("RCODER_CACHE_MAX_CAPACITY") {
info!("RCODER_CACHE_MAX_CAPACITY overridden");
match val.parse() {
Ok(capacity) => self.cache_max_capacity = Some(capacity),
Err(e) => {
tracing::warn!(
"⚠️ [CONFIG] Failed to parse RCODER_CACHE_MAX_CAPACITY '{}': {}, using default",
val,
e
);
}
}
}
Ok(())
}
/// Get configuration summary
pub fn get_summary(&self) -> String {
format!(
"Docker config: network_mode={}, network_base_name={}, work_dir={}, auto_cleanup={}, container_ttl={}, api_timeout={}s, quick_timeout={}s, status_cache={}s, network_cache={}s, cache_max_capacity={}",
self.network_mode.as_deref().unwrap_or("default"),
self.network_base_name.as_deref().unwrap_or("agent-network"),
self.work_dir.as_deref().unwrap_or("/app"),
self.auto_cleanup.unwrap_or(true),
self.container_ttl_seconds.unwrap_or(3600),
self.api_timeout_seconds.unwrap_or(10),
self.api_timeout_quick_seconds.unwrap_or(5),
self.cache_status_ttl_seconds.unwrap_or(10),
self.cache_network_ttl_seconds.unwrap_or(15),
self.cache_max_capacity.unwrap_or(10000)
)
}
}
/// 加载配置(命令行参数 + 配置文件 + 环境变量)
pub fn load_config_with_args(cli_args: CliArgs) -> anyhow::Result<AppConfig> {
let mut config = if std::path::Path::new(CONFIG_FILE).exists() {
// 尝试从文件加载配置
match load_config_from_file() {
Ok(file_config) => {
info!("Config file already loaded: {}", CONFIG_FILE);
file_config
}
Err(e) => {
warn!("Failed to load config file, using default config: {}", e);
AppConfig::default()
}
}
} else {
info!(
"configfilenot found, createddefaultconfigfile: {}",
CONFIG_FILE
);
let default_config = AppConfig::default();
create_default_config_file(&default_config)?;
default_config
};
// 命令行参数覆盖配置文件
if let Some(port) = cli_args.port {
config.port = port;
}
if let Some(projects_dir) = cli_args.projects_dir {
config.projects_dir = PathBuf::from(projects_dir);
}
// 环境变量覆盖所有配置
if let Ok(port) = std::env::var("RCODER_PORT") {
if let Ok(port) = port.parse::<u16>() {
config.port = port;
} else {
warn!(" parse RCODER_PORT failed: {}", port);
}
}
if let Ok(projects_dir) = std::env::var("RCODER_PROJECTS_DIR") {
config.projects_dir = PathBuf::from(projects_dir);
}
// 如果启用了代理,配置代理相关参数
if cli_args.enable_proxy {
let mut proxy_config = ProxyConfig::default();
if let Some(proxy_port) = cli_args.proxy_port {
proxy_config.listen_port = proxy_port;
}
if let Some(default_backend_port) = cli_args.default_backend_port {
proxy_config.default_backend_port = default_backend_port;
}
config.proxy_config = Some(proxy_config);
}
// 应用 Docker 配置的环境变量覆盖
if let Some(docker_config) = &mut config.docker_config {
docker_config.apply_env_overrides()?;
}
// 应用 API Key 配置的环境变量覆盖
if let Ok(val) = std::env::var("RCODER_API_KEY_ENABLED") {
if let Ok(enabled) = val.parse::<bool>() {
config.api_key_auth.enabled = enabled;
info!(" RCODER_API_KEY_ENABLED: {}", enabled);
} else {
warn!(" parse RCODER_API_KEY_ENABLED failed: {}", val);
}
}
if let Ok(val) = std::env::var("RCODER_API_KEY") {
config.api_key_auth.api_key = val.clone();
info!(" RCODER_API_KEY configured");
}
// 验证 API Key 配置
if config.api_key_auth.enabled && config.api_key_auth.api_key.trim().is_empty() {
return Err(anyhow::anyhow!(
"API Key authentication is enabled but API Key is empty, please check config file or environment variables"
));
}
// 配置验证
if let Some(docker_config) = &config.docker_config {
if let Err(e) = docker_config.validate_multi_image_config() {
return Err(anyhow::anyhow!("Docker configuration validation failed: {}", e));
}
}
info!(
"Final config: port={}, projects_dir={:?}, default_agent_id={}, proxy_enabled={}",
config.port,
config.projects_dir,
config.default_agent_id,
config.proxy_config.is_some()
);
Ok(config)
}
/// 加载配置(保留旧接口以保持兼容性)
pub fn load_config() -> anyhow::Result<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))?;
tracing::debug!("config file content: {}", config_content);
let config: AppConfig = serde_yaml::from_str(&config_content)
.map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?;
// 调试:打印解析后的多镜像配置
if let Some(ref docker_config) = config.docker_config {
tracing::info!("[CONFIG] docker_config is Some, checking multi_image_config");
if let Some(ref multi_config) = docker_config.multi_image_config {
tracing::info!("[CONFIG] multi_image_config is Some, services count: {}", multi_config.services.len());
for (service_key, service_config) in &multi_config.services {
tracing::info!("[CONFIG] Service '{}': arm64_image={:?}, amd64_image={:?}",
service_key, service_config.arm64_image, service_config.amd64_image);
tracing::debug!(
" Service '{}' mount config (total {} mounts):",
service_key,
service_config.mounts.len()
);
for (i, mount) in service_config.mounts.iter().enumerate() {
tracing::debug!(
" [{}]: {} -> {} ({})",
i,
mount.container_path,
mount.host_path,
mount.mount_type
);
}
}
}
}
Ok(config)
}
/// 从配置文件中仅加载 API Key 配置(用于热更新)
///
/// 此函数由 config_watcher 模块调用,用于配置热重载。
/// 编译器可能误报为未使用,因为是跨模块调用。
#[allow(dead_code)]
pub fn load_api_key_config_from_file(
config_path: &std::path::Path,
) -> anyhow::Result<ApiKeyAuthConfig> {
let config_content =
fs::read_to_string(config_path).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.api_key_auth)
}
/// 创建默认配置文件
fn create_default_config_file(_config: &AppConfig) -> anyhow::Result<()> {
// 检查配置文件是否已存在
if std::path::Path::new(CONFIG_FILE).exists() {
return Ok(());
}
// 创建配置文件目录(如果不存在)
if let Some(parent) = std::path::Path::new(CONFIG_FILE).parent() {
std::fs::create_dir_all(parent).map_err(|e| anyhow::anyhow!("Failed to create config directory: {}", e))?;
}
// 使用嵌入式配置文件
let default_config = include_str!("rcoder_default.yml");
// 🆕 生成随机 API Key 并替换模板占位符
let generated_api_key = generate_random_api_key();
let config_content = default_config.replace("{{GENERATED_API_KEY}}", &generated_api_key);
fs::write(CONFIG_FILE, config_content)
.map_err(|e| anyhow::anyhow!("Failed to write default config file: {}", e))?;
info!("Created default config file: {}", CONFIG_FILE);
info!("🔑 Loaded API Key (not set)");
Ok(())
}