恢复编辑器 Agent 稳定任务标识

保留外部生成任务稳定 job id 与去重键

供异步结果查询按固定任务标识回填
This commit is contained in:
2026-07-15 13:41:20 +08:00
parent d55f4ba71c
commit ddb508e729
2 changed files with 87 additions and 41 deletions
@@ -6,70 +6,71 @@ use module_editor_agent::framework::memory::VecMemory;
use module_editor_agent::framework::run::PromptOutput;
use module_editor_agent::framework::tool::Tool;
use module_editor_agent::{
derive_conversation_title, editor_agent_messages_object_key,
EDITOR_AGENT_CONVERSATION_ID_PREFIX, EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE,
EDITOR_AGENT_CONVERSATION_ID_PREFIX, EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE,
derive_conversation_title, editor_agent_messages_object_key,
};
use platform_llm::LlmMessage;
use serde::Serialize;
use serde_json::{json, Value};
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use shared_contracts::assets::{
EditorBackgroundMusicGenerateRequest, EditorSoundEffectGenerateRequest,
EditorVideoGenerateRequest,
EditorBackgroundMusicGenerateRequest, EditorSoundEffectGenerateRequest,
EditorVideoGenerateRequest,
};
use shared_contracts::editor_agent::{
CreateEditorAgentConversationRequest, EditorAgentConversationListResponse,
EditorAgentConversationMessagesDocument, EditorAgentConversationResponse,
EditorAgentConversationSummary, EditorAgentMessage, EditorAgentMessageRequest,
EditorAgentMessageResponse, EditorAgentMessageRole, EditorAgentToolCall,
EditorAgentToolCallStatus,
CreateEditorAgentConversationRequest, EditorAgentConversationListResponse,
EditorAgentConversationMessagesDocument, EditorAgentConversationResponse,
EditorAgentConversationSummary, EditorAgentMessage, EditorAgentMessageRequest,
EditorAgentMessageResponse, EditorAgentMessageRole, EditorAgentToolCall,
EditorAgentToolCallStatus,
};
use spacetime_client::{
EditorAgentConversationCreateRecordInput, EditorAgentConversationDeleteRecordInput,
EditorAgentConversationRecord, EditorAgentConversationTouchRecordInput,
EditorProjectGetRecordInput,
EditorAgentConversationCreateRecordInput, EditorAgentConversationDeleteRecordInput,
EditorAgentConversationRecord, EditorAgentConversationTouchRecordInput,
EditorProjectGetRecordInput,
};
use crate::api_response::json_success_body;
use crate::auth::AuthenticatedAccessToken;
use module_editor_agent::agent::agent::LlmChatAgentBuilder;
use crate::editor_agent::pricing::EditorAgentPricedTool;
use module_editor_agent::agent::tools::edit_image::{EditImageTool, EditImageToolArgs};
use module_editor_agent::agent::tools::generate_background_music::{
GenerateBackgroundMusicTool, GenerateBackgroundMusicToolArgs,
};
use module_editor_agent::agent::tools::generate_character::GenerateCharacterTool;
use module_editor_agent::agent::tools::generate_icon_spritesheet::{
GenerateIconSpritesheetTool, GenerateIconSpritesheetToolArgs,
};
use module_editor_agent::agent::tools::generate_image::{GenerateImageTool, GenerateImageToolArgs};
use module_editor_agent::agent::tools::generate_sound_effect::{
GenerateSoundEffectTool, GenerateSoundEffectToolArgs,
};
use module_editor_agent::agent::tools::generate_ui_design::GenerateUiDesignTool;
use module_editor_agent::agent::tools::generate_video::{GenerateVideoTool, GenerateVideoToolArgs};
use crate::editor_agent::utils::{
build_editor_agent_canvas_completion, conversation_detail_from_record, conversation_summary_from_record,
editor_agent_bad_request, empty_messages_document, ensure_editor_project_access,
normalize_editor_agent_attachments, now_rfc3339, read_messages_document,
require_editor_agent_sidebar_enabled, write_messages_document, IntoImageId,
IntoImageId, build_editor_agent_canvas_completion, conversation_detail_from_record,
conversation_summary_from_record, editor_agent_bad_request, empty_messages_document,
ensure_editor_project_access, normalize_editor_agent_attachments, now_rfc3339,
read_messages_document, require_editor_agent_sidebar_enabled, write_messages_document,
};
use crate::editor_agent::{context, display_args, reconcile};
use crate::editor_generation_config::EditorGenerationPricingConfig;
use crate::editor_generation_queue::{
enqueue_editor_generation_job, EDITOR_BACKGROUND_MUSIC_GENERATION_JOB_KIND,
EDITOR_ICON_SPRITESHEET_GENERATION_JOB_KIND, EDITOR_IMAGE_EDIT_JOB_KIND,
EDITOR_IMAGE_GENERATION_JOB_KIND, EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND,
EDITOR_VIDEO_GENERATION_JOB_KIND,
EDITOR_BACKGROUND_MUSIC_GENERATION_JOB_KIND, EDITOR_ICON_SPRITESHEET_GENERATION_JOB_KIND,
EDITOR_IMAGE_EDIT_JOB_KIND, EDITOR_IMAGE_GENERATION_JOB_KIND,
EDITOR_SOUND_EFFECT_GENERATION_JOB_KIND, EDITOR_VIDEO_GENERATION_JOB_KIND,
enqueue_editor_generation_job_with_identity,
};
use crate::editor_project::{
EditorIconSpritesheetGenerationRequest, EditorImageEditRequest, EditorImageGenerationRequest,
EditorIconSpritesheetGenerationRequest, EditorImageEditRequest, EditorImageGenerationRequest,
};
use crate::editor_project::{current_utc_micros, map_editor_project_error};
use crate::http_error::AppError;
use crate::request_context::RequestContext;
use crate::state::AppState;
use shared_kernel::{build_prefixed_uuid_id, normalize_optional_string};
use module_editor_agent::agent::agent::LlmChatAgentBuilder;
use module_editor_agent::agent::tools::context::EditorToolContext;
use module_editor_agent::agent::tools::edit_image::{EditImageTool, EditImageToolArgs};
use module_editor_agent::agent::tools::generate_background_music::{
GenerateBackgroundMusicTool, GenerateBackgroundMusicToolArgs,
};
use module_editor_agent::agent::tools::generate_character::GenerateCharacterTool;
use module_editor_agent::agent::tools::generate_icon_spritesheet::{
GenerateIconSpritesheetTool, GenerateIconSpritesheetToolArgs,
};
use module_editor_agent::agent::tools::generate_image::{GenerateImageTool, GenerateImageToolArgs};
use module_editor_agent::agent::tools::generate_sound_effect::{
GenerateSoundEffectTool, GenerateSoundEffectToolArgs,
};
use module_editor_agent::agent::tools::generate_ui_design::GenerateUiDesignTool;
use module_editor_agent::agent::tools::generate_video::{GenerateVideoTool, GenerateVideoToolArgs};
use shared_kernel::{build_prefixed_uuid_id, normalize_optional_string};
pub async fn editor_agent_message(
State(state): State<AppState>,
@@ -844,15 +845,21 @@ pub async fn confirm_editor_agent_tool_call(
AppError::from_status(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
.with_details(json!({"message": format!("failed to serialize tool payload: {error}")}))
})?;
let job = enqueue_editor_generation_job(
let (job_id, dedupe_key) = editor_agent_tool_job_identity(
conversation.conversation_id.as_str(),
message_id,
tool_name.as_str(),
);
let job = enqueue_editor_generation_job_with_identity(
&state,
&request_context,
conversation.owner_user_id.as_str(),
job_kind,
conversation.project_id.clone(),
request_label,
u64::from(price_mud_points),
&payload,
job_id,
dedupe_key,
)
.await?;
let message = &mut document.messages[message_id];
@@ -874,6 +881,16 @@ fn parse_confirm_tool_args<T: serde::de::DeserializeOwned>(value: &Value) -> Res
.map_err(|error| editor_agent_bad_request(format!("invalid tool call args: {error}")))
}
fn editor_agent_tool_job_identity(
conversation_id: &str,
message_id: usize,
tool_name: &str,
) -> (String, String) {
let dedupe_key = format!("editor-agent:{conversation_id}:{message_id}:{tool_name}");
let digest = Sha256::digest(dedupe_key.as_bytes());
(format!("task-editor-agent-{digest:x}"), dedupe_key)
}
async fn load_editor_agent_project(
state: &AppState,
conversation: &EditorAgentConversationRecord,
@@ -47,12 +47,41 @@ where
T: Serialize,
{
let job_id = build_prefixed_uuid_id("task-");
enqueue_editor_generation_job_with_identity(
state,
owner_user_id,
job_kind,
source_entity_id,
request_label,
price_mud_points,
payload,
job_id.clone(),
format!("editor-canvas:{job_kind}:{job_id}"),
)
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn enqueue_editor_generation_job_with_identity<T>(
state: &AppState,
owner_user_id: &str,
job_kind: &str,
source_entity_id: impl Into<String>,
request_label: impl Into<String>,
price_mud_points: u64,
payload: &T,
job_id: String,
dedupe_key: String,
) -> Result<ExternalGenerationJobRecord, AppError>
where
T: Serialize,
{
let request_payload_json = serialize_editor_generation_job_payload(payload)?;
let now_micros = current_utc_micros();
state
.spacetime_client()
.enqueue_external_generation_job(ExternalGenerationJobEnqueueRecordInput {
dedupe_key: format!("editor-canvas:{job_kind}:{job_id}"),
dedupe_key,
job_id,
job_kind: job_kind.to_string(),
owner_user_id: owner_user_id.to_string(),