diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs index 8de0f9440..12bab6797 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs @@ -4,6 +4,7 @@ use super::model::{ }; use crate::agent::{ read_manifest_for_project, sanitize_attachment_local_path, GameCreationAppManifest, + MAX_DIRECT_CODEX_ATTACHMENTS, }; use std::path::Path; @@ -27,6 +28,7 @@ pub(crate) fn validate_direct_codex_user_item( } let manifest = read_manifest_for_project(root)?; let mut reference_count = 0usize; + let mut attachment_count = 0usize; for part in &message.content { match part { DirectCodexUserContentPart::InputText { .. } => {} @@ -39,6 +41,12 @@ pub(crate) fn validate_direct_codex_user_item( validate_runtime_region_reference(&manifest, reference)?; } DirectCodexUserContentPart::AgcAttachmentReference(reference) => { + attachment_count = attachment_count.saturating_add(1); + if attachment_count > MAX_DIRECT_CODEX_ATTACHMENTS { + return Err(format!( + "一次最多携带 {MAX_DIRECT_CODEX_ATTACHMENTS} 个附件" + )); + } if reference.name.trim().is_empty() { return Err("附件缺少文件名".to_string()); } @@ -107,8 +115,9 @@ fn validate_runtime_region_reference( #[cfg(test)] mod tests { - use super::content_has_meaningful_input; + use super::{content_has_meaningful_input, validate_direct_codex_user_item}; use crate::agent::direct_codex_user_item::model::DirectCodexUserContentPart; + use serde_json::json; fn input_text(text: &str) -> DirectCodexUserContentPart { DirectCodexUserContentPart::InputText { @@ -148,4 +157,33 @@ mod tests { }, ])); } + + #[test] + fn inline_attachment_count_is_bounded_independently() { + let root = tempfile::tempdir().expect("temp project"); + crate::init_local_game_project_at(root.path(), "validation-test", "校验测试") + .expect("init project"); + let content = (0..=crate::agent::MAX_DIRECT_CODEX_ATTACHMENTS) + .map(|index| { + json!({ + "type": "agc_attachment_reference", + "name": format!("attachment-{index}.txt"), + "mediaType": "text/plain", + "size": 1, + "localPath": "", + "status": "failed" + }) + }) + .collect::>(); + let item = serde_json::from_value(json!({ + "type": "message", + "role": "user", + "content": content, + "id": "turn-1:user" + })) + .expect("deserialize user item"); + let error = validate_direct_codex_user_item(root.path(), &item) + .expect_err("too many inline attachments must be rejected"); + assert!(error.contains("最多携带"), "{error}"); + } }