feat(agents): allow CPA configuration for config-only clients
This commit is contained in:
1 parent
b0f69877f8
commit
286f4bbe1b
6 files changed
+98
-23
No files matched your search
@@ -896,6 +896,24 @@ fn pi_template_only_writes_configuration_and_package_references() {
|
||||
assert_eq!(list_backups("pi", &home.0).unwrap().versions.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pi_config_file_allows_cpa_connection_without_detected_cli() {
|
||||
let home = Home::new();
|
||||
let paths = config_paths("pi", &home.0).unwrap();
|
||||
save(&paths[0], "{}\n");
|
||||
|
||||
let before = inspect_pi_provider_status(&home.0, 8317, "test-key");
|
||||
assert!(before.config_exists);
|
||||
assert!(!before.plugin_installed);
|
||||
|
||||
configure_pi_provider_without_cli_inner(&home.0, 8317, "test-key", "gpt-one").unwrap();
|
||||
|
||||
let connected = inspect_pi_provider_status(&home.0, 8317, "test-key");
|
||||
assert!(connected.configured);
|
||||
assert!(connected.plugin_installed);
|
||||
assert_eq!(connected.current_model.as_deref(), Some("gpt-one"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_setup_repairs_legacy_names_without_changing_other_profiles() {
|
||||
if !AgentClient::ClaudeDesktop.supported_platform() {
|
||||
|
||||
@@ -202,14 +202,23 @@ pub(crate) async fn install_pi_provider(
|
||||
.home_dir()
|
||||
.map_err(|error| format!("无法获取用户目录: {error}"))?;
|
||||
let config = gui_config_state.snapshot()?;
|
||||
let executable = find_pi_executable(&home)
|
||||
.ok_or_else(|| "未检测到 Pi CLI,请先安装 Pi 并确保 pi 命令在 PATH 中".to_string())?;
|
||||
let executable = find_pi_executable(&home);
|
||||
if executable.is_none()
|
||||
&& !inspect_pi_provider_status(&home, config.port, effective_agent_api_key(&config))
|
||||
.config_exists
|
||||
{
|
||||
return Err("未检测到 Pi CLI 或 Pi 配置文件".to_string());
|
||||
}
|
||||
let model = resolve_pi_default_model(&config, &model).await?;
|
||||
let port = config.port;
|
||||
let api_key = effective_agent_api_key(&config).to_string();
|
||||
let proxy_url = config.proxy_url.clone();
|
||||
let result = tauri::async_runtime::spawn_blocking(move || {
|
||||
install_pi_provider_inner(&home, &executable, port, &api_key, &model, &proxy_url)
|
||||
if let Some(executable) = executable {
|
||||
install_pi_provider_inner(&home, &executable, port, &api_key, &model, &proxy_url)
|
||||
} else {
|
||||
configure_pi_provider_without_cli_inner(&home, port, &api_key, &model)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("安装 Pi CLIProxyAPI provider 任务失败: {error}"))??;
|
||||
@@ -1257,8 +1266,7 @@ pub(crate) fn validate_agent_can_enable(
|
||||
));
|
||||
}
|
||||
let detection = inspect_agent_config(client, home, port, api_key);
|
||||
let workbuddy_config_available = client == AgentClient::WorkBuddy && detection.config_exists;
|
||||
if !detection.installed && !workbuddy_config_available {
|
||||
if !detection.installed && !detection.config_exists {
|
||||
return Err(format!("{} is not installed", client.name()));
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -307,6 +307,20 @@ pub(crate) fn build_pi_provider_settings(
|
||||
let object = root
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| "Pi settings.json 根节点必须是 JSON 对象".to_string())?;
|
||||
let packages = object
|
||||
.entry("packages")
|
||||
.or_insert_with(|| serde_json::json!([]))
|
||||
.as_array_mut()
|
||||
.ok_or_else(|| "Pi settings.json packages 必须是数组".to_string())?;
|
||||
if !packages.iter().any(|package| {
|
||||
package.as_str().is_some_and(pi_package_source_matches)
|
||||
|| package
|
||||
.get("source")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(pi_package_source_matches)
|
||||
}) {
|
||||
packages.push(serde_json::json!(PI_CLIPROXYAPI_PACKAGE));
|
||||
}
|
||||
object.insert(
|
||||
"defaultProvider".to_string(),
|
||||
serde_json::json!(PI_CLIPROXYAPI_PROVIDER_ID),
|
||||
@@ -389,8 +403,10 @@ pub(crate) fn inspect_pi_provider_status(
|
||||
"Pi 插件配置不完整,请使用“应用配置”重新写入凭据、默认 provider 和默认模型".to_string(),
|
||||
);
|
||||
}
|
||||
if executable.is_none() && plugin_installed {
|
||||
warnings.push("已找到 Pi 插件配置,但未检测到 Pi CLI 命令".to_string());
|
||||
if executable.is_none() && config_exists {
|
||||
warnings.push(
|
||||
"只检测到 Pi 配置文件,仍可编辑配置并接入 CPA;启动功能不可用".to_string(),
|
||||
);
|
||||
}
|
||||
let modification_state = if configured {
|
||||
"applied"
|
||||
@@ -476,6 +492,36 @@ pub(crate) fn install_pi_provider_inner(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn configure_pi_provider_without_cli_inner(
|
||||
home: &Path,
|
||||
port: u16,
|
||||
api_key: &str,
|
||||
default_model: &str,
|
||||
) -> Result<AgentConfigActionResult, String> {
|
||||
if port == 0 {
|
||||
return Err("内核端口无效".to_string());
|
||||
}
|
||||
if api_key.trim().is_empty() {
|
||||
return Err("EasyCLIProxyAPI 没有可用的 API key".to_string());
|
||||
}
|
||||
let settings_path = pi_provider_settings_path(home);
|
||||
config_package_operation(home, "plugin-config", || {
|
||||
let settings = if settings_path.is_file() {
|
||||
fs::read_to_string(&settings_path).map_err(|error| {
|
||||
format!(
|
||||
"读取 Pi settings.json 失败 {}: {error}",
|
||||
path_to_string(&settings_path)
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
"{}".to_string()
|
||||
};
|
||||
let rendered = build_pi_provider_settings(&settings, default_model)?;
|
||||
write_bytes_directly(&settings_path, rendered.as_bytes())
|
||||
})?;
|
||||
repair_pi_provider_inner(home, port, api_key, default_model)
|
||||
}
|
||||
|
||||
pub(crate) fn repair_pi_provider_inner(
|
||||
home: &Path,
|
||||
port: u16,
|
||||
@@ -1003,12 +1049,9 @@ pub(crate) fn inspect_agent_config(
|
||||
if !client.supported_platform() {
|
||||
warnings.push("当前平台不支持 Claude Desktop 3P 配置".to_string());
|
||||
} else if !installed && config_exists {
|
||||
warnings.push(if client == AgentClient::WorkBuddy {
|
||||
"只检测到 WorkBuddy 配置文件,仍可编辑配置并接入 CPA;启动功能不可用"
|
||||
.to_string()
|
||||
} else {
|
||||
"只检测到配置文件,未检测到客户端".to_string()
|
||||
});
|
||||
warnings.push(
|
||||
"只检测到配置文件,仍可编辑配置并接入 CPA;启动功能不可用".to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(message) = error.as_ref() {
|
||||
warnings.push(message.clone());
|
||||
|
||||
Reference in new issue
Block a user