prepare info display to user for review

This commit is contained in:
2026-07-10 20:46:27 +08:00
parent ffde522b1e
commit 88368cd900
10 changed files with 751 additions and 129 deletions
File diff suppressed because it is too large Load Diff
@@ -27,12 +27,10 @@ impl EditorToolContext {
///
/// 通用 `Tool` 仍只负责参数校验;价格依赖 api-server 的运行时配置,不能下沉到
/// `module-editor-agent`。实际执行和扣费仍由既有生成 BFF 负责。
#[allow(dead_code)]
pub(crate) trait EditorAgentPricedTool: Tool {
fn pricing(&self, pricing: &EditorGenerationPricingConfig, args: &<Self as Tool>::Args) -> u32;
}
#[allow(dead_code)]
pub(crate) fn editor_agent_image_mud_points(
pricing: &EditorGenerationPricingConfig,
kind: Option<&str>,
@@ -24,11 +24,8 @@ pub struct GenerateVideoTool {
pub context: crate::editor_agent::editor_tools::common::EditorToolContext,
}
#[allow(dead_code)]
const DEFAULT_VIDEO_MODEL: &str = "seedance2.0-fast";
#[allow(dead_code)]
const DEFAULT_VIDEO_RESOLUTION: &str = "720p";
#[allow(dead_code)]
const DEFAULT_VIDEO_DURATION_SECONDS: u32 = 4;
#[derive(Debug, Clone)]
@@ -99,14 +99,60 @@ pub struct EditorAgentGeneratedAudio {
pub audio_src: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EditorAgentToolCallDisplayArgs {
pub string_args: Vec<EditorAgentToolCallStringArg>,
pub image_args: Vec<EditorAgentToolCallImageArg>,
pub extras: EditorAgentToolCallDisplayExtras,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EditorAgentToolCallDisplayExtras {
pub price_mud_points: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EditorAgentToolCallStringArg {
pub name: String,
pub label: String,
pub value: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EditorAgentToolCallImageArg {
pub name: String,
pub label: String,
pub refs: Vec<EditorAgentToolCallImageRef>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EditorAgentToolCallImageRef {
pub image_id: String,
pub image_src: String,
#[serde(default)]
pub object_key: Option<String>,
#[serde(default)]
pub thumbnail_src: Option<String>,
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub width: Option<u32>,
#[serde(default)]
pub height: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct EditorAgentToolCall {
pub tool_name: String,
#[serde(default)]
pub summary: String,
pub status: EditorAgentToolCallStatus,
pub args: serde_json::Value,
pub display_args: EditorAgentToolCallDisplayArgs,
#[serde(default)]
pub images: Vec<EditorAgentGeneratedImage>,
#[serde(default)]
@@ -195,46 +241,79 @@ pub struct EditorAgentMessageResponse {
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use super::*;
use serde_json::json;
#[test]
fn tool_call_media_results_use_camel_case_and_default_for_existing_documents() {
let legacy: EditorAgentToolCall = serde_json::from_value(json!({
#[test]
fn tool_call_display_args_and_media_use_camel_case() {
let missing_display_args = serde_json::from_value::<EditorAgentToolCall>(json!({
"toolName": "generate-video",
"status": "completed",
"args": {},
"images": []
}))
.expect("existing message documents should remain readable");
assert!(legacy.videos.is_empty());
assert!(legacy.audios.is_empty());
}));
assert!(missing_display_args.is_err());
let tool_call = EditorAgentToolCall {
tool_name: "generate-video".to_string(),
summary: String::new(),
status: EditorAgentToolCallStatus::Completed,
args: json!({}),
images: Vec::new(),
videos: vec![EditorAgentGeneratedVideo {
resource_id: Some("resource-video-1".to_string()),
object_key: Some("generated/video.mp4".to_string()),
asset_object_id: Some("asset-video-1".to_string()),
video_src: "/generated-video.mp4".to_string(),
thumbnail_src: Some("/generated-video.png".to_string()),
width: Some(1280),
height: Some(720),
}],
audios: vec![EditorAgentGeneratedAudio {
resource_id: Some("resource-audio-1".to_string()),
object_key: Some("generated/audio.mp3".to_string()),
asset_object_id: Some("asset-audio-1".to_string()),
audio_src: "/generated-audio.mp3".to_string(),
}],
error: None,
};
let payload = serde_json::to_value(tool_call).expect("tool call should serialize");
assert_eq!(payload["videos"][0]["videoSrc"], "/generated-video.mp4");
assert_eq!(payload["audios"][0]["audioSrc"], "/generated-audio.mp3");
}
let tool_call = EditorAgentToolCall {
tool_name: "generate-video".to_string(),
status: EditorAgentToolCallStatus::Completed,
args: json!({}),
display_args: EditorAgentToolCallDisplayArgs {
string_args: vec![EditorAgentToolCallStringArg {
name: "prompt".to_string(),
label: "提示词".to_string(),
value: "生成像素风角色".to_string(),
}],
image_args: vec![EditorAgentToolCallImageArg {
name: "reference_image_ids".to_string(),
label: "参考图片".to_string(),
refs: vec![EditorAgentToolCallImageRef {
image_id: "sha256:reference-1".to_string(),
image_src: "/reference.png".to_string(),
object_key: Some("generated/reference.png".to_string()),
thumbnail_src: Some("/reference-thumbnail.png".to_string()),
label: Some("角色参考".to_string()),
width: Some(512),
height: Some(512),
}],
}],
extras: EditorAgentToolCallDisplayExtras {
price_mud_points: 5,
},
},
images: Vec::new(),
videos: vec![EditorAgentGeneratedVideo {
resource_id: Some("resource-video-1".to_string()),
object_key: Some("generated/video.mp4".to_string()),
asset_object_id: Some("asset-video-1".to_string()),
video_src: "/generated-video.mp4".to_string(),
thumbnail_src: Some("/generated-video.png".to_string()),
width: Some(1280),
height: Some(720),
}],
audios: vec![EditorAgentGeneratedAudio {
resource_id: Some("resource-audio-1".to_string()),
object_key: Some("generated/audio.mp3".to_string()),
asset_object_id: Some("asset-audio-1".to_string()),
audio_src: "/generated-audio.mp3".to_string(),
}],
error: None,
};
let payload = serde_json::to_value(tool_call).expect("tool call should serialize");
assert_eq!(payload["videos"][0]["videoSrc"], "/generated-video.mp4");
assert_eq!(payload["audios"][0]["audioSrc"], "/generated-audio.mp3");
assert_eq!(
payload["displayArgs"]["stringArgs"][0]["value"],
"生成像素风角色"
);
assert_eq!(
payload["displayArgs"]["imageArgs"][0]["refs"][0]["imageId"],
"sha256:reference-1"
);
assert_eq!(
payload["displayArgs"]["imageArgs"][0]["refs"][0]["objectKey"],
"generated/reference.png"
);
assert_eq!(payload["displayArgs"]["extras"]["priceMudPoints"], 5);
}
}