guanghu/src-tauri/src/kiro_cli.rs
冰朔 8739805f99
Some checks failed
Auto-update PR branches / Update open PR branches (push) Has been cancelled
CI / Frontend Static Quality Checks (push) Has been cancelled
CI / Frontend Tests & Coverage (push) Has been cancelled
CI / Rust Tests & Quality Checks (push) Has been cancelled
CI / Linux build verification (push) Has been cancelled
Release (Alpha) / Compute alpha version (push) Has been cancelled
Release (Alpha) / Build release artifacts (push) Has been cancelled
Release (Alpha) / GitHub Release (alpha) (push) Has been cancelled
Release (Alpha) / Update docs and release pages (push) Has been cancelled
Deploy docs / Build VitePress site (push) Has been cancelled
Deploy docs / Deploy to GitHub Pages (push) Has been cancelled
光湖开源源码快照 · Tolaria AGPL 分叉基线 · 独立更新链
2026-07-05 17:45:16 +08:00

199 lines
6.4 KiB
Rust

use crate::ai_agents::{AiAgentAvailability, AiAgentStreamEvent};
use crate::cli_agent_runtime::{AgentStreamRequest, LineStreamProcess};
use std::path::Path;
use std::process::Stdio;
struct KiroMcpConfig<'a> {
vault_path: &'a str,
vault_paths: &'a [String],
mcp_server_path: &'a str,
}
pub fn check_cli() -> AiAgentAvailability {
crate::kiro_discovery::check_cli()
}
pub fn run_agent_stream<F>(request: AgentStreamRequest, emit: F) -> Result<String, String>
where
F: FnMut(AiAgentStreamEvent),
{
let binary = crate::kiro_discovery::find_binary()?;
ensure_mcp_config(&request)?;
let prompt =
crate::cli_agent_runtime::build_prompt(&request.message, request.system_prompt.as_deref());
let command = build_kiro_command(&binary, Path::new(&request.vault_path));
crate::cli_agent_runtime::run_ai_agent_line_stream(
LineStreamProcess::new(command, "kiro-cli", "kiro").with_stdin(prompt),
emit,
format_kiro_error,
)
}
fn build_kiro_command(binary: &Path, vault_path: &Path) -> std::process::Command {
let mut command = crate::hidden_command(binary);
crate::cli_agent_runtime::configure_agent_command_environment(&mut command, binary);
command
.arg("chat")
.arg("--no-interactive")
.arg("--trust-all-tools")
.current_dir(vault_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
command
}
fn ensure_mcp_config(request: &AgentStreamRequest) -> Result<(), String> {
let mcp_server_path = crate::cli_agent_runtime::mcp_server_path_string()?;
write_mcp_json(KiroMcpConfig {
vault_path: &request.vault_path,
vault_paths: &request.vault_paths,
mcp_server_path: &mcp_server_path,
})
}
fn write_mcp_json(config: KiroMcpConfig<'_>) -> Result<(), String> {
let config_dir = Path::new(config.vault_path).join(".kiro").join("settings");
std::fs::create_dir_all(&config_dir)
.map_err(|e| format!("Failed to create .kiro/settings: {e}"))?;
let config_path = config_dir.join("mcp.json");
let mut json_config: serde_json::Value = std::fs::read_to_string(&config_path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_else(|| serde_json::json!({}));
let servers = json_config
.as_object_mut()
.ok_or("Invalid mcp.json: not an object")?
.entry("mcpServers")
.or_insert_with(|| serde_json::json!({}));
let mut server = crate::cli_agent_runtime::tolaria_node_mcp_server(
config.mcp_server_path,
config.vault_path,
config.vault_paths,
true,
);
server["disabled"] = serde_json::json!(false);
servers["tolaria"] = server;
std::fs::write(
&config_path,
serde_json::to_string_pretty(&json_config)
.map_err(|e| format!("JSON serialize error: {e}"))?,
)
.map_err(|e| format!("Failed to write mcp.json: {e}"))?;
Ok(())
}
fn format_kiro_error(stderr_output: &str, status: &str) -> String {
if is_auth_error(stderr_output) {
return "Kiro CLI is not authenticated. Run `kiro-cli login` in your terminal to sign in."
.into();
}
if stderr_output.trim().is_empty() {
format!("kiro-cli exited with status {status}")
} else {
stderr_output.lines().take(3).collect::<Vec<_>>().join("\n")
}
}
fn is_auth_error(stderr_output: &str) -> bool {
let lower = stderr_output.to_ascii_lowercase();
["auth", "login", "token"]
.iter()
.any(|needle| lower.contains(needle))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strip_ansi_codes_removes_terminal_colors() {
assert_eq!(
crate::cli_agent_runtime::strip_ansi_codes("\x1b[38;5;141m> \x1b[0mHello! \x1b[2K"),
"> Hello! "
);
assert_eq!(
crate::cli_agent_runtime::strip_ansi_codes("plain text"),
"plain text"
);
}
#[test]
fn format_kiro_error_detects_auth_errors() {
let result = format_kiro_error("Error: auth token expired", "1");
assert!(result.contains("kiro-cli login"));
}
#[test]
fn format_kiro_error_returns_status_for_empty_stderr() {
let result = format_kiro_error("", "1");
assert!(result.contains("status 1"));
}
#[test]
fn write_mcp_json_creates_config() {
let dir = tempfile::tempdir().unwrap();
let vault_path = dir.path().to_str().unwrap();
write_mcp_json(KiroMcpConfig {
vault_path,
vault_paths: &["/other/vault".into(), vault_path.into()],
mcp_server_path: "/opt/mcp/index.js",
})
.unwrap();
let config_path = dir.path().join(".kiro/settings/mcp.json");
let content: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&config_path).unwrap()).unwrap();
assert_eq!(content["mcpServers"]["tolaria"]["command"], "node");
assert_eq!(
content["mcpServers"]["tolaria"]["args"][0],
"/opt/mcp/index.js"
);
assert_eq!(
content["mcpServers"]["tolaria"]["env"]["VAULT_PATH"],
vault_path
);
assert_eq!(
content["mcpServers"]["tolaria"]["env"]["VAULT_PATHS"],
serde_json::json!(serde_json::to_string(&vec![vault_path, "/other/vault"]).unwrap())
);
assert_eq!(
content["mcpServers"]["tolaria"]["env"]["WS_UI_PORT"],
"9711"
);
}
#[test]
fn write_mcp_json_merges_preserving_existing_servers() {
let dir = tempfile::tempdir().unwrap();
let vault_path = dir.path().to_str().unwrap();
let config_dir = dir.path().join(".kiro/settings");
std::fs::create_dir_all(&config_dir).unwrap();
std::fs::write(
config_dir.join("mcp.json"),
r#"{"mcpServers":{"other":{"command":"python","args":["server.py"]}}}"#,
)
.unwrap();
write_mcp_json(KiroMcpConfig {
vault_path,
vault_paths: &[],
mcp_server_path: "/new/index.js",
})
.unwrap();
let content: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(dir.path().join(".kiro/settings/mcp.json")).unwrap(),
)
.unwrap();
assert_eq!(content["mcpServers"]["tolaria"]["args"][0], "/new/index.js");
assert_eq!(content["mcpServers"]["other"]["command"], "python");
}
}