diff --git a/server-rs/crates/api-server/src/editor_agent/api.rs b/server-rs/crates/api-server/src/editor_agent/api.rs index 99791362a..48443bc84 100644 --- a/server-rs/crates/api-server/src/editor_agent/api.rs +++ b/server-rs/crates/api-server/src/editor_agent/api.rs @@ -15,11 +15,11 @@ use serde::Serialize; use serde_json::{Value, json}; use module_editor_agent::agent::tool::Tool; use shared_contracts::editor_agent::{ - CreateEditorAgentConversationRequest, EditorAgentConversationListResponse, - EditorAgentConversationMessagesDocument, EditorAgentConversationResponse, - EditorAgentConversationSummary, EditorAgentGeneratedImage, EditorAgentMessage, - EditorAgentMessageRole, EditorAgentToolCall, EditorAgentToolCallStatus, - StreamEditorAgentMessageRequest, + CreateEditorAgentConversationRequest, EditorAgentConversationListResponse, + EditorAgentConversationMessagesDocument, EditorAgentConversationResponse, + EditorAgentConversationSummary, EditorAgentGeneratedImage, EditorAgentMessage, + EditorAgentMessageRole, EditorAgentToolCall, EditorAgentToolCallStatus, + EditorAgentMessageRequest, }; use spacetime_client::{ EditorAgentConversationCreateRecordInput, EditorAgentConversationDeleteRecordInput, @@ -45,11 +45,11 @@ use crate::state::AppState; use shared_kernel::{build_prefixed_uuid_id, normalize_optional_string}; pub async fn editor_agent_message( - State(state): State, - Path(conversation_id): Path, - Extension(_request_context): Extension, - Extension(authenticated): Extension, - Json(payload): Json, + State(state): State, + Path(conversation_id): Path, + Extension(_request_context): Extension, + Extension(authenticated): Extension, + Json(payload): Json, ) -> Result, AppError> { let owner_user_id = authenticated.claims().user_id().to_string(); require_editor_agent_sidebar_enabled(&state, owner_user_id.as_str()).await?; diff --git a/server-rs/crates/shared-contracts/src/editor_agent.rs b/server-rs/crates/shared-contracts/src/editor_agent.rs index 295d41de5..87c55d818 100644 --- a/server-rs/crates/shared-contracts/src/editor_agent.rs +++ b/server-rs/crates/shared-contracts/src/editor_agent.rs @@ -8,17 +8,6 @@ pub const EDITOR_AGENT_TITLE_MAX_CHARS: usize = 20; pub const EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE: &str = "新对话"; pub const EDITOR_AGENT_MESSAGES_DOCUMENT_VERSION: u32 = 1; -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum EditorAgentStage { - Idle, - Thinking, - Responding, - Generating, - Completed, - Failed, -} - #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum EditorAgentMessageRole { @@ -27,23 +16,6 @@ pub enum EditorAgentMessageRole { System } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum EditorAgentMessageKind { - Chat, - Stage, - Error, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum EditorAgentMessageStatus { - Streaming, - Generating, - Completed, - Failed, - Stopped, -} #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] @@ -52,16 +24,6 @@ pub enum EditorAgentAttachmentSource { LibraryAsset, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum EditorAgentToolName { - GenerateImage, - EditImage, - GenerateCharacter, - GenerateIconSpritesheet, - GenerateUiDesign, -} - #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum EditorAgentToolCallStatus { @@ -183,209 +145,8 @@ pub struct EditorAgentConversationResponse { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct StreamEditorAgentMessageRequest { - pub client_message_id: String, +pub struct EditorAgentMessageRequest { pub text: String, #[serde(default)] pub attachments: Vec, } - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum EditorAgentSseEventType { - Stage, - MessageDelta, - ToolStarted, - ToolCompleted, - GenerationResult, - Error, - Done, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentSseEnvelope { - pub event: EditorAgentSseEventType, - pub data: serde_json::Value, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentStageEvent { - pub conversation_id: String, - pub stage: EditorAgentStage, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentMessageDeltaEvent { - pub conversation_id: String, - pub message_id: String, - pub role: EditorAgentMessageRole, - pub kind: EditorAgentMessageKind, - pub text_delta: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentToolEvent { - pub conversation_id: String, - pub message_id: String, - pub tool_call_id: String, - pub tool_name: EditorAgentToolName, - #[serde(default)] - pub summary: Option, - #[serde(default)] - pub task_id: Option, - #[serde(default)] - pub model: Option, - #[serde(default)] - pub status: Option, - #[serde(default)] - pub error: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentGenerationResultEvent { - pub conversation_id: String, - pub message_id: String, - pub tool_call_id: String, - pub tool_name: EditorAgentToolName, - #[serde(default)] - pub model: Option, - pub images: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentErrorEvent { - #[serde(default)] - pub conversation_id: Option, - pub code: String, - pub message: String, - pub recoverable: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct EditorAgentDoneEvent { - pub conversation_id: String, - #[serde(default)] - pub title: Option, -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::json; - - #[test] - fn editor_agent_conversation_detail_uses_camel_case() { - let detail = EditorAgentConversationDetail { - conversation_id: "editor-agent-conv-1".to_string(), - project_id: "project-1".to_string(), - title: "新对话".to_string(), - created_at: "2026-07-03T00:00:00Z".to_string(), - updated_at: "2026-07-03T00:00:01Z".to_string(), - messages: vec![EditorAgentMessage { - id: "message-1".to_string(), - role: EditorAgentMessageRole::User, - kind: EditorAgentMessageKind::Chat, - text: "帮我生成一张森林背景".to_string(), - attachments: vec![EditorAgentAttachmentRef { - source: EditorAgentAttachmentSource::CanvasResource, - reference_id: "resource-1".to_string(), - object_key: Some("editor/resource-1.png".to_string()), - image_src: "https://example.test/resource-1.png".to_string(), - thumbnail_src: None, - label: Some("森林参考".to_string()), - width: Some(1024), - height: Some(768), - }], - tool_calls: vec![], - status: EditorAgentMessageStatus::Completed, - created_at: "2026-07-03T00:00:00Z".to_string(), - }], - }; - - let payload = serde_json::to_value(&detail).expect("detail should serialize"); - assert_eq!(payload["conversationId"], json!("editor-agent-conv-1")); - assert_eq!(payload["messages"][0]["role"], json!("user")); - assert_eq!( - payload["messages"][0]["attachments"][0]["source"], - json!("canvas_resource") - ); - assert_eq!( - payload["messages"][0]["attachments"][0]["referenceId"], - json!("resource-1") - ); - assert_eq!(payload["messages"][0]["status"], json!("completed")); - - let decoded: EditorAgentConversationDetail = - serde_json::from_value(payload).expect("detail should deserialize"); - assert_eq!(decoded, detail); - } - - #[test] - fn editor_agent_sse_events_serialize_event_names() { - let envelope = EditorAgentSseEnvelope { - event: EditorAgentSseEventType::GenerationResult, - data: serde_json::to_value(EditorAgentGenerationResultEvent { - conversation_id: "editor-agent-conv-1".to_string(), - message_id: "message-2".to_string(), - tool_call_id: "tool-call-1".to_string(), - tool_name: EditorAgentToolName::GenerateImage, - model: Some("z-image".to_string()), - images: vec![EditorAgentGeneratedImage { - resource_id: Some("resource-2".to_string()), - object_key: Some("generated/result.png".to_string()), - asset_object_id: Some("asset-object-2".to_string()), - image_src: "https://example.test/generated.png".to_string(), - thumbnail_src: None, - width: Some(1024), - height: Some(1024), - }], - }) - .expect("event data should serialize"), - }; - - let payload = serde_json::to_value(envelope).expect("envelope should serialize"); - assert_eq!(payload["event"], json!("generation_result")); - assert_eq!(payload["data"]["toolName"], json!("generate_image")); - assert_eq!( - payload["data"]["images"][0]["resourceId"], - json!("resource-2") - ); - assert_eq!( - payload["data"]["images"][0]["objectKey"], - json!("generated/result.png") - ); - - let error_envelope = EditorAgentSseEnvelope { - event: EditorAgentSseEventType::Error, - data: serde_json::to_value(EditorAgentErrorEvent { - conversation_id: Some("editor-agent-conv-1".to_string()), - code: "INSUFFICIENT_BALANCE".to_string(), - message: "泥点余额不足,本次生成未执行".to_string(), - recoverable: true, - }) - .expect("event data should serialize"), - }; - let error_payload = - serde_json::to_value(error_envelope).expect("envelope should serialize"); - assert_eq!(error_payload["event"], json!("error")); - assert_eq!(error_payload["data"]["recoverable"], json!(true)); - } - - #[test] - fn stream_request_defaults_attachments_to_empty() { - let payload = json!({ - "clientMessageId": "client-message-1", - "text": "随便聊聊" - }); - let decoded: StreamEditorAgentMessageRequest = - serde_json::from_value(payload).expect("request should deserialize"); - assert!(decoded.attachments.is_empty()); - } -}