给 Skill 引用补上每条消息的数量上限

- apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs 新增 MAX_DIRECT_CODEX_SKILL_REFERENCES=32 与 skill_count 计数,超出即拒绝
- 附件与素材引用已有上限,Skill 引用是唯一没有上限的非文本 part:每条都会触发一次 root/<name>/SKILL.md 文件探测并往 turn/start.input 里加一项,畸形超长 userItem 可以放大文件探测与模型输入
- 补上 inline_skill_reference_count_is_bounded_independently;cargo test direct_codex_user_item::validation:7 passed
This commit is contained in:
2026-09-19 23:35:41 +08:00
parent c5a7386465
commit dc1cac1c15
@@ -10,6 +10,9 @@ use crate::agent::{
use std::path::Path;
pub(crate) const MAX_DIRECT_CODEX_REFERENCES: usize = 32;
// Skill 引用也是非文本 part,但没有走 reference_count:它每一条都会触发一次
// `root/<name>/SKILL.md` 文件探测并往 turn input 里加一项,所以单独设上限。
pub(crate) const MAX_DIRECT_CODEX_SKILL_REFERENCES: usize = 32;
pub(crate) fn validate_direct_codex_user_item(
root: &Path,
@@ -30,6 +33,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;
let mut skill_count = 0usize;
for part in &message.content {
match part {
DirectCodexUserContentPart::InputText { .. } => {}
@@ -38,6 +42,12 @@ pub(crate) fn validate_direct_codex_user_item(
validate_resource_id_and_manifest(&manifest, resource_id)?;
}
DirectCodexUserContentPart::AgcSkillReference { name } => {
skill_count = skill_count.saturating_add(1);
if skill_count > MAX_DIRECT_CODEX_SKILL_REFERENCES {
return Err(format!(
"一次最多引用 {MAX_DIRECT_CODEX_SKILL_REFERENCES} 个 Skill"
));
}
let name = name.trim();
if name.is_empty()
|| name.chars().count() > 120
@@ -150,7 +160,10 @@ fn validate_runtime_region_reference(
#[cfg(test)]
mod tests {
use super::{content_has_meaningful_input, validate_direct_codex_user_item};
use super::{
content_has_meaningful_input, validate_direct_codex_user_item,
MAX_DIRECT_CODEX_SKILL_REFERENCES,
};
use crate::agent::direct_codex_user_item::model::DirectCodexUserContentPart;
use serde_json::json;
@@ -246,6 +259,26 @@ mod tests {
assert!(error.contains("缺少项目路径"), "{error}");
}
#[test]
fn inline_skill_reference_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..=MAX_DIRECT_CODEX_SKILL_REFERENCES)
.map(|index| json!({ "type": "agc_skill_reference", "name": format!("skill-{index}") }))
.collect::<Vec<_>>();
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 skill references must be rejected");
assert!(error.contains("最多引用"), "{error}");
}
#[test]
fn attachment_name_and_media_type_are_bounded_and_well_formed() {
let root = tempfile::tempdir().expect("temp project");