添加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,569 @@
//! 项目适配器
//!
//! 提供统一的项目数据访问接口,替代原有的 3 个 DashMap
//! - project_and_agent_map: DashMap<String, Arc<ProjectAndContainerInfo>>
//! - sessions: DashMap<String, Arc<ProjectAndContainerInfo>>
//! - session_to_container_id: DashMap<String, String>
use chrono::{DateTime, Utc};
use duckdb_manager::{ContainerRecord, DuckDbStorage, ProjectRecord, StorageStats, UnifiedStorage};
use shared_types::{ContainerBasicInfo, ProjectAndContainerInfo, ServiceType};
use std::sync::Arc;
use tracing::{debug, warn};
use super::bridge::DataBridge;
/// 项目适配器
///
/// 统一管理项目、会话和容器数据,替代原有的 3 个 DashMap
#[derive(Clone)]
pub struct ProjectAdapter {
storage: Arc<DuckDbStorage>,
}
impl ProjectAdapter {
/// 创建新的项目适配器
pub fn new() -> Result<Self, duckdb_manager::DuckDbError> {
let storage = DuckDbStorage::new()?;
Ok(Self {
storage: Arc::new(storage),
})
}
/// 从现有存储创建适配器
pub fn from_storage(storage: Arc<DuckDbStorage>) -> Self {
Self { storage }
}
// ========== project_and_agent_map 替代方法 ==========
/// 获取项目信息(替代 project_and_agent_map.get
pub fn get(&self, project_id: &str) -> Option<Arc<ProjectAndContainerInfo>> {
match self.storage.get_project(project_id) {
Ok(Some(record)) => {
// 获取关联的容器信息
let container = self.get_container_for_project(&record);
Some(Arc::new(DataBridge::project_record_to_info(
&record, container,
)))
}
Ok(None) => None,
Err(e) => {
warn!("getproject {} failed: {}", project_id, e);
None
}
}
}
/// 插入或更新项目信息(替代 project_and_agent_map.insert
pub fn insert(
&self,
project_id: String,
info: Arc<ProjectAndContainerInfo>,
) -> Result<(), duckdb_manager::DuckDbError> {
// 如果有容器信息,先保存容器
if let Some(container) = info.container() {
debug!(
"Inserting project container info: project_id={}, container_id={}, container_ip={}",
project_id, container.container_id, container.container_ip
);
let container_record =
DataBridge::container_info_to_record(container, info.service_type());
self.storage.save_container(&container_record)?;
} else {
debug!(
"No project container: project_id={}",
project_id
);
}
// 保存项目记录
let record = DataBridge::info_to_project_record(&info, &project_id);
debug!(
"Saving project record: project_id={}, session_id={:?}, container_id={}",
record.project_id, record.session_id, record.container_id
);
self.storage.save_project(&record)?;
debug!("Removed project: {}", project_id);
Ok(())
}
/// 删除项目(替代 project_and_agent_map.remove
pub fn remove(&self, project_id: &str) -> Option<Arc<ProjectAndContainerInfo>> {
// 先获取现有数据
let info = self.get(project_id)?;
// 删除项目记录
if let Err(e) = self.storage.delete_project(project_id) {
warn!("Failed to remove project {}: {}", project_id, e);
return None;
}
debug!("Removed project: {}", project_id);
Some(info)
}
/// 检查项目是否存在(替代 project_and_agent_map.contains_key
pub fn contains_key(&self, project_id: &str) -> bool {
match self.storage.project_exists(project_id) {
Ok(exists) => exists,
Err(e) => {
warn!("Failed to check project {} exists: {}", project_id, e);
false
}
}
}
/// 获取所有项目(替代 project_and_agent_map.iter
pub fn iter(&self) -> impl Iterator<Item = (String, Arc<ProjectAndContainerInfo>)> + '_ {
let projects = self.storage.get_all_projects().unwrap_or_default();
projects.into_iter().filter_map(move |record| {
let container = self.get_container_for_project(&record);
let info = DataBridge::project_record_to_info(&record, container);
Some((record.project_id.clone(), Arc::new(info)))
})
}
/// 获取项目数量(替代 project_and_agent_map.len
pub fn len(&self) -> usize {
match self.storage.get_stats() {
Ok(stats) => stats.total_projects,
Err(_) => 0,
}
}
/// 检查是否为空(替代 project_and_agent_map.is_empty
pub fn is_empty(&self) -> bool {
self.len() == 0
}
// ========== sessions 替代方法 ==========
/// 通过会话ID获取项目信息替代 sessions.get
pub fn get_by_session_id(&self, session_id: &str) -> Option<Arc<ProjectAndContainerInfo>> {
match self.storage.get_project_by_session(session_id) {
Ok(Some(record)) => {
let container = self.get_container_for_project(&record);
Some(Arc::new(DataBridge::project_record_to_info(
&record, container,
)))
}
Ok(None) => None,
Err(e) => {
warn!("Failed to get project for session {}: {}", session_id, e);
None
}
}
}
/// 更新会话信息(替代 sessions.insert 和会话更新逻辑)
pub fn update_session(
&self,
project_id: &str,
session_id: &str,
) -> Result<(), duckdb_manager::DuckDbError> {
self.storage.update_session(project_id, session_id)?;
debug!(
"Updating session: project_id={}, session_id={}",
project_id, session_id
);
Ok(())
}
/// 清除会话信息(将 session_id 设置为 NULL
///
/// 用于 Agent 停止后清理会话状态
pub fn clear_session(&self, project_id: &str) -> Result<(), duckdb_manager::DuckDbError> {
self.storage.clear_session(project_id)?;
debug!(
"Clearing session: project_id={}",
project_id
);
Ok(())
}
// ========== session_to_container_id 替代方法 ==========
/// 通过会话ID获取容器名称用于容器重启后的容器查询
///
/// 与 `get_container_id_by_session` 不同,返回稳定的 `container_name`(如 `computer-agent-runner-user_123`)。
/// 即使容器被重建container_name 保持不变,可直接通过 Docker API 查询容器状态。
pub fn get_container_name_by_session(&self, session_id: &str) -> Option<String> {
match self.storage.get_container_name_by_session(session_id) {
Ok(container_name) => {
debug!(
"Getting container name by session_id: session_id={}, container_name={:?}",
session_id, container_name
);
container_name
}
Err(e) => {
warn!(
" sessionID {} getcontainer failed: {}",
session_id, e
);
None
}
}
}
// ========== 活动时间更新方法 ==========
/// 更新项目活动时间,返回实际更新使用的时间戳
pub fn update_activity(&self, project_id: &str) -> Option<DateTime<Utc>> {
match self.storage.update_project_activity(project_id) {
Ok(Some(updated_time)) => {
// 使用相同的时间更新关联容器的活动时间
if let Ok(Some(record)) = self.storage.get_project(project_id) {
let _ = self
.storage
.update_container_activity_with_time(&record.container_id, updated_time);
}
Some(updated_time)
}
Ok(None) => None,
Err(e) => {
warn!("update project {} failed: {}", project_id, e);
None
}
}
}
/// 更新会话活动时间
pub fn update_session_activity(&self, session_id: &str) -> bool {
match self.storage.update_session_activity(session_id) {
Ok(updated) => {
if updated {
// 底层 storage.update_session_activity 现在会自动更新关联容器的活动时间
// 无需在此处手动调用,避免了使用不可靠的 container_id
}
updated
}
Err(e) => {
warn!("update session {} failed: {}", session_id, e);
false
}
}
}
// ========== Agent 状态更新方法 ==========
/// 原子更新 Agent 状态
pub fn update_agent_status(
&self,
project_id: &str,
status_code: i32,
status_name: &str,
) -> Result<bool, duckdb_manager::DuckDbError> {
self.storage
.update_agent_status(project_id, status_code, status_name)
}
// ========== 容器相关方法 ==========
/// 保存容器信息
pub fn save_container(
&self,
container: &ContainerBasicInfo,
service_type: Option<ServiceType>,
) -> Result<(), duckdb_manager::DuckDbError> {
let record = DataBridge::container_info_to_record(container, service_type);
self.storage.save_container(&record)
}
/// 获取容器信息
pub fn get_container(&self, container_id: &str) -> Option<ContainerBasicInfo> {
match self.storage.get_container(container_id) {
Ok(Some(record)) => Some(DataBridge::container_record_to_info(&record)),
Ok(None) => None,
Err(e) => {
warn!("getcontainer {} failed: {}", container_id, e);
None
}
}
}
/// 删除容器及其关联的项目
pub fn delete_container_with_projects(
&self,
container_id: &str,
) -> Result<(bool, usize), duckdb_manager::DuckDbError> {
self.storage.delete_container_with_projects(container_id)
}
/// 按服务类型获取所有容器
pub fn get_containers_by_service_type(
&self,
service_type: ServiceType,
) -> Vec<ContainerBasicInfo> {
match self.storage.get_containers_by_service_type(service_type) {
Ok(records) => records
.iter()
.map(DataBridge::container_record_to_info)
.collect(),
Err(e) => {
warn!("Failed to get container: {}", e);
Vec::new()
}
}
}
/// 获取所有容器记录
pub fn get_all_container_records(
&self,
) -> Result<Vec<ContainerRecord>, duckdb_manager::DuckDbError> {
self.storage.get_all_containers()
}
/// 根据容器ID获取关联的项目列表
pub fn get_projects_by_container_id(
&self,
container_id: &str,
) -> Result<Vec<ProjectRecord>, duckdb_manager::DuckDbError> {
self.storage.get_projects_by_container(container_id)
}
// ========== ComputerAgentRunner 模式专用方法 ==========
/// 通过用户ID获取容器信息ComputerAgentRunner模式
///
/// 在 ComputerAgentRunner 模式中,一个用户对应一个容器
/// 此方法返回该用户最近活跃项目关联的容器信息
pub fn get_container_by_user_id(&self, user_id: &str) -> Option<ContainerBasicInfo> {
match self.storage.get_latest_container_id_by_user_id(user_id) {
Ok(Some(container_id)) => {
// 通过容器ID获取容器信息
self.get_container(&container_id)
}
Ok(None) => {
debug!("No project for user_id {} ", user_id);
None
}
Err(e) => {
warn!("Failed to get container for user_id {}: {}", user_id, e);
None
}
}
}
/// 通过 Pod ID 获取容器信息(共享容器模式)
///
/// 在共享容器模式中,多个项目可能共享同一个容器
/// 此方法返回该 Pod 下最近活跃项目关联的容器信息
pub fn get_container_by_pod_id(&self, pod_id: &str) -> Option<ContainerBasicInfo> {
match self.storage.get_latest_container_id_by_pod_id(pod_id) {
Ok(Some(container_id)) => {
debug!(
"Found container_id for pod_id={}: container_id={}",
pod_id, container_id
);
self.get_container(&container_id)
}
Ok(None) => {
debug!("No container found for pod_id={}", pod_id);
None
}
Err(e) => {
warn!("Failed to get container for pod_id {}: {}", pod_id, e);
None
}
}
}
/// 通过用户ID查找所有项目ComputerAgentRunner模式
///
/// 返回该用户的所有项目记录,按最后活动时间倒序排列
pub fn find_projects_by_user_id(
&self,
user_id: &str,
) -> Vec<duckdb_manager::models::ProjectRecord> {
match self.storage.find_projects_by_user_id(user_id) {
Ok(projects) => projects,
Err(e) => {
warn!("Failed to get project for user_id {}: {}", user_id, e);
Vec::new()
}
}
}
/// 根据 pod_id 查找所有项目RCoder 共享容器模式)
///
/// 返回该 Pod 下所有项目记录,按最后活动时间倒序排列
pub fn find_projects_by_pod_id(
&self,
pod_id: &str,
) -> Vec<duckdb_manager::models::ProjectRecord> {
match self.storage.find_projects_by_pod_id(pod_id) {
Ok(projects) => projects,
Err(e) => {
warn!("Failed to get project for pod_id {}: {}", pod_id, e);
Vec::new()
}
}
}
// ========== 清理相关方法 ==========
/// 查找闲置容器
pub fn find_idle_containers(
&self,
idle_minutes: i64,
protection_minutes: i64,
) -> Vec<duckdb_manager::IdleContainerInfo> {
match self
.storage
.find_idle_containers(idle_minutes, protection_minutes)
{
Ok(containers) => containers,
Err(e) => {
warn!("Failed to get container: {}", e);
Vec::new()
}
}
}
/// 获取存储统计信息
pub fn get_stats(&self) -> StorageStats {
match self.storage.get_stats() {
Ok(stats) => stats,
Err(e) => {
warn!("get value failed: {}", e);
StorageStats::default()
}
}
}
// ========== 调试方法 ==========
/// 执行原始 SQL 查询(仅用于调试)
///
/// ⚠️ 此方法仅用于开发和调试目的
pub fn execute_raw_query(
&self,
sql: &str,
) -> Result<(Vec<String>, Vec<serde_json::Value>), String> {
self.storage
.execute_raw_query(sql)
.map_err(|e| format!("Query execution failed: {}", e))
}
/// 获取 DuckDB 内存使用统计(用于调试和监控)
pub fn get_memory_stats(&self) -> Result<String, String> {
self.storage
.get_memory_stats()
.map_err(|e| format!("Failed to get memory stats: {}", e))
}
// ========== 内部辅助方法 ==========
/// 获取项目关联的容器信息
fn get_container_for_project(&self, record: &ProjectRecord) -> Option<ContainerBasicInfo> {
match self.storage.get_container(&record.container_id) {
Ok(Some(container)) => Some(DataBridge::container_record_to_info(&container)),
_ => None,
}
}
}
impl Default for ProjectAdapter {
fn default() -> Self {
Self::new().expect("创建 ProjectAdapter 失败")
}
}
impl std::fmt::Debug for ProjectAdapter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProjectAdapter")
.field("storage", &"<DuckDbStorage>")
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_info(project_id: &str) -> ProjectAndContainerInfo {
let mut info = ProjectAndContainerInfo::new(project_id.to_string());
info.set_service_type(Some(ServiceType::RCoder));
info
}
#[test]
fn test_project_crud() {
let adapter = ProjectAdapter::new().unwrap();
let project_id = "test-project-1";
let info = Arc::new(create_test_info(project_id));
// 插入
adapter
.insert(project_id.to_string(), info.clone())
.unwrap();
assert!(adapter.contains_key(project_id));
// 获取
let retrieved = adapter.get(project_id);
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().project_id(), project_id);
// 删除
let removed = adapter.remove(project_id);
assert!(removed.is_some());
assert!(!adapter.contains_key(project_id));
}
#[test]
fn test_session_operations() {
let adapter = ProjectAdapter::new().unwrap();
let project_id = "test-project-2";
let session_id = "test-session-1";
// 创建项目
let mut info = create_test_info(project_id);
info.set_container(Some(ContainerBasicInfo {
container_id: "c1".to_string(),
container_name: "container-1".to_string(),
container_ip: "127.0.0.1".to_string(),
internal_port: 8080,
external_port: 8080,
project_id: project_id.to_string(),
status: "running".to_string(),
created_at: chrono::Utc::now(),
service_url: "http://localhost:8080".to_string(),
}));
adapter
.insert(project_id.to_string(), Arc::new(info))
.unwrap();
// 更新会话
adapter.update_session(project_id, session_id).unwrap();
// 通过会话ID获取
let by_session = adapter.get_by_session_id(session_id);
assert!(by_session.is_some());
assert_eq!(by_session.unwrap().project_id(), project_id);
// 获取容器名称
let container_name = adapter.get_container_name_by_session(session_id);
assert_eq!(container_name, Some("container-1".to_string()));
}
#[test]
fn test_iter() {
let adapter = ProjectAdapter::new().unwrap();
// 插入多个项目
for i in 0..3 {
let project_id = format!("iter-project-{}", i);
let info = Arc::new(create_test_info(&project_id));
adapter.insert(project_id, info).unwrap();
}
// 验证迭代
let count = adapter.iter().count();
assert_eq!(count, 3);
}
}

View File

@@ -0,0 +1,252 @@
//! 数据转换桥接器
//!
//! 提供 DuckDB 记录和 shared_types 结构之间的转换
use chrono::Utc;
use duckdb_manager::{ContainerRecord, ProjectRecord};
use shared_types::{
AgentStatus, ContainerBasicInfo, ModelProviderConfig, ProjectAndContainerInfo, ServiceType,
};
/// 数据桥接器
///
/// 提供 DuckDB 记录和应用层结构之间的转换方法
pub struct DataBridge;
impl DataBridge {
// ========== ProjectRecord <-> ProjectAndContainerInfo 转换 ==========
/// 将 ProjectRecord 转换为 ProjectAndContainerInfo
pub fn project_record_to_info(
record: &ProjectRecord,
container: Option<ContainerBasicInfo>,
) -> ProjectAndContainerInfo {
let mut info = ProjectAndContainerInfo::new(record.project_id.clone());
// 设置基本信息
info.set_session_id(record.session_id.clone());
info.set_user_id(record.user_id.clone());
info.set_pod_id(record.pod_id.clone());
info.set_service_type(Some(record.service_type.clone()));
info.set_container(container);
// 设置 Agent 状态
if let (Some(code), Some(name)) = (&record.agent_status_code, &record.agent_status_name) {
let status = Self::code_to_agent_status(*code, name);
info.set_status(Some(status));
}
// 设置请求ID
info.set_request_id(record.request_id.clone());
// 设置模型提供商配置
if let Some(json) = &record.model_provider_json {
if let Ok(config) = serde_json::from_str::<ModelProviderConfig>(json) {
info.set_model_provider(Some(config));
}
}
// 🔧 关键修复:从 record 恢复原始的时间戳
// 否则 created_at 会被 new() 设置为当前时间,导致清理任务一直认为容器"刚创建"
info.set_timestamps(record.created_at, record.last_activity);
info
}
/// 将 ProjectAndContainerInfo 转换为 ProjectRecord
pub fn info_to_project_record(
info: &ProjectAndContainerInfo,
project_id: &str,
) -> ProjectRecord {
let service_type = info.service_type().unwrap_or(ServiceType::RCoder);
let container_id = info
.container()
.map(|c| c.container_id.clone())
.unwrap_or_default();
let mut record = ProjectRecord::new(project_id.to_string(), service_type, container_id);
// 设置会话信息
record.session_id = info.session_id().map(|s| s.to_string());
// 设置用户ID
record.user_id = info.user_id().map(|s| s.to_string());
// 设置 Pod ID共享容器模式
record.pod_id = info.pod_id().map(|s| s.to_string());
// 设置 Agent 状态
if let Some(status) = info.status() {
let (code, name) = Self::agent_status_to_code(status);
record.agent_status_code = Some(code);
record.agent_status_name = Some(name);
}
// 设置请求ID
record.request_id = info.request_id().map(|s| s.to_string());
// 设置模型提供商配置
if let Some(config) = info.model_provider() {
if let Ok(json) = serde_json::to_string(config) {
record.model_provider_json = Some(json);
}
}
// 设置时间戳
record.created_at = info.created_at();
record.last_activity = info.last_activity();
// 如果有会话,设置会话时间
if info.session_id().is_some() {
record.session_created_at = Some(info.created_at());
record.session_last_activity = Some(info.last_activity());
}
record
}
// ========== ContainerRecord <-> ContainerBasicInfo 转换 ==========
/// 将 ContainerRecord 转换为 ContainerBasicInfo
pub fn container_record_to_info(record: &ContainerRecord) -> ContainerBasicInfo {
ContainerBasicInfo {
container_id: record.container_id.clone(),
container_name: record.container_name.clone(),
container_ip: record.container_ip.clone(),
internal_port: record.internal_port,
external_port: record.external_port,
project_id: String::new(), // 从关联的项目获取
status: record.status.clone(),
created_at: record.created_at,
service_url: record.service_url.clone(),
}
}
/// 将 ContainerBasicInfo 转换为 ContainerRecord
pub fn container_info_to_record(
info: &ContainerBasicInfo,
service_type: Option<ServiceType>,
) -> ContainerRecord {
ContainerRecord::new(
info.container_id.clone(),
info.container_name.clone(),
info.container_ip.clone(),
info.internal_port,
info.external_port,
service_type.unwrap_or(ServiceType::RCoder),
info.status.clone(),
info.service_url.clone(),
)
}
// ========== AgentStatus <-> 状态码/名称 转换 ==========
/// 将 AgentStatus 转换为状态码和名称
pub fn agent_status_to_code(status: &AgentStatus) -> (i32, String) {
match status {
AgentStatus::Pending => (3, "pending".to_string()),
AgentStatus::Idle => (0, "idle".to_string()),
AgentStatus::Active => (1, "active".to_string()),
AgentStatus::Terminating => (2, "terminating".to_string()),
}
}
/// 将状态码和名称转换为 AgentStatus
pub fn code_to_agent_status(code: i32, _name: &str) -> AgentStatus {
match code {
0 => AgentStatus::Idle,
1 => AgentStatus::Active,
2 => AgentStatus::Terminating,
3 => AgentStatus::Pending,
_ => AgentStatus::Idle, // 默认
}
}
// ========== 辅助方法 ==========
/// 创建项目记录并关联会话
pub fn create_project_with_session(
project_id: &str,
session_id: &str,
service_type: ServiceType,
container_id: &str,
) -> ProjectRecord {
let now = Utc::now();
let mut record = ProjectRecord::new(
project_id.to_string(),
service_type,
container_id.to_string(),
);
record.session_id = Some(session_id.to_string());
record.session_created_at = Some(now);
record.session_last_activity = Some(now);
record
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_status_conversion() {
// 测试各种状态的转换
let statuses = vec![
AgentStatus::Idle,
AgentStatus::Active,
AgentStatus::Terminating,
];
for status in statuses {
let (code, name) = DataBridge::agent_status_to_code(&status);
let converted = DataBridge::code_to_agent_status(code, &name);
assert_eq!(
std::mem::discriminant(&status),
std::mem::discriminant(&converted)
);
}
}
#[test]
fn test_container_info_conversion() {
let info = ContainerBasicInfo {
container_id: "c1".to_string(),
container_name: "test-container".to_string(),
container_ip: "192.168.1.100".to_string(),
internal_port: 8080,
external_port: 9090,
project_id: "p1".to_string(),
status: "running".to_string(),
created_at: Utc::now(),
service_url: "http://localhost:9090".to_string(),
};
let record = DataBridge::container_info_to_record(&info, Some(ServiceType::RCoder));
assert_eq!(record.container_id, info.container_id);
assert_eq!(record.container_name, info.container_name);
assert_eq!(record.container_ip, info.container_ip);
assert_eq!(record.internal_port, info.internal_port);
assert_eq!(record.external_port, info.external_port);
let converted = DataBridge::container_record_to_info(&record);
assert_eq!(converted.container_id, info.container_id);
assert_eq!(converted.container_name, info.container_name);
}
#[test]
fn test_project_record_conversion() {
let mut info = ProjectAndContainerInfo::new("test-project".to_string());
info.set_service_type(Some(ServiceType::RCoder));
info.set_session_id(Some("session-1".to_string()));
info.set_status(Some(AgentStatus::Active));
let record = DataBridge::info_to_project_record(&info, "test-project");
assert_eq!(record.project_id, "test-project");
assert_eq!(record.session_id, Some("session-1".to_string()));
assert_eq!(record.agent_status_code, Some(1));
assert_eq!(record.agent_status_name, Some("active".to_string()));
}
}

View File

@@ -0,0 +1,9 @@
//! 存储适配层
//!
//! 提供从 DashMap 到 DuckDB 存储的适配器
mod adapter;
mod bridge;
pub use adapter::ProjectAdapter;
pub use bridge::DataBridge;