Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/cli.rs
T
AIGameCreator App a2bbeb79ee 补齐Agent聊天失败回执
Agent 聊天在用户消息已保存后将回复失败原因落盘为 assistant 消息

新增 --agent-chat 开发诊断入口验证单 Agent LLM 调用链

补充开发 Agent 聊天失败回执和 CLI 解析测试
2026-07-09 15:41:12 +08:00

201 lines
7.1 KiB
Rust

use super::*;
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum CliCommand {
LlmStatus,
AgentChat {
project_path: PathBuf,
agent_id: String,
prompt: String,
},
AgentRun {
project_path: PathBuf,
prompt: String,
wait_for_enter: bool,
},
}
pub(crate) fn game_creator_llm_status_lines(status: &GameCreatorLlmConfigStatus) -> Vec<String> {
let mut lines = vec![
format!("llm.configured={}", status.configured),
format!("llm.apiKeyPresent={}", status.api_key_present),
format!(
"llm.baseUrl={}",
status.base_url.as_deref().unwrap_or_default()
),
format!("llm.model={}", status.model.as_deref().unwrap_or_default()),
format!("llm.apiKind={}", status.api_kind),
format!("llm.stream={}", status.stream),
];
for agent in &status.agents {
lines.push(format!(
"llm.agent.{}.configured={}",
agent.agent_id, agent.configured
));
lines.push(format!(
"llm.agent.{}.apiKeyPresent={}",
agent.agent_id, agent.api_key_present
));
lines.push(format!(
"llm.agent.{}.baseUrl={}",
agent.agent_id,
agent.base_url.as_deref().unwrap_or_default()
));
lines.push(format!(
"llm.agent.{}.model={}",
agent.agent_id,
agent.model.as_deref().unwrap_or_default()
));
lines.push(format!(
"llm.agent.{}.apiKind={}",
agent.agent_id, agent.api_kind
));
lines.push(format!(
"llm.agent.{}.stream={}",
agent.agent_id, agent.stream
));
if let Some(error) = agent.error.as_deref() {
lines.push(format!("llm.agent.{}.error={error}", agent.agent_id));
}
}
if let Some(error) = status.error.as_deref() {
lines.push(format!("llm.error={error}"));
}
lines
}
pub(crate) fn parse_cli_command(args: &[String]) -> Result<Option<CliCommand>, String> {
if args.first().map(String::as_str) == Some("--llm-status") {
return Ok(Some(CliCommand::LlmStatus));
}
if args.first().map(String::as_str) == Some("--agent-chat") {
let project_path = args.get(1).map(String::as_str).ok_or_else(|| {
"用法:--agent-chat <本地项目绝对路径> <agentId> <聊天内容>".to_string()
})?;
let agent_id = args.get(2).map(String::as_str).ok_or_else(|| {
"用法:--agent-chat <本地项目绝对路径> <agentId> <聊天内容>".to_string()
})?;
if args.len() < 4 {
return Err("用法:--agent-chat <本地项目绝对路径> <agentId> <聊天内容>".to_string());
}
let prompt = args[3..].join(" ");
let prompt = prompt.trim();
if prompt.is_empty() {
return Err("聊天内容不能为空".to_string());
}
return Ok(Some(CliCommand::AgentChat {
project_path: PathBuf::from(project_path),
agent_id: agent_id.trim().to_string(),
prompt: prompt.to_string(),
}));
}
if args.first().map(String::as_str) != Some("--agent-run") {
return Ok(None);
}
let mut rest = args[1..].to_vec();
let wait_for_enter = if let Some(index) = rest.iter().position(|arg| arg == "--no-wait") {
rest.remove(index);
false
} else {
true
};
let project_path = rest
.first()
.map(String::as_str)
.ok_or_else(|| "用法:--agent-run [--no-wait] <本地项目绝对路径> <创作需求>".to_string())?;
if rest.len() < 2 {
return Err("用法:--agent-run [--no-wait] <本地项目绝对路径> <创作需求>".to_string());
}
let prompt = rest[1..].join(" ");
let prompt = prompt.trim();
if prompt.is_empty() {
return Err("创作需求不能为空".to_string());
}
Ok(Some(CliCommand::AgentRun {
project_path: PathBuf::from(project_path),
prompt: prompt.to_string(),
wait_for_enter,
}))
}
pub(crate) fn run_cli_command(command: CliCommand) -> Result<(), String> {
match command {
CliCommand::LlmStatus => {
let status = check_game_creator_llm_config_from_config();
for line in game_creator_llm_status_lines(&status) {
println!("{line}");
}
if status.configured {
Ok(())
} else {
Err("LLM 配置未就绪".to_string())
}
}
CliCommand::AgentChat {
project_path,
agent_id,
prompt,
} => {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.map_err(|error| format!("创建 CLI runtime 失败:{error}"))?;
let reply = runtime.block_on(chat_with_game_creator_role_agent_at(
&project_path,
&agent_id,
&prompt,
))?;
println!("agent.chat.completed");
println!("projectPath={}", project_path.display());
println!("agentId={agent_id}");
println!("replyText={}", reply.reply_text);
Ok(())
}
CliCommand::AgentRun {
project_path,
prompt,
wait_for_enter,
} => {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.map_err(|error| format!("创建 CLI runtime 失败:{error}"))?;
let result =
runtime.block_on(generate_local_game_draft_at(&project_path, &prompt, None))?;
let (preview, stop) = start_local_game_preview_for_project(&project_path)?;
record_preview_state(
&project_path,
GameCreationAppPreviewStatus::Running,
Some(preview.url.clone()),
Some(preview.port),
)?;
append_preview_log(&project_path, "running", Some(&preview.url))?;
append_preview_start_trace_step(&project_path, &preview)?;
println!("agent.run.completed");
println!("projectPath={}", result.project_path);
println!("gameIndexPath={}", result.game_index_path);
println!("designPath={}", result.design_path);
println!(
"tracePath={}",
project_path.join(".agent/run.latest.json").display()
);
println!("previewUrl={}", preview.url);
if wait_for_enter {
println!("按 Enter 停止本地预览。");
let mut line = String::new();
let _ = std::io::stdin().read_line(&mut line);
}
let _ = stop.send(());
let _ = record_preview_state(
&project_path,
GameCreationAppPreviewStatus::Stopped,
None,
None,
);
let _ = append_preview_log(&project_path, "stopped", None);
let _ = append_preview_stop_trace_step(&project_path);
Ok(())
}
}
}