diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs index 25d1591e4..58422c12b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs @@ -1,6 +1,7 @@ use super::*; use base64::Engine as _; use platform_llm::LlmMessageRole; +use serde_json::Value; use sha2::{Digest, Sha256}; use std::collections::{HashMap, HashSet}; use std::process::Stdio; @@ -2840,8 +2841,22 @@ impl CodexAppServerConnection { codex_app_server_text_prompt(&request) .map_err(platform_llm::LlmError::InvalidRequest)? }; - let input = - codex_app_server_turn_input(&request, &prompt, &self.inner.workspace_path).await?; + let input = if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject { + if let Some(item) = direct_user_item { + let canonical: DirectCodexUserItem = serde_json::from_value(item.clone()) + .map_err(|error| platform_llm::LlmError::InvalidRequest(error.to_string()))?; + direct_codex_user_item_to_codex_turn_input( + &self.inner.workspace_path, + &canonical, + self.inner._skill_roots.as_deref().unwrap_or_default(), + ) + .map_err(platform_llm::LlmError::InvalidRequest)? + } else { + codex_app_server_turn_input(&request, &prompt, &self.inner.workspace_path).await? + } + } else { + codex_app_server_turn_input(&request, &prompt, &self.inner.workspace_path).await? + }; let _direct_tool_bridge_turn_guard = if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject { Some( diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs index 2488ae4ad..a5cb1344e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/mod.rs @@ -11,6 +11,6 @@ pub(crate) use model::{ }; pub(crate) use validation::validate_direct_codex_user_item; pub(crate) use wire::{ - direct_codex_user_item_to_prompt, direct_codex_user_item_to_response_item, - direct_codex_user_item_to_wire_input, + direct_codex_user_item_to_codex_turn_input, direct_codex_user_item_to_prompt, + direct_codex_user_item_to_response_item, direct_codex_user_item_to_wire_input, }; diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs index e9c958c75..0fdc0ad90 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs @@ -124,6 +124,76 @@ pub(crate) fn direct_codex_user_item_to_wire_input( Ok(Value::Array(input)) } +pub(crate) fn direct_codex_user_item_to_codex_turn_input( + root: &Path, + item: &DirectCodexUserItem, + skill_roots: &[std::path::PathBuf], +) -> Result { + validate_direct_codex_user_item(root, item)?; + let DirectCodexUserItem::Message(message) = item; + let mut input = Vec::with_capacity(message.content.len()); + for part in &message.content { + match part { + DirectCodexUserContentPart::InputText { text } => { + input.push(serde_json::json!({ "type": "text", "text": text })); + } + DirectCodexUserContentPart::AgcResourceReference { resource_id } => { + let asset = read_manifest_for_project(root)? + .assets + .into_iter() + .find(|asset| asset.id == resource_id.trim()) + .ok_or_else(|| "引用的素材已不存在,请移除后重新选择".to_string())?; + let path = sanitize_attachment_local_path(&asset.local_path) + .ok_or_else(|| "引用的素材路径无效,请移除后重新选择".to_string())?; + input.push(serde_json::json!({ + "type": "text", + "text": format!("[素材引用 resourceId={};项目路径={path}]", resource_id.trim()), + })); + } + DirectCodexUserContentPart::AgcSkillReference { name } => { + let name = name.trim(); + let path = skill_roots + .iter() + .map(|root| root.join(name).join("SKILL.md")) + .find(|path| path.is_file()) + .ok_or_else(|| "引用的 Skill 当前不可用,请重新选择".to_string())?; + input.push(serde_json::json!({ + "type": "skill", + "name": name, + "path": path, + })); + } + DirectCodexUserContentPart::AgcRuntimeRegionReference(reference) => { + let resources = reference + .resource_ids + .iter() + .map(|id| id.trim()) + .collect::>() + .join(","); + let mut summary = format!("[运行画面区域:名称={} ", reference.label.trim()); + if let Some(run_id) = reference.run_id.as_deref() { + summary.push_str(&format!("运行标识={} ", run_id.trim())); + } + if let Some(role) = reference.element_role.as_deref() { + summary.push_str(&format!("角色={} ", role.trim())); + } + if let Some(text) = reference.text.as_deref() { + summary.push_str(&format!("文本={} ", text.trim())); + } + if !resources.is_empty() { + summary.push_str(&format!("关联素材={resources}")); + } + summary.push(']'); + input.push(serde_json::json!({ + "type": "text", + "text": summary, + })); + } + } + } + Ok(Value::Array(input)) +} + pub(crate) fn direct_codex_user_item_to_prompt( root: &Path, item: &DirectCodexUserItem,