feat: checkpoint m5 and bootstrap m6 asset flow
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, Path, State, rejection::JsonRejection},
|
||||
http::{HeaderName, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
use module_custom_world::{
|
||||
CustomWorldThemeMode, empty_agent_anchor_content_json, empty_agent_asset_coverage_json,
|
||||
@@ -10,35 +10,34 @@ use module_custom_world::{
|
||||
};
|
||||
use serde_json::{Map, Value, json};
|
||||
use shared_contracts::runtime::{
|
||||
CreateCustomWorldAgentSessionRequest, CustomWorldAgentCheckpointResponse,
|
||||
CustomWorldAgentCardDetailResponse,
|
||||
CustomWorldAgentMessageResponse, CustomWorldAgentOperationResponse,
|
||||
CustomWorldAgentSessionResponse, CustomWorldAgentSessionSnapshotResponse,
|
||||
CustomWorldDraftCardDetailResponse, CustomWorldDraftCardDetailSectionResponse,
|
||||
CustomWorldDraftCardSummaryResponse, CustomWorldGalleryCardResponse,
|
||||
CustomWorldGalleryDetailResponse, CustomWorldGalleryResponse, CustomWorldLibraryEntryResponse,
|
||||
CustomWorldLibraryMutationResponse, CustomWorldLibraryResponse,
|
||||
CustomWorldProfileUpsertRequest, CustomWorldSupportedActionResponse,
|
||||
CustomWorldPublishGateResponse, CustomWorldResultPreviewBlockerResponse,
|
||||
CustomWorldWorkSummaryResponse, CustomWorldWorksResponse,
|
||||
ExecuteCustomWorldAgentActionRequest, SendCustomWorldAgentMessageRequest,
|
||||
CreateCustomWorldAgentSessionRequest, CustomWorldAgentCardDetailResponse,
|
||||
CustomWorldAgentCheckpointResponse, CustomWorldAgentMessageResponse,
|
||||
CustomWorldAgentOperationResponse, CustomWorldAgentSessionResponse,
|
||||
CustomWorldAgentSessionSnapshotResponse, CustomWorldDraftCardDetailResponse,
|
||||
CustomWorldDraftCardDetailSectionResponse, CustomWorldDraftCardSummaryResponse,
|
||||
CustomWorldGalleryCardResponse, CustomWorldGalleryDetailResponse, CustomWorldGalleryResponse,
|
||||
CustomWorldLibraryEntryResponse, CustomWorldLibraryMutationResponse,
|
||||
CustomWorldLibraryResponse, CustomWorldProfileUpsertRequest, CustomWorldPublishGateResponse,
|
||||
CustomWorldResultPreviewBlockerResponse, CustomWorldSupportedActionResponse,
|
||||
CustomWorldWorkSummaryResponse, CustomWorldWorksResponse, ExecuteCustomWorldAgentActionRequest,
|
||||
SendCustomWorldAgentMessageRequest,
|
||||
};
|
||||
use shared_kernel::build_prefixed_uuid_id;
|
||||
use spacetime_client::{
|
||||
CustomWorldAgentActionExecuteRecordInput,
|
||||
CustomWorldAgentCheckpointRecord, CustomWorldAgentMessageRecord,
|
||||
CustomWorldAgentMessageSubmitRecordInput, CustomWorldAgentOperationRecord,
|
||||
CustomWorldAgentSessionCreateRecordInput, CustomWorldAgentSessionRecord,
|
||||
CustomWorldDraftCardDetailRecord, CustomWorldDraftCardDetailSectionRecord,
|
||||
CustomWorldDraftCardRecord, CustomWorldGalleryEntryRecord, CustomWorldLibraryEntryRecord,
|
||||
CustomWorldAgentActionExecuteRecordInput, CustomWorldAgentCheckpointRecord,
|
||||
CustomWorldAgentMessageRecord, CustomWorldAgentMessageSubmitRecordInput,
|
||||
CustomWorldAgentOperationRecord, CustomWorldAgentSessionCreateRecordInput,
|
||||
CustomWorldAgentSessionRecord, CustomWorldDraftCardDetailRecord,
|
||||
CustomWorldDraftCardDetailSectionRecord, CustomWorldDraftCardRecord,
|
||||
CustomWorldGalleryEntryRecord, CustomWorldLibraryEntryRecord,
|
||||
CustomWorldProfileUpsertRecordInput, CustomWorldPublishGateRecord,
|
||||
CustomWorldResultPreviewBlockerRecord, CustomWorldWorkSummaryRecord,
|
||||
CustomWorldSupportedActionRecord, SpacetimeClientError,
|
||||
CustomWorldResultPreviewBlockerRecord, CustomWorldSupportedActionRecord,
|
||||
CustomWorldWorkSummaryRecord, SpacetimeClientError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
request_context::RequestContext, state::AppState,
|
||||
request_context::RequestContext, sse::SseEventBuffer, state::AppState,
|
||||
};
|
||||
|
||||
pub async fn get_custom_world_library(
|
||||
@@ -84,10 +83,7 @@ pub async fn get_custom_world_library_detail(
|
||||
|
||||
let detail = state
|
||||
.spacetime_client()
|
||||
.get_custom_world_library_detail(
|
||||
authenticated.claims().user_id().to_string(),
|
||||
profile_id,
|
||||
)
|
||||
.get_custom_world_library_detail(authenticated.claims().user_id().to_string(), profile_id)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
custom_world_error_response(&request_context, map_custom_world_client_error(error))
|
||||
@@ -582,19 +578,15 @@ pub async fn stream_custom_world_agent_message(
|
||||
|
||||
// 这里先用“一次性构造完整 SSE 文本”的最小兼容方案,
|
||||
// 复用 Stage 7 的同步 deterministic 写表逻辑,保证前端当前的 reader 协议可直接消费。
|
||||
let mut sse_body = String::new();
|
||||
append_sse_event(&mut sse_body, "reply_delta", &json!({ "text": reply_text }))
|
||||
let mut sse = SseEventBuffer::new();
|
||||
sse.push_json("reply_delta", &json!({ "text": reply_text }))
|
||||
.map_err(|error| custom_world_error_response(&request_context, error))?;
|
||||
append_sse_event(
|
||||
&mut sse_body,
|
||||
"session",
|
||||
&json!({ "session": session_response }),
|
||||
)
|
||||
.map_err(|error| custom_world_error_response(&request_context, error))?;
|
||||
append_sse_event(&mut sse_body, "done", &json!({ "ok": true }))
|
||||
sse.push_json("session", &json!({ "session": session_response }))
|
||||
.map_err(|error| custom_world_error_response(&request_context, error))?;
|
||||
sse.push_json("done", &json!({ "ok": true }))
|
||||
.map_err(|error| custom_world_error_response(&request_context, error))?;
|
||||
|
||||
Ok(build_event_stream_response(sse_body))
|
||||
Ok(sse.into_response())
|
||||
}
|
||||
|
||||
pub async fn get_custom_world_agent_operation(
|
||||
@@ -815,7 +807,9 @@ fn map_custom_world_agent_session_response(
|
||||
.into_iter()
|
||||
.map(map_custom_world_supported_action_response)
|
||||
.collect(),
|
||||
publish_gate: session.publish_gate.map(map_custom_world_publish_gate_response),
|
||||
publish_gate: session
|
||||
.publish_gate
|
||||
.map(map_custom_world_publish_gate_response),
|
||||
result_preview: session.result_preview,
|
||||
updated_at: session.updated_at,
|
||||
}
|
||||
@@ -958,40 +952,11 @@ fn resolve_stream_reply_text(session: &CustomWorldAgentSessionSnapshotResponse)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn append_sse_event(body: &mut String, event: &str, payload: &Value) -> Result<(), AppError> {
|
||||
let payload_text = serde_json::to_string(payload).map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": format!("SSE payload 序列化失败:{error}"),
|
||||
}))
|
||||
})?;
|
||||
|
||||
body.push_str("event: ");
|
||||
body.push_str(event);
|
||||
body.push('\n');
|
||||
body.push_str("data: ");
|
||||
body.push_str(&payload_text);
|
||||
body.push_str("\n\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_event_stream_response(body: String) -> Response {
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, "text/event-stream; charset=utf-8"),
|
||||
(header::CACHE_CONTROL, "no-cache"),
|
||||
// 反向代理场景下显式关闭缓冲,避免 SSE 事件被聚合后才下发。
|
||||
(HeaderName::from_static("x-accel-buffering"), "no"),
|
||||
],
|
||||
body,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn map_custom_world_client_error(error: SpacetimeClientError) -> AppError {
|
||||
let status = match &error {
|
||||
SpacetimeClientError::Procedure(message) if message.contains("custom_world_profile 不存在") => {
|
||||
SpacetimeClientError::Procedure(message)
|
||||
if message.contains("custom_world_profile 不存在") =>
|
||||
{
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
SpacetimeClientError::Procedure(message)
|
||||
|
||||
Reference in New Issue
Block a user