From 0e6cd8aedf109cc7364d863391bd2e956f2ecb7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 15 Sep 2026 17:41:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E9=80=9ASkill=E6=8F=90=E5=8F=8ACodex?= =?UTF-8?q?=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 canonical Skill part 按原顺序转换为 type skill 输入项 沿用客户端隔离 Skill root 解析受控路径 --- .../src/agent/codex_app_server/mod.rs | 19 ++++- .../src/agent/direct_codex_user_item/mod.rs | 4 +- .../src/agent/direct_codex_user_item/wire.rs | 70 +++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) 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,