ec3a187dd7
Project CI / AI game creator shell Rust crates (push) Successful in 1m11s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m34s
Project CI / Backend tests (push) Successful in 4m46s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m31s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m41s
Project CI / Frontend tests (push) Successful in 2m19s
Project CI / Native shell tests (push) Successful in 7m5s
Project CI / AI game creator shell web tests (push) Successful in 1m54s
Project CI / Repository checks (push) Successful in 2m36s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/503 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
163 lines
5.1 KiB
Rust
163 lines
5.1 KiB
Rust
mod content_validation;
|
||
mod discovery;
|
||
mod identity_order_validation;
|
||
mod ledger;
|
||
mod model;
|
||
mod storage_common;
|
||
#[cfg(unix)]
|
||
mod storage_unix;
|
||
#[cfg(windows)]
|
||
mod storage_windows;
|
||
#[cfg(test)]
|
||
mod tests;
|
||
mod thinking;
|
||
|
||
const TOOL_PLAN_HANDOFF_SAFE_DIAGNOSTIC_MARKER: &str = ";safeDiagnostic=";
|
||
|
||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq)]
|
||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||
pub(crate) struct AgentRuntimeToolPlanHandoffSafeDiagnostic {
|
||
pub(crate) function_class: String,
|
||
pub(crate) json_pointer: String,
|
||
pub(crate) path_shape: String,
|
||
pub(crate) relation_to_root: String,
|
||
pub(crate) duplicate_safe_json: bool,
|
||
pub(crate) hit_count: usize,
|
||
}
|
||
|
||
#[cfg(any(unix, windows))]
|
||
pub(crate) use discovery::list_at;
|
||
pub(crate) use ledger::{
|
||
ensure_capacity_for_request_at, is_later_repair_identity, lookup_at, read_for_run_at,
|
||
remove_at, write_at,
|
||
};
|
||
pub(crate) use model::{AgentRuntimeToolPlanHandoffEntry, AgentRuntimeToolPlanHandoffLookup};
|
||
#[cfg(test)]
|
||
pub(crate) use model::{AgentRuntimeToolPlanHandoffLedger, TOOL_PLAN_HANDOFF_SCHEMA_VERSION};
|
||
|
||
pub(crate) fn absolute_path_validation_error(
|
||
label: &str,
|
||
diagnostic: &AgentRuntimeToolPlanHandoffSafeDiagnostic,
|
||
) -> String {
|
||
let diagnostic = serde_json::to_string(diagnostic)
|
||
.unwrap_or_else(|_| "{\"diagnostic\":\"unavailable\"}".to_string());
|
||
format!(
|
||
"tool-plan 成功响应交接 {label} 的结构化输入包含绝对路径{TOOL_PLAN_HANDOFF_SAFE_DIAGNOSTIC_MARKER}{diagnostic}"
|
||
)
|
||
}
|
||
|
||
pub(crate) fn safe_failure_diagnostic(
|
||
error: &str,
|
||
) -> Option<AgentRuntimeToolPlanHandoffSafeDiagnostic> {
|
||
let (_, diagnostic) = error.split_once(TOOL_PLAN_HANDOFF_SAFE_DIAGNOSTIC_MARKER)?;
|
||
serde_json::from_str(diagnostic)
|
||
.ok()
|
||
.filter(valid_safe_failure_diagnostic)
|
||
}
|
||
|
||
fn valid_safe_failure_diagnostic(diagnostic: &AgentRuntimeToolPlanHandoffSafeDiagnostic) -> bool {
|
||
let function_class_valid = matches!(
|
||
diagnostic.function_class.as_str(),
|
||
"legacy-tool-plan"
|
||
| "other"
|
||
| "native:project.search"
|
||
| "native:file.list"
|
||
| "native:file.read"
|
||
| "native:file.write"
|
||
| "native:file.patch"
|
||
| "native:file.delete"
|
||
| "native:project.patchset"
|
||
| "native:project.git_commit"
|
||
| "native:command.exec"
|
||
| "native:command.start"
|
||
| "native:image.inspect"
|
||
| "native:canvas.asset_generate"
|
||
);
|
||
function_class_valid
|
||
&& valid_safe_json_pointer(&diagnostic.json_pointer)
|
||
&& matches!(
|
||
diagnostic.path_shape.as_str(),
|
||
"exact-absolute" | "exact-platform-absolute" | "file-uri" | "embedded-absolute"
|
||
)
|
||
&& matches!(
|
||
diagnostic.relation_to_root.as_str(),
|
||
"project-root" | "project-internal" | "external-or-unresolved" | "not-applicable"
|
||
)
|
||
&& (1..=4096).contains(&diagnostic.hit_count)
|
||
}
|
||
|
||
fn valid_safe_json_pointer(pointer: &str) -> bool {
|
||
if pointer == "#" {
|
||
return true;
|
||
}
|
||
if pointer.len() > 192 || !pointer.starts_with("#/") || !pointer.is_ascii() {
|
||
return false;
|
||
}
|
||
pointer[2..].split('/').all(|segment| {
|
||
[
|
||
"input",
|
||
"program",
|
||
"args",
|
||
"cwd",
|
||
"path",
|
||
"paths",
|
||
"changes",
|
||
"outputPath",
|
||
"output_path",
|
||
"expectedArtifacts",
|
||
"expected_artifacts",
|
||
"writeScopes",
|
||
"write_scopes",
|
||
"artifacts",
|
||
"children",
|
||
"scope",
|
||
"field",
|
||
"truncated",
|
||
]
|
||
.contains(&segment)
|
||
|| (segment.len() <= 4
|
||
&& !segment.is_empty()
|
||
&& segment.bytes().all(|byte| byte.is_ascii_digit()))
|
||
})
|
||
}
|
||
|
||
pub(crate) fn failure_kind(error: &str) -> &'static str {
|
||
if error.contains("敏感规则") || error.contains("敏感 JSON") {
|
||
"tool-plan-sensitive-content"
|
||
} else if error.contains("绝对路径") {
|
||
"tool-plan-absolute-path"
|
||
} else if error.contains("thinking") {
|
||
"tool-plan-thinking-normalization"
|
||
} else if error.contains("超过")
|
||
|| error.contains("不能为空")
|
||
|| error.contains("无效")
|
||
|| error.contains("不支持")
|
||
{
|
||
"tool-plan-response-shape"
|
||
} else if error.contains("slot")
|
||
|| error.contains("loop")
|
||
|| error.contains("repair")
|
||
|| error.contains("identity")
|
||
|| error.contains("顺序")
|
||
{
|
||
"tool-plan-identity-order"
|
||
} else if error.contains("写入")
|
||
|| error.contains("读取")
|
||
|| error.contains("创建")
|
||
|| error.contains("安装")
|
||
|| error.contains("目录")
|
||
|| error.contains("文件")
|
||
|| error.contains("权限")
|
||
{
|
||
"tool-plan-storage"
|
||
} else if error.contains("冲突")
|
||
|| error.contains("不匹配")
|
||
|| error.contains("不存在")
|
||
|| error.contains("损坏")
|
||
{
|
||
"tool-plan-integrity"
|
||
} else {
|
||
"tool-plan-unknown"
|
||
}
|
||
}
|