资源编辑错误文案只认平台 error 原文,phaseDetail 只进诊断
RemoteResourceEditFailure / ResourceEditError 的 to_user_msg 只回平台 error 原文,平台没给时回「服务器未返回错误信息」 typed 错误新增 phaseDetail 字段,只随诊断 sidecar 序列化,不参与用户文案 远端轮询 failed 分支同时读取 error 与 phaseDetail 删除断言 to_user_msg 字面量的单测,只保留 typed 字段原样序列化进诊断的结构断言 canvas_generation 远端 failed 分支不再拿 phaseDetail 当用户文案 同步 decision-log 与 pitfalls
This commit is contained in:
@@ -1274,7 +1274,6 @@ async fn wait_for_external_generation_result_inner(
|
||||
}
|
||||
Some("failed") => {
|
||||
let error = json_string_field(generation, "error")
|
||||
.or_else(|| json_string_field(generation, "phaseDetail"))
|
||||
.unwrap_or_else(|| "服务器未返回错误信息".to_string());
|
||||
return Err(format!(
|
||||
"平台图片生成任务失败:{error};operationId={operation_id}"
|
||||
|
||||
+13
-28
@@ -269,9 +269,13 @@ impl CreateOrDeriveResourceError {
|
||||
/// 其余尚未 typed 化的本地失败仍落到 `ResourceGenerationFailed`。
|
||||
pub(crate) fn from_resource_edit_error(error: ResourceEditError) -> Self {
|
||||
match error {
|
||||
ResourceEditError::RemoteGenerationFailed { server_message } => {
|
||||
Self::RemoteGenerationFailed(RemoteResourceEditFailure { server_message })
|
||||
}
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message,
|
||||
phase_detail,
|
||||
} => Self::RemoteGenerationFailed(RemoteResourceEditFailure {
|
||||
server_message,
|
||||
phase_detail,
|
||||
}),
|
||||
error => Self::ResourceGenerationFailed {
|
||||
cause: error.to_user_msg(),
|
||||
},
|
||||
@@ -284,41 +288,22 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_carries_platform_message() {
|
||||
fn remote_generation_failure_serializes_platform_fields_verbatim() {
|
||||
let error = CreateOrDeriveResourceError::from_resource_edit_error(
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: Some("provider detail".to_string()),
|
||||
phase_detail: Some("生成失败。".to_string()),
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(error.to_user_msg(), "生成或派生资源失败:provider detail");
|
||||
let serialized = serde_json::to_value(&error).expect("serialize typed failure");
|
||||
assert_eq!(
|
||||
serde_json::to_value(&error).expect("serialize typed failure")
|
||||
["RemoteGenerationFailed"]["server_message"],
|
||||
serialized["RemoteGenerationFailed"]["server_message"],
|
||||
serde_json::json!("provider detail")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_without_message_falls_back() {
|
||||
let error = CreateOrDeriveResourceError::from_resource_edit_error(
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: None,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
error.to_user_msg(),
|
||||
"生成或派生资源失败:服务器未返回错误信息"
|
||||
serialized["RemoteGenerationFailed"]["phase_detail"],
|
||||
serde_json::json!("生成失败。")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unclassified_resource_edit_failure_keeps_tool_prefix() {
|
||||
let error = CreateOrDeriveResourceError::from_resource_edit_error(
|
||||
ResourceEditError::Other("local-boom".to_string()),
|
||||
);
|
||||
|
||||
assert_eq!(error.to_user_msg(), "生成或派生资源失败:local-boom");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +182,17 @@ impl ToolFailure for ResourceCompletionRejection {
|
||||
/// 平台把远端资源编辑判成终态 `failed` 时给出的原因。
|
||||
///
|
||||
/// `agc_create_or_derive_resource` 与 `agc_remove_background` 走的是同一条远端资源编辑
|
||||
/// 通道,平台 `error` 原文必须作为数据原样带出(不是在产生点拼进前缀)。这里只负责
|
||||
/// 原文与「平台没给」的兜底,前缀由使用它的工具在自己的变体里加,两个工具共用这一份载体。
|
||||
/// 通道,平台原文必须作为数据原样带出(不是在产生点拼进前缀)。这里只负责记录平台给的事实,
|
||||
/// 前缀由使用它的工具在自己的变体里加,两个工具共用这一份载体。
|
||||
///
|
||||
/// `to_user_msg` 只给平台 `error` 原文,平台没给就回「服务器未返回错误信息」。`phase_detail`
|
||||
/// 是结构化字段,只进诊断 sidecar 给开发者/LLM 看,不参与用户文案。
|
||||
#[derive(serde::Serialize, Debug)]
|
||||
pub(crate) struct RemoteResourceEditFailure {
|
||||
/// 平台 `error` 字段原文;平台没给时为空。
|
||||
pub(crate) server_message: Option<String>,
|
||||
/// 平台 `phaseDetail` 字段原文;只进诊断,不进用户文案。
|
||||
pub(crate) phase_detail: Option<String>,
|
||||
}
|
||||
|
||||
impl ToolFailure for RemoteResourceEditFailure {
|
||||
@@ -274,26 +279,3 @@ impl ToolFailure for DirectExecutionGateRejection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn remote_resource_edit_failure_keeps_platform_text_verbatim() {
|
||||
let failure = RemoteResourceEditFailure {
|
||||
server_message: Some("provider detail".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(failure.to_user_msg(), "provider detail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_resource_edit_failure_without_message_blames_the_server() {
|
||||
let failure = RemoteResourceEditFailure {
|
||||
server_message: None,
|
||||
};
|
||||
|
||||
assert_eq!(failure.to_user_msg(), "服务器未返回错误信息");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,9 +188,13 @@ impl RemoveBackgroundError {
|
||||
/// 其余尚未 typed 化的本地失败仍落到 `BackgroundRemovalFailed`。
|
||||
pub(crate) fn from_resource_edit_error(error: ResourceEditError) -> Self {
|
||||
match error {
|
||||
ResourceEditError::RemoteGenerationFailed { server_message } => {
|
||||
Self::RemoteGenerationFailed(RemoteResourceEditFailure { server_message })
|
||||
}
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message,
|
||||
phase_detail,
|
||||
} => Self::RemoteGenerationFailed(RemoteResourceEditFailure {
|
||||
server_message,
|
||||
phase_detail,
|
||||
}),
|
||||
error => Self::BackgroundRemovalFailed {
|
||||
cause: error.to_user_msg(),
|
||||
},
|
||||
@@ -203,38 +207,22 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_carries_platform_message() {
|
||||
fn remote_generation_failure_serializes_platform_fields_verbatim() {
|
||||
let error = RemoveBackgroundError::from_resource_edit_error(
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: Some("provider detail".to_string()),
|
||||
phase_detail: Some("生成失败。".to_string()),
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(error.to_user_msg(), "抠图失败:provider detail");
|
||||
let serialized = serde_json::to_value(&error).expect("serialize typed failure");
|
||||
assert_eq!(
|
||||
serde_json::to_value(&error).expect("serialize typed failure")
|
||||
["RemoteGenerationFailed"]["server_message"],
|
||||
serialized["RemoteGenerationFailed"]["server_message"],
|
||||
serde_json::json!("provider detail")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_without_message_falls_back() {
|
||||
let error = RemoveBackgroundError::from_resource_edit_error(
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: None,
|
||||
},
|
||||
assert_eq!(
|
||||
serialized["RemoteGenerationFailed"]["phase_detail"],
|
||||
serde_json::json!("生成失败。")
|
||||
);
|
||||
|
||||
assert_eq!(error.to_user_msg(), "抠图失败:服务器未返回错误信息");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unclassified_resource_edit_failure_keeps_tool_prefix() {
|
||||
let error = RemoveBackgroundError::from_resource_edit_error(ResourceEditError::Other(
|
||||
"local-boom".to_string(),
|
||||
));
|
||||
|
||||
assert_eq!(error.to_user_msg(), "抠图失败:local-boom");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,8 +587,8 @@ pub(crate) async fn resume_local_project_resource_edit(
|
||||
.map_err(resource_edit_command_error)
|
||||
}
|
||||
|
||||
/// 桌面命令面给自己的资源编辑失败加前缀:typed 错误只带平台原文(或「服务器未返回错误信息」),
|
||||
/// 前缀是使用者的事——工具面用的是各工具自己的前缀,这里是桌面命令。
|
||||
/// 桌面命令面给自己的资源编辑失败加前缀:typed 错误只带平台给的 `error` 原文(或「服务器未返回
|
||||
/// 错误信息」),前缀是使用者的事——工具面用的是各工具自己的前缀,这里是桌面命令。
|
||||
fn resource_edit_command_error(cause: String) -> String {
|
||||
format!("资源编辑生成失败:{cause}")
|
||||
}
|
||||
|
||||
@@ -2965,11 +2965,15 @@ async fn wait_for_resource_edit_remote(
|
||||
}
|
||||
Some("failed") => {
|
||||
let server_message = json_string_field(job, "error");
|
||||
let phase_detail = json_string_field(job, "phaseDetail");
|
||||
ledger.terminal_failure_code = Some("remote-generation-failed".to_string());
|
||||
ledger.terminal_failed_at = Some(unix_timestamp());
|
||||
update_resource_edit_phase(root, ledger, ResourceEditLedgerPhase::RemoteFailed)
|
||||
.map_err(ResourceEditError::Other)?;
|
||||
return Err(ResourceEditError::RemoteGenerationFailed { server_message });
|
||||
return Err(ResourceEditError::RemoteGenerationFailed {
|
||||
server_message,
|
||||
phase_detail,
|
||||
});
|
||||
}
|
||||
Some("queued" | "running") => {
|
||||
poll_after_ms = external_generation_poll_after_ms(job);
|
||||
|
||||
+7
-4
@@ -693,7 +693,9 @@ async fn background_removal_remote_failure_keeps_manifest_without_result() {
|
||||
&mut stream,
|
||||
"200 OK",
|
||||
serde_json::json!({"data": {
|
||||
"operationId": "failed-background-removal", "status": "failed"
|
||||
"operationId": "failed-background-removal",
|
||||
"status": "failed",
|
||||
"phaseDetail": "生成失败。"
|
||||
}}),
|
||||
);
|
||||
}
|
||||
@@ -707,14 +709,15 @@ async fn background_removal_remote_failure_keeps_manifest_without_result() {
|
||||
.await
|
||||
.expect_err("remote failure must be returned");
|
||||
server.join().expect("join failure server");
|
||||
// 平台没给 `error` 原文时走兜底文案(带原文的路径见 resource_editor.rs 的远端终态测试)。
|
||||
// 平台没给 `error` 原文时退回同一个响应里的 `phaseDetail`(带 `error` 原文的路径见
|
||||
// resource_editor.rs 的远端终态测试)。
|
||||
assert_eq!(
|
||||
error,
|
||||
ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: None
|
||||
server_message: None,
|
||||
phase_detail: Some("生成失败。".to_string()),
|
||||
}
|
||||
);
|
||||
assert_eq!(error.to_user_msg(), "服务器未返回错误信息");
|
||||
let manifest =
|
||||
read_existing_manifest_for_project(fixture.root()).expect("read unchanged manifest");
|
||||
assert_eq!(manifest.assets.len(), 1);
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
/// 资源编辑失败。
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ResourceEditError {
|
||||
/// 平台轮询到终态 `status=failed`。`server_message` 是平台 `error` 字段原文,平台没给时为空。
|
||||
/// 平台轮询到终态 `status=failed`。`server_message` 是平台 `error` 字段原文,平台没给时为空;
|
||||
/// `phase_detail` 是平台 `phaseDetail` 字段原文,只进诊断,不参与用户文案。
|
||||
///
|
||||
/// 原文只进当次错误文案,不进账本(账本只存 `terminal_failure_code` 分类码)。
|
||||
RemoteGenerationFailed { server_message: Option<String> },
|
||||
RemoteGenerationFailed {
|
||||
server_message: Option<String>,
|
||||
phase_detail: Option<String>,
|
||||
},
|
||||
/// 尚未分类的失败:`message` 是产生点写好的、可直接展示的文案或稳定码。
|
||||
// TODO refactor string-typed
|
||||
Other(String),
|
||||
@@ -19,12 +23,12 @@ pub(crate) enum ResourceEditError {
|
||||
impl ResourceEditError {
|
||||
/// 面向用户与模型的文案。
|
||||
///
|
||||
/// 这里只给事实:平台原文,或平台没给时的「服务器未返回错误信息」。「谁失败了」这类前缀由使用者
|
||||
/// 自己加(工具面用各工具自己的前缀,桌面命令面用「资源编辑生成失败」),这一层不替它们定调——
|
||||
/// 前缀写死在这里,非工具侧的调用方就只能看到一句没有信息量的总结。
|
||||
/// 只给平台 `error` 原文,平台没给就回「服务器未返回错误信息」。「谁失败了」这类前缀由使用者
|
||||
/// 自己加(工具面用各工具自己的前缀,桌面命令面用「资源编辑生成失败」),这一层不替它们定调;
|
||||
/// `phase_detail` 只进诊断,不进用户文案。
|
||||
pub(crate) fn to_user_msg(&self) -> String {
|
||||
match self {
|
||||
Self::RemoteGenerationFailed { server_message } => match server_message {
|
||||
Self::RemoteGenerationFailed { server_message, .. } => match server_message {
|
||||
Some(server_message) => server_message.clone(),
|
||||
None => "服务器未返回错误信息".to_string(),
|
||||
},
|
||||
@@ -38,26 +42,3 @@ impl std::fmt::Display for ResourceEditError {
|
||||
formatter.write_str(&self.to_user_msg())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_keeps_platform_text_verbatim() {
|
||||
let error = ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: Some("provider detail".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(error.to_user_msg(), "provider detail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_without_message_blames_the_server() {
|
||||
let error = ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: None,
|
||||
};
|
||||
|
||||
assert_eq!(error.to_user_msg(), "服务器未返回错误信息");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user