添加qiming-rcoder模块
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
//! 默认 Agent 配置定义
|
||||
//!
|
||||
//! 此模块从编译时嵌入的 JSON 配置文件加载默认配置。
|
||||
//! 支持两种服务类型的配置:
|
||||
//! - RCoder: `configs/default_agents.json`
|
||||
//! - ComputerAgentRunner: `configs/computer_agent_default.json`
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::types::agent_config::AgentConfig;
|
||||
use crate::types::mcp_config::ContextServerConfig;
|
||||
|
||||
/// 编译时嵌入的 RCoder 默认配置 JSON
|
||||
const DEFAULT_CONFIG_JSON: &str = include_str!("../../configs/default_agents.json");
|
||||
|
||||
/// 编译时嵌入的 ComputerAgentRunner 默认配置 JSON
|
||||
const COMPUTER_AGENT_CONFIG_JSON: &str = include_str!("../../configs/computer_agent_default.json");
|
||||
|
||||
/// Claude Code ACP Agent 的默认 ID
|
||||
pub const CLAUDE_CODE_ACP_AGENT_ID: &str = "claude-code-acp-ts";
|
||||
|
||||
/// 默认配置的内部结构(用于 JSON 反序列化)
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct EmbeddedConfig {
|
||||
agent_servers: HashMap<String, AgentConfig>,
|
||||
#[serde(default)]
|
||||
context_servers: HashMap<String, ContextServerConfig>,
|
||||
}
|
||||
|
||||
/// 懒加载的默认配置
|
||||
///
|
||||
/// 在首次访问时解析 JSON,之后复用解析结果。
|
||||
/// 如果 JSON 解析失败,程序会 panic(这是编译时嵌入的文件,不应失败)。
|
||||
static DEFAULT_CONFIG: LazyLock<EmbeddedConfig> = LazyLock::new(|| {
|
||||
serde_json::from_str(DEFAULT_CONFIG_JSON).expect(
|
||||
"Failed to parse embedded default_agents.json. This is a bug - please check the JSON syntax.",
|
||||
)
|
||||
});
|
||||
|
||||
/// 懒加载的 ComputerAgentRunner 默认配置
|
||||
///
|
||||
/// 在首次访问时解析 JSON,之后复用解析结果。
|
||||
static COMPUTER_AGENT_CONFIG: LazyLock<EmbeddedConfig> = LazyLock::new(|| {
|
||||
serde_json::from_str(COMPUTER_AGENT_CONFIG_JSON).expect(
|
||||
"Failed to parse embedded computer_agent_default.json. This is a bug - please check the JSON syntax.",
|
||||
)
|
||||
});
|
||||
|
||||
/// 获取默认的 Claude Code ACP Agent 配置
|
||||
///
|
||||
/// 从 `configs/default_agents.json` 加载配置。
|
||||
/// 修改 JSON 文件后重新编译即可生效。
|
||||
///
|
||||
/// # Panics
|
||||
/// 如果 JSON 中不存在 `claude-code-acp-ts` 配置,会 panic。
|
||||
pub fn default_claude_code_agent() -> AgentConfig {
|
||||
DEFAULT_CONFIG
|
||||
.agent_servers
|
||||
.get(CLAUDE_CODE_ACP_AGENT_ID)
|
||||
.cloned()
|
||||
.expect("claude-code-acp-ts not found in default_agents.json")
|
||||
}
|
||||
|
||||
/// 获取默认的 Context Servers 配置
|
||||
///
|
||||
/// 从 `configs/default_agents.json` 加载配置。
|
||||
/// 修改 JSON 文件后重新编译即可生效。
|
||||
pub fn default_context_servers() -> HashMap<String, ContextServerConfig> {
|
||||
DEFAULT_CONFIG.context_servers.clone()
|
||||
}
|
||||
|
||||
/// 获取默认的 Agent Servers 配置
|
||||
///
|
||||
/// 从 `configs/default_agents.json` 加载配置。
|
||||
/// 修改 JSON 文件后重新编译即可生效。
|
||||
pub fn default_agent_servers() -> HashMap<String, AgentConfig> {
|
||||
DEFAULT_CONFIG.agent_servers.clone()
|
||||
}
|
||||
|
||||
/// 获取指定 Agent 的默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `agent_id`: Agent 标识符
|
||||
///
|
||||
/// # 返回
|
||||
/// 如果存在返回配置的克隆,否则返回 None
|
||||
pub fn get_default_agent(agent_id: &str) -> Option<AgentConfig> {
|
||||
DEFAULT_CONFIG.agent_servers.get(agent_id).cloned()
|
||||
}
|
||||
|
||||
/// 获取指定 Context Server 的默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `name`: Context Server 名称
|
||||
///
|
||||
/// # 返回
|
||||
/// 如果存在返回配置的克隆,否则返回 None
|
||||
pub fn get_default_context_server(name: &str) -> Option<ContextServerConfig> {
|
||||
DEFAULT_CONFIG.context_servers.get(name).cloned()
|
||||
}
|
||||
|
||||
/// 根据服务类型获取默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `service_type`: 服务类型(RCoder 或 ComputerAgentRunner)
|
||||
///
|
||||
/// # 返回
|
||||
/// 对应服务类型的默认配置引用
|
||||
pub fn get_default_config_by_service_type(
|
||||
service_type: &shared_types::ServiceType,
|
||||
) -> &'static EmbeddedConfig {
|
||||
match service_type {
|
||||
shared_types::ServiceType::RCoder => &DEFAULT_CONFIG,
|
||||
shared_types::ServiceType::ComputerAgentRunner => &COMPUTER_AGENT_CONFIG,
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据服务类型获取默认 Agent Servers 配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `service_type`: 服务类型
|
||||
///
|
||||
/// # 返回
|
||||
/// Agent Servers 配置的克隆
|
||||
pub fn default_agent_servers_for_service(
|
||||
service_type: &shared_types::ServiceType,
|
||||
) -> HashMap<String, AgentConfig> {
|
||||
get_default_config_by_service_type(service_type)
|
||||
.agent_servers
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// 根据服务类型获取默认 Context Servers 配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `service_type`: 服务类型
|
||||
///
|
||||
/// # 返回
|
||||
/// Context Servers 配置的克隆
|
||||
pub fn default_context_servers_for_service(
|
||||
service_type: &shared_types::ServiceType,
|
||||
) -> HashMap<String, ContextServerConfig> {
|
||||
get_default_config_by_service_type(service_type)
|
||||
.context_servers
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// 根据服务类型获取指定 Agent 的默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `agent_id`: Agent 标识符
|
||||
/// - `service_type`: 服务类型
|
||||
///
|
||||
/// # 返回
|
||||
/// 如果存在返回配置的克隆,否则返回 None
|
||||
pub fn get_default_agent_for_service(
|
||||
agent_id: &str,
|
||||
service_type: &shared_types::ServiceType,
|
||||
) -> Option<AgentConfig> {
|
||||
get_default_config_by_service_type(service_type)
|
||||
.agent_servers
|
||||
.get(agent_id)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// 根据服务类型获取指定 Context Server 的默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `name`: Context Server 名称
|
||||
/// - `service_type`: 服务类型
|
||||
///
|
||||
/// # 返回
|
||||
/// 如果存在返回配置的克隆,否则返回 None
|
||||
pub fn get_default_context_server_for_service(
|
||||
name: &str,
|
||||
service_type: &shared_types::ServiceType,
|
||||
) -> Option<ContextServerConfig> {
|
||||
get_default_config_by_service_type(service_type)
|
||||
.context_servers
|
||||
.get(name)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_config_loads_successfully() {
|
||||
// 确保 JSON 能成功解析
|
||||
let _ = &*DEFAULT_CONFIG;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_claude_code_agent() {
|
||||
let agent = default_claude_code_agent();
|
||||
assert_eq!(agent.agent_id, CLAUDE_CODE_ACP_AGENT_ID);
|
||||
assert_eq!(agent.command, "claude-code-acp-ts");
|
||||
assert!(agent.enabled);
|
||||
assert!(agent.env.contains_key("ANTHROPIC_API_KEY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_context_servers() {
|
||||
let servers = default_context_servers();
|
||||
// 验证 JSON 中定义的 context servers
|
||||
assert!(servers.contains_key("fetch") || servers.contains_key("context7"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_agent_servers() {
|
||||
let agents = default_agent_servers();
|
||||
assert!(agents.contains_key(CLAUDE_CODE_ACP_AGENT_ID));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_agent() {
|
||||
let agent = get_default_agent(CLAUDE_CODE_ACP_AGENT_ID);
|
||||
assert!(agent.is_some());
|
||||
assert_eq!(agent.unwrap().agent_id, CLAUDE_CODE_ACP_AGENT_ID);
|
||||
|
||||
let non_existent = get_default_agent("non-existent-agent");
|
||||
assert!(non_existent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_context_server() {
|
||||
// 测试存在的 server
|
||||
let servers = default_context_servers();
|
||||
if let Some(first_server_name) = servers.keys().next() {
|
||||
let server = get_default_context_server(first_server_name);
|
||||
assert!(server.is_some());
|
||||
}
|
||||
|
||||
// 测试不存在的 server
|
||||
let non_existent = get_default_context_server("non-existent-server");
|
||||
assert!(non_existent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_computer_agent_config_loads_successfully() {
|
||||
// 确保 ComputerAgentRunner 配置能成功解析
|
||||
let _ = &*COMPUTER_AGENT_CONFIG;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_config_by_service_type() {
|
||||
// 测试 RCoder 配置
|
||||
let rcoder_config = get_default_config_by_service_type(&shared_types::ServiceType::RCoder);
|
||||
assert!(
|
||||
rcoder_config
|
||||
.agent_servers
|
||||
.contains_key(CLAUDE_CODE_ACP_AGENT_ID)
|
||||
);
|
||||
|
||||
// 测试 ComputerAgentRunner 配置
|
||||
let car_config =
|
||||
get_default_config_by_service_type(&shared_types::ServiceType::ComputerAgentRunner);
|
||||
assert!(
|
||||
car_config
|
||||
.agent_servers
|
||||
.contains_key(CLAUDE_CODE_ACP_AGENT_ID)
|
||||
);
|
||||
|
||||
// ComputerAgentRunner 应该有更多 context servers
|
||||
assert!(
|
||||
car_config.context_servers.contains_key("chrome-devtools")
|
||||
|| car_config.context_servers.contains_key("puppeteer")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_agent_servers_for_service() {
|
||||
// RCoder
|
||||
let rcoder_servers = default_agent_servers_for_service(&shared_types::ServiceType::RCoder);
|
||||
assert!(!rcoder_servers.is_empty());
|
||||
|
||||
// ComputerAgentRunner
|
||||
let car_servers =
|
||||
default_agent_servers_for_service(&shared_types::ServiceType::ComputerAgentRunner);
|
||||
assert!(!car_servers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_context_servers_for_service() {
|
||||
// RCoder - 基础 MCP servers
|
||||
let rcoder_contexts =
|
||||
default_context_servers_for_service(&shared_types::ServiceType::RCoder);
|
||||
assert!(rcoder_contexts.contains_key("fetch") || rcoder_contexts.contains_key("context7"));
|
||||
|
||||
// ComputerAgentRunner - 增强 MCP servers
|
||||
let car_contexts =
|
||||
default_context_servers_for_service(&shared_types::ServiceType::ComputerAgentRunner);
|
||||
// 至少应该包含浏览器相关的 server
|
||||
assert!(car_contexts.len() >= rcoder_contexts.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_agent_for_service() {
|
||||
// 测试 RCoder
|
||||
let agent = get_default_agent_for_service(
|
||||
CLAUDE_CODE_ACP_AGENT_ID,
|
||||
&shared_types::ServiceType::RCoder,
|
||||
);
|
||||
assert!(agent.is_some());
|
||||
|
||||
// 测试 ComputerAgentRunner
|
||||
let agent = get_default_agent_for_service(
|
||||
CLAUDE_CODE_ACP_AGENT_ID,
|
||||
&shared_types::ServiceType::ComputerAgentRunner,
|
||||
);
|
||||
assert!(agent.is_some());
|
||||
|
||||
// 测试不存在的 agent
|
||||
let agent =
|
||||
get_default_agent_for_service("non-existent-agent", &shared_types::ServiceType::RCoder);
|
||||
assert!(agent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_context_server_for_service() {
|
||||
// RCoder context server
|
||||
let rcoder_contexts =
|
||||
default_context_servers_for_service(&shared_types::ServiceType::RCoder);
|
||||
if let Some(first_server_name) = rcoder_contexts.keys().next() {
|
||||
let server = get_default_context_server_for_service(
|
||||
first_server_name,
|
||||
&shared_types::ServiceType::RCoder,
|
||||
);
|
||||
assert!(server.is_some());
|
||||
}
|
||||
|
||||
// ComputerAgentRunner context server
|
||||
let _server = get_default_context_server_for_service(
|
||||
"chrome-devtools",
|
||||
&shared_types::ServiceType::ComputerAgentRunner,
|
||||
);
|
||||
// 可能存在或不存在,取决于配置
|
||||
|
||||
// 测试不存在的 server
|
||||
let server = get_default_context_server_for_service(
|
||||
"non-existent-server",
|
||||
&shared_types::ServiceType::RCoder,
|
||||
);
|
||||
assert!(server.is_none());
|
||||
}
|
||||
}
|
||||
13
qiming-rcoder/crates/agent_config/src/config/mod.rs
Normal file
13
qiming-rcoder/crates/agent_config/src/config/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! Configuration management module.
|
||||
//!
|
||||
//! - `default_agent_config`: 默认 Agent 配置定义
|
||||
//! - `servers_config`: 配置文件加载和管理
|
||||
//! - `prompt_assembler`: 提示词配置组装工具
|
||||
|
||||
pub mod default_agent_config;
|
||||
pub mod prompt_assembler;
|
||||
pub mod servers_config;
|
||||
|
||||
pub use default_agent_config::*;
|
||||
pub use prompt_assembler::PromptConfigAssembler;
|
||||
pub use servers_config::*;
|
||||
384
qiming-rcoder/crates/agent_config/src/config/prompt_assembler.rs
Normal file
384
qiming-rcoder/crates/agent_config/src/config/prompt_assembler.rs
Normal file
@@ -0,0 +1,384 @@
|
||||
//! 提示词配置组装工具
|
||||
//!
|
||||
//! 将用户入参组装为内部使用的配置结构。
|
||||
//! 简化设计:直接使用入参,无优先级冲突。
|
||||
|
||||
use shared_types::{ChatAgentConfig, ChatAgentServerConfig};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::servers_config::AgentServersConfig;
|
||||
use crate::types::agent_config::AgentConfig;
|
||||
use crate::types::mcp_config::ContextServerConfig;
|
||||
|
||||
/// 提示词配置组装器
|
||||
///
|
||||
/// 职责:
|
||||
/// 1. 组装系统提示词(入参 > 默认配置)
|
||||
/// 2. 应用用户提示词模板
|
||||
/// 3. 组装 Agent 服务器配置
|
||||
/// 4. 合并 MCP 服务器配置
|
||||
pub struct PromptConfigAssembler {
|
||||
/// 系统提示词入参
|
||||
system_prompt: Option<String>,
|
||||
/// 用户提示词模板入参
|
||||
user_prompt_template: Option<String>,
|
||||
/// Agent 运行时配置入参
|
||||
agent_config: Option<ChatAgentConfig>,
|
||||
/// 默认配置
|
||||
default_config: AgentServersConfig,
|
||||
}
|
||||
|
||||
impl PromptConfigAssembler {
|
||||
/// 创建新的配置组装器
|
||||
///
|
||||
/// # 参数
|
||||
/// - `default_config`: 默认配置(从文件或内置配置加载)
|
||||
pub fn new(default_config: AgentServersConfig) -> Self {
|
||||
Self {
|
||||
system_prompt: None,
|
||||
user_prompt_template: None,
|
||||
agent_config: None,
|
||||
default_config,
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置系统提示词覆盖
|
||||
pub fn with_system_prompt(mut self, system_prompt: Option<String>) -> Self {
|
||||
self.system_prompt = system_prompt;
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置用户提示词模板覆盖
|
||||
pub fn with_user_prompt_template(mut self, template: Option<String>) -> Self {
|
||||
self.user_prompt_template = template;
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置 Agent 运行时配置覆盖
|
||||
pub fn with_agent_config(mut self, config: Option<ChatAgentConfig>) -> Self {
|
||||
self.agent_config = config;
|
||||
self
|
||||
}
|
||||
|
||||
/// 获取最终的系统提示词
|
||||
///
|
||||
/// 逻辑:入参有值则使用入参,否则使用默认配置
|
||||
pub fn get_system_prompt(&self, agent_id: &str) -> String {
|
||||
// 入参有值且非空,直接使用
|
||||
if let Some(ref sp) = self.system_prompt {
|
||||
if !sp.is_empty() {
|
||||
return sp.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用默认配置
|
||||
self.default_config.get_system_prompt(agent_id)
|
||||
}
|
||||
|
||||
/// 应用用户提示词模板
|
||||
///
|
||||
/// 逻辑:
|
||||
/// 1. 如果有模板入参,使用模板替换 `{user_prompt}`
|
||||
/// 2. 如果没有模板入参,检查默认配置
|
||||
/// 3. 都没有,直接返回原始输入
|
||||
pub fn apply_user_prompt(&self, agent_id: &str, user_input: &str) -> String {
|
||||
// 入参有模板且非空,使用入参模板
|
||||
if let Some(ref template) = self.user_prompt_template {
|
||||
if !template.is_empty() {
|
||||
return template.replace("{user_prompt}", user_input);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查默认配置中的 user_prompt 模板
|
||||
if let Some(agent) = self.default_config.get_agent(agent_id) {
|
||||
if let Some(ref prompt_config) = agent.user_prompt {
|
||||
if prompt_config.enabled {
|
||||
return prompt_config.apply(user_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 无模板,直接返回原始输入
|
||||
user_input.to_string()
|
||||
}
|
||||
|
||||
/// 获取最终的 Agent 服务器配置
|
||||
///
|
||||
/// 逻辑:
|
||||
/// 1. 如果入参有 agent_server,与默认配置合并(入参字段覆盖默认值)
|
||||
/// 2. 如果入参没有 agent_server,使用默认配置
|
||||
pub fn get_agent_server_config(&self, default_agent_id: &str) -> AgentConfig {
|
||||
// 获取默认的 Agent 配置
|
||||
let default_agent = self
|
||||
.default_config
|
||||
.get_agent(default_agent_id)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
// 如果入参有 agent_server 配置,合并覆盖
|
||||
if let Some(ref config) = self.agent_config {
|
||||
if let Some(ref agent_server) = config.agent_server {
|
||||
return self.merge_agent_config(&default_agent, agent_server);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用默认配置
|
||||
default_agent
|
||||
}
|
||||
|
||||
/// 合并 Agent 配置(入参覆盖默认值)
|
||||
fn merge_agent_config(
|
||||
&self,
|
||||
default: &AgentConfig,
|
||||
override_config: &ChatAgentServerConfig,
|
||||
) -> AgentConfig {
|
||||
let mut merged = default.clone();
|
||||
|
||||
// agent_id: 入参有值则覆盖
|
||||
if let Some(ref agent_id) = override_config.agent_id {
|
||||
merged.agent_id = agent_id.clone();
|
||||
}
|
||||
|
||||
// command: 入参有值则覆盖
|
||||
if let Some(ref command) = override_config.command {
|
||||
merged.command = command.clone();
|
||||
}
|
||||
|
||||
// args: 入参有值则覆盖(替换而非追加)
|
||||
if let Some(ref args) = override_config.args {
|
||||
merged.args = args.clone();
|
||||
}
|
||||
|
||||
// env: 入参有值则合并(入参优先)
|
||||
if let Some(ref env) = override_config.env {
|
||||
for (key, value) in env {
|
||||
merged.env.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// metadata: 入参有值则合并
|
||||
if let Some(ref metadata) = override_config.metadata {
|
||||
for (key, value) in metadata {
|
||||
merged.metadata.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
merged
|
||||
}
|
||||
|
||||
/// 获取最终的 MCP 服务器配置
|
||||
///
|
||||
/// 逻辑:
|
||||
/// 1. 如果入参明确提供了非空的 context_servers,使用入参
|
||||
/// 2. 否则使用默认配置
|
||||
///
|
||||
/// 注意:即使提供了 agent_config,但 context_servers 为空时,仍使用默认配置
|
||||
pub fn get_context_servers(&self) -> HashMap<String, ContextServerConfig> {
|
||||
// 入参有非空的 MCP 配置,使用入参
|
||||
if let Some(ref config) = self.agent_config {
|
||||
if config.has_context_servers() {
|
||||
return config
|
||||
.context_servers
|
||||
.iter()
|
||||
.map(|(name, chat_config)| {
|
||||
let ctx_config = ContextServerConfig {
|
||||
source: chat_config.source.clone(),
|
||||
enabled: chat_config.enabled,
|
||||
command: chat_config.command.clone(),
|
||||
args: chat_config.args.clone(),
|
||||
env: chat_config.env.clone(),
|
||||
};
|
||||
(name.clone(), ctx_config)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
// context_servers 为空或未提供,使用默认配置
|
||||
self.default_config.context_servers.clone()
|
||||
}
|
||||
|
||||
/// 获取使用的 Agent ID
|
||||
///
|
||||
/// 逻辑:入参有指定则使用入参,否则使用默认
|
||||
pub fn get_agent_id(&self, default_agent_id: &str) -> String {
|
||||
if let Some(ref config) = self.agent_config {
|
||||
if let Some(ref agent_server) = config.agent_server {
|
||||
return agent_server.get_agent_id().to_string();
|
||||
}
|
||||
}
|
||||
default_agent_id.to_string()
|
||||
}
|
||||
|
||||
/// 检查是否有系统提示词覆盖
|
||||
pub fn has_system_prompt_override(&self) -> bool {
|
||||
self.system_prompt.as_ref().is_some_and(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// 检查是否有用户提示词模板覆盖
|
||||
pub fn has_user_prompt_template_override(&self) -> bool {
|
||||
self.user_prompt_template
|
||||
.as_ref()
|
||||
.is_some_and(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// 检查是否有 Agent 配置覆盖
|
||||
pub fn has_agent_config_override(&self) -> bool {
|
||||
self.agent_config.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_test_default_config() -> AgentServersConfig {
|
||||
AgentServersConfig::default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_prompt_with_override() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config)
|
||||
.with_system_prompt(Some("自定义系统提示词".to_string()));
|
||||
|
||||
let result = assembler.get_system_prompt("claude-code-acp-ts");
|
||||
assert_eq!(result, "自定义系统提示词");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_prompt_without_override() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config);
|
||||
|
||||
let result = assembler.get_system_prompt("claude-code-acp-ts");
|
||||
// 应该返回默认系统提示词
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_prompt_empty_override() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config).with_system_prompt(Some("".to_string()));
|
||||
|
||||
let result = assembler.get_system_prompt("claude-code-acp-ts");
|
||||
// 空字符串应该回退到默认值
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user_prompt_with_template() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config)
|
||||
.with_user_prompt_template(Some("请用 Rust 完成:{user_prompt}".to_string()));
|
||||
|
||||
let result = assembler.apply_user_prompt("claude-code-acp-ts", "Hello World");
|
||||
assert_eq!(result, "请用 Rust 完成:Hello World");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user_prompt_without_template() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config);
|
||||
|
||||
let result = assembler.apply_user_prompt("claude-code-acp-ts", "Hello World");
|
||||
// 没有模板时应该返回原始输入
|
||||
assert_eq!(result, "Hello World");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_servers_with_override() {
|
||||
let config = create_test_default_config();
|
||||
let mut context_servers = HashMap::new();
|
||||
context_servers.insert(
|
||||
"my-mcp".to_string(),
|
||||
shared_types::ChatContextServerConfig {
|
||||
source: "custom".to_string(),
|
||||
enabled: true,
|
||||
command: Some("bunx".to_string()),
|
||||
args: Some(vec!["-y".to_string(), "my-mcp-server".to_string()]),
|
||||
env: None,
|
||||
},
|
||||
);
|
||||
|
||||
let agent_config = ChatAgentConfig {
|
||||
agent_server: None,
|
||||
context_servers,
|
||||
resource_limits: None,
|
||||
};
|
||||
|
||||
let assembler = PromptConfigAssembler::new(config).with_agent_config(Some(agent_config));
|
||||
|
||||
let result = assembler.get_context_servers();
|
||||
assert!(result.contains_key("my-mcp"));
|
||||
assert_eq!(
|
||||
result.get("my-mcp").unwrap().command,
|
||||
Some("bunx".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_servers_without_override() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config);
|
||||
|
||||
let result = assembler.get_context_servers();
|
||||
// 应该返回默认的 context servers
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_agent_id_with_override() {
|
||||
let config = create_test_default_config();
|
||||
let agent_config = ChatAgentConfig {
|
||||
agent_server: Some(shared_types::ChatAgentServerConfig {
|
||||
agent_id: Some("custom-agent".to_string()),
|
||||
command: None,
|
||||
args: None,
|
||||
env: None,
|
||||
metadata: None,
|
||||
}),
|
||||
context_servers: HashMap::new(),
|
||||
resource_limits: None,
|
||||
};
|
||||
|
||||
let assembler = PromptConfigAssembler::new(config).with_agent_config(Some(agent_config));
|
||||
|
||||
let result = assembler.get_agent_id("claude-code-acp-ts");
|
||||
assert_eq!(result, "custom-agent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_agent_id_without_override() {
|
||||
let config = create_test_default_config();
|
||||
let assembler = PromptConfigAssembler::new(config);
|
||||
|
||||
let result = assembler.get_agent_id("claude-code-acp-ts");
|
||||
assert_eq!(result, "claude-code-acp-ts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_agent_config_env() {
|
||||
let config = create_test_default_config();
|
||||
let mut override_env = HashMap::new();
|
||||
override_env.insert("NEW_VAR".to_string(), "new_value".to_string());
|
||||
|
||||
let agent_config = ChatAgentConfig {
|
||||
agent_server: Some(shared_types::ChatAgentServerConfig {
|
||||
agent_id: None,
|
||||
command: None,
|
||||
args: None,
|
||||
env: Some(override_env),
|
||||
metadata: None,
|
||||
}),
|
||||
context_servers: HashMap::new(),
|
||||
resource_limits: None,
|
||||
};
|
||||
|
||||
let assembler = PromptConfigAssembler::new(config).with_agent_config(Some(agent_config));
|
||||
|
||||
let result = assembler.get_agent_server_config("claude-code-acp-ts");
|
||||
assert!(result.env.contains_key("NEW_VAR"));
|
||||
assert_eq!(result.env.get("NEW_VAR"), Some(&"new_value".to_string()));
|
||||
}
|
||||
}
|
||||
226
qiming-rcoder/crates/agent_config/src/config/servers_config.rs
Normal file
226
qiming-rcoder/crates/agent_config/src/config/servers_config.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
//! Agent servers configuration structure.
|
||||
//!
|
||||
//! 此模块负责配置的查询和管理。
|
||||
//! 默认配置从 `default_agent_config` 模块加载(来源于 `configs/default_agents.json`)。
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use super::default_agent_config::{
|
||||
default_agent_servers, default_agent_servers_for_service, default_context_servers,
|
||||
default_context_servers_for_service,
|
||||
};
|
||||
use crate::types::agent_config::AgentConfig;
|
||||
use crate::types::error::{ConfigError, Result};
|
||||
use crate::types::mcp_config::ContextServerConfig;
|
||||
use crate::types::system_prompt::DEFAULT_SYSTEM_PROMPT;
|
||||
|
||||
/// Agent servers configuration
|
||||
///
|
||||
/// 包含所有 Agent 和 Context Server 的配置集合。
|
||||
/// 默认配置来自 `configs/default_agents.json`(编译时嵌入)。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentServersConfig {
|
||||
/// Agent servers configuration
|
||||
pub agent_servers: HashMap<String, AgentConfig>,
|
||||
|
||||
/// Context servers configuration (MCP servers)
|
||||
#[serde(default)]
|
||||
pub context_servers: HashMap<String, ContextServerConfig>,
|
||||
}
|
||||
|
||||
impl AgentServersConfig {
|
||||
/// 获取默认配置
|
||||
///
|
||||
/// 返回从 `configs/default_agents.json` 加载的配置。
|
||||
///
|
||||
/// # 已弃用
|
||||
///
|
||||
/// 此方法已弃用,请使用 `load_or_default_for_service` 以支持多服务类型。
|
||||
#[deprecated(
|
||||
since = "0.2.0",
|
||||
note = "请使用 load_or_default_for_service 以支持多服务类型"
|
||||
)]
|
||||
pub async fn load_or_default() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// 根据服务类型加载或使用默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `service_type`: 服务类型(RCoder 或 ComputerAgentRunner)
|
||||
///
|
||||
/// # 返回
|
||||
/// 对应服务类型的默认配置
|
||||
pub async fn load_or_default_for_service(service_type: &shared_types::ServiceType) -> Self {
|
||||
Self::default_for_service(service_type)
|
||||
}
|
||||
|
||||
/// 根据服务类型创建默认配置
|
||||
///
|
||||
/// # 参数
|
||||
/// - `service_type`: 服务类型
|
||||
///
|
||||
/// # 返回
|
||||
/// 对应服务类型的默认配置实例
|
||||
pub fn default_for_service(service_type: &shared_types::ServiceType) -> Self {
|
||||
Self {
|
||||
agent_servers: default_agent_servers_for_service(service_type),
|
||||
context_servers: default_context_servers_for_service(service_type),
|
||||
}
|
||||
}
|
||||
|
||||
/// 从外部文件加载配置
|
||||
///
|
||||
/// 用于加载用户自定义的配置文件,覆盖默认配置。
|
||||
pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
return Err(ConfigError::file_not_found(path.display()).into());
|
||||
}
|
||||
|
||||
let content = tokio::fs::read_to_string(path)
|
||||
.await
|
||||
.map_err(|e| ConfigError::Read(format!("{}: {}", path.display(), e)))?;
|
||||
|
||||
Self::from_json(&content)
|
||||
}
|
||||
|
||||
/// 从 JSON 字符串加载配置
|
||||
///
|
||||
/// 用于解析用户传入的 JSON 配置。
|
||||
pub fn from_json(json: &str) -> Result<Self> {
|
||||
serde_json::from_str(json).map_err(|e| ConfigError::Deserialization(e.to_string()).into())
|
||||
}
|
||||
|
||||
/// Validate configuration
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
// Validate agent servers
|
||||
for (name, config) in &self.agent_servers {
|
||||
if config.agent_id.is_empty() {
|
||||
return Err(
|
||||
ConfigError::missing_field(format!("agent_servers.{}.agent_id", name)).into(),
|
||||
);
|
||||
}
|
||||
if config.command.is_empty() {
|
||||
return Err(
|
||||
ConfigError::missing_field(format!("agent_servers.{}.command", name)).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate context servers
|
||||
for (name, config) in &self.context_servers {
|
||||
if config.command.is_none() {
|
||||
return Err(ConfigError::missing_field(format!(
|
||||
"context_servers.{}.command",
|
||||
name
|
||||
))
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all enabled agents
|
||||
pub fn get_enabled_agents(&self) -> Vec<&AgentConfig> {
|
||||
self.agent_servers
|
||||
.values()
|
||||
.filter(|config| config.enabled)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get an agent configuration by ID
|
||||
pub fn get_agent(&self, agent_id: &str) -> Option<&AgentConfig> {
|
||||
self.agent_servers.get(agent_id)
|
||||
}
|
||||
|
||||
/// 获取系统提示词(优先使用配置,否则使用默认值)
|
||||
///
|
||||
/// # 参数
|
||||
/// - `agent_id`: Agent 标识符
|
||||
///
|
||||
/// # 返回
|
||||
/// 根据配置的 source 字段决定返回内容:
|
||||
/// - `source: "embedded"` -> 返回编译时嵌入的默认提示词
|
||||
/// - `source: "custom"` 且 template 非空 -> 返回自定义模板内容
|
||||
/// - 其他情况 -> 回退到编译时嵌入的默认提示词
|
||||
///
|
||||
/// 注意:此方法始终返回有效的系统提示词,不受 enabled 字段影响。
|
||||
/// 如需检查是否启用,请直接调用 `SystemPromptConfig::get_prompt()`。
|
||||
pub fn get_system_prompt(&self, agent_id: &str) -> String {
|
||||
if let Some(agent) = self.get_agent(agent_id) {
|
||||
// 如果有系统提示词配置,使用 get_prompt_or_default() 确保始终返回有效提示词
|
||||
if let Some(ref prompt_config) = agent.system_prompt {
|
||||
return prompt_config.get_prompt_or_default().to_string();
|
||||
}
|
||||
}
|
||||
// 回退到编译时嵌入的默认值
|
||||
DEFAULT_SYSTEM_PROMPT.to_string()
|
||||
}
|
||||
|
||||
/// Get all enabled context servers
|
||||
pub fn get_enabled_context_servers(&self) -> Vec<&ContextServerConfig> {
|
||||
self.context_servers
|
||||
.values()
|
||||
.filter(|config| config.enabled)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a context server configuration by name
|
||||
pub fn get_context_server(&self, name: &str) -> Option<&ContextServerConfig> {
|
||||
self.context_servers.get(name)
|
||||
}
|
||||
|
||||
/// 创建默认配置
|
||||
///
|
||||
/// 从 `configs/default_agents.json` 加载的配置(编译时嵌入)。
|
||||
/// 修改 JSON 文件后重新编译即可生效。
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
agent_servers: default_agent_servers(),
|
||||
context_servers: default_context_servers(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_or_default_for_service() {
|
||||
// 测试 RCoder
|
||||
let rcoder_config =
|
||||
AgentServersConfig::load_or_default_for_service(&shared_types::ServiceType::RCoder)
|
||||
.await;
|
||||
assert!(!rcoder_config.agent_servers.is_empty());
|
||||
|
||||
// 测试 ComputerAgentRunner
|
||||
let car_config = AgentServersConfig::load_or_default_for_service(
|
||||
&shared_types::ServiceType::ComputerAgentRunner,
|
||||
)
|
||||
.await;
|
||||
assert!(!car_config.agent_servers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_for_service() {
|
||||
// 测试 RCoder
|
||||
let rcoder_config =
|
||||
AgentServersConfig::default_for_service(&shared_types::ServiceType::RCoder);
|
||||
assert!(!rcoder_config.agent_servers.is_empty());
|
||||
|
||||
// 测试 ComputerAgentRunner
|
||||
let car_config = AgentServersConfig::default_for_service(
|
||||
&shared_types::ServiceType::ComputerAgentRunner,
|
||||
);
|
||||
assert!(!car_config.agent_servers.is_empty());
|
||||
|
||||
// 验证配置不同
|
||||
// ComputerAgentRunner 应该有更多或不同的 context servers
|
||||
assert!(car_config.context_servers.len() >= rcoder_config.context_servers.len());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user