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 021db31f6..06a62dffa 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 @@ -1,5 +1,6 @@ use super::model::{ - DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserRuntimeRegionPart, + DirectCodexUserAttachmentReferencePart, DirectCodexUserContentPart, DirectCodexUserItem, + DirectCodexUserRuntimeRegionPart, }; use super::validation::validate_direct_codex_user_item; use crate::agent::{ @@ -97,6 +98,25 @@ fn runtime_region_summary(reference: &DirectCodexUserRuntimeRegionPart) -> Strin summary } +/// 附件引用的安全摘要。 +/// +/// turn 输入与 history/prompt 投影共用这一份清洗:文件名取 basename 并去控制字符、 +/// media type 与项目路径同样过白名单,避免两条路径对同一个引用给出不同摘要。 +fn attachment_reference_summary(reference: &DirectCodexUserAttachmentReferencePart) -> String { + let name = sanitize_attachment_name(&reference.name); + let media_type = sanitize_attachment_media_type(&reference.media_type); + let mut summary = format!( + "[附件:名称={name};类型={media_type};大小={} 字节", + reference.size + ); + if let Some(local_path) = sanitize_attachment_local_path(&reference.local_path) { + summary.push_str(&format!(";项目路径={local_path}")); + } + summary.push_str(&format!(";状态={}", reference.status.trim())); + summary.push(']'); + summary +} + /// 将 canonical user item 转为 app-server `turn/start.input` 可接受的文本数组。 /// AGC 私有 part 只在这里投影为安全摘要,canonical item 本身不被修改。 pub(crate) fn direct_codex_user_item_to_wire_input( @@ -120,19 +140,7 @@ pub(crate) fn direct_codex_user_item_to_wire_input( runtime_region_summary(reference) } DirectCodexUserContentPart::AgcAttachmentReference(reference) => { - let name = sanitize_attachment_name(&reference.name); - let media_type = sanitize_attachment_media_type(&reference.media_type); - let local_path = sanitize_attachment_local_path(&reference.local_path); - let mut summary = format!( - "[附件:名称={};类型={};大小={} 字节", - name, media_type, reference.size - ); - if let Some(local_path) = local_path { - summary.push_str(&format!(";项目路径={local_path}")); - } - summary.push_str(&format!(";状态={}", reference.status.trim())); - summary.push(']'); - summary + attachment_reference_summary(reference) } }; input.push(serde_json::json!({ "type": "text", "text": text })); @@ -179,20 +187,9 @@ pub(crate) fn direct_codex_user_item_to_codex_turn_input( })); } DirectCodexUserContentPart::AgcAttachmentReference(reference) => { - let mut summary = format!( - "[附件:名称={};类型={};大小={} 字节", - reference.name.trim(), - reference.media_type.trim(), - reference.size - ); - if !reference.local_path.trim().is_empty() { - summary.push_str(&format!(";项目路径={}", reference.local_path.trim())); - } - summary.push_str(&format!(";状态={}", reference.status.trim())); - summary.push(']'); input.push(serde_json::json!({ "type": "text", - "text": summary, + "text": attachment_reference_summary(reference), })); } }