feat: complete M5 custom world and agent chain
This commit is contained in:
@@ -11,21 +11,28 @@ 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,
|
||||
SendCustomWorldAgentMessageRequest,
|
||||
CustomWorldPublishGateResponse, CustomWorldResultPreviewBlockerResponse,
|
||||
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,
|
||||
CustomWorldProfileUpsertRecordInput, CustomWorldPublishWorldRecordInput,
|
||||
CustomWorldProfileUpsertRecordInput, CustomWorldPublishGateRecord,
|
||||
CustomWorldResultPreviewBlockerRecord, CustomWorldWorkSummaryRecord,
|
||||
CustomWorldSupportedActionRecord, SpacetimeClientError,
|
||||
};
|
||||
|
||||
@@ -386,6 +393,66 @@ pub async fn get_custom_world_agent_session(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_custom_world_works(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let items = state
|
||||
.spacetime_client()
|
||||
.list_custom_world_works(authenticated.claims().user_id().to_string())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
custom_world_error_response(&request_context, map_custom_world_client_error(error))
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
CustomWorldWorksResponse {
|
||||
items: items
|
||||
.into_iter()
|
||||
.map(map_custom_world_work_summary_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_custom_world_agent_card_detail(
|
||||
State(state): State<AppState>,
|
||||
Path((session_id, card_id)): Path<(String, String)>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
if session_id.trim().is_empty() || card_id.trim().is_empty() {
|
||||
return Err(custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": "sessionId and cardId are required",
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
let card = state
|
||||
.spacetime_client()
|
||||
.get_custom_world_agent_card_detail(
|
||||
session_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
card_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
custom_world_error_response(&request_context, map_custom_world_client_error(error))
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
CustomWorldAgentCardDetailResponse {
|
||||
card: map_custom_world_draft_card_detail_response(card),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn submit_custom_world_agent_message(
|
||||
State(state): State<AppState>,
|
||||
Path(session_id): Path<String>,
|
||||
@@ -569,7 +636,7 @@ pub async fn execute_custom_world_agent_action(
|
||||
Path(session_id): Path<String>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
payload: Result<Json<Value>, JsonRejection>,
|
||||
payload: Result<Json<ExecuteCustomWorldAgentActionRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let Json(payload) = payload.map_err(|error| {
|
||||
custom_world_error_response(
|
||||
@@ -581,84 +648,46 @@ pub async fn execute_custom_world_agent_action(
|
||||
)
|
||||
})?;
|
||||
|
||||
let action = payload
|
||||
.get("action")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.unwrap_or_default();
|
||||
if action != "publish_world" {
|
||||
if session_id.trim().is_empty() {
|
||||
return Err(custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::NOT_IMPLEMENTED).with_details(json!({
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": "当前 Stage 5 仅支持 publish_world action",
|
||||
"message": "sessionId is required",
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
let profile_id = payload
|
||||
.get("profileId")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| format!("agent-draft-{session_id}"));
|
||||
let draft_profile = payload.get("draftProfile").cloned().ok_or_else(|| {
|
||||
let action = payload.action.trim().to_string();
|
||||
if action.is_empty() {
|
||||
return Err(custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": "action is required",
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
let payload_json = serde_json::to_string(&payload).map_err(|error| {
|
||||
custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": "publish_world 当前必须显式提供 draftProfile",
|
||||
"message": format!("action payload JSON 序列化失败:{error}"),
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
let setting_text = payload
|
||||
.get("settingText")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| {
|
||||
custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": "publish_world 当前必须显式提供 settingText",
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
|
||||
let publish_result = state
|
||||
let result = state
|
||||
.spacetime_client()
|
||||
.publish_custom_world_world(CustomWorldPublishWorldRecordInput {
|
||||
session_id: session_id.clone(),
|
||||
profile_id,
|
||||
.execute_custom_world_agent_action(CustomWorldAgentActionExecuteRecordInput {
|
||||
session_id,
|
||||
owner_user_id: authenticated.claims().user_id().to_string(),
|
||||
draft_profile_json: serde_json::to_string(&draft_profile).map_err(|error| {
|
||||
custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": format!("draftProfile JSON 序列化失败:{error}"),
|
||||
})),
|
||||
)
|
||||
})?,
|
||||
legacy_result_profile_json: payload
|
||||
.get("legacyResultProfile")
|
||||
.map(serde_json::to_string)
|
||||
.transpose()
|
||||
.map_err(|error| {
|
||||
custom_world_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "custom-world-agent",
|
||||
"message": format!("legacyResultProfile JSON 序列化失败:{error}"),
|
||||
})),
|
||||
)
|
||||
})?,
|
||||
setting_text,
|
||||
author_display_name: resolve_author_display_name(&authenticated),
|
||||
published_at_micros: current_utc_micros(),
|
||||
operation_id: build_prefixed_uuid_id("operation-"),
|
||||
action,
|
||||
payload_json: Some(payload_json),
|
||||
submitted_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| {
|
||||
@@ -668,15 +697,7 @@ pub async fn execute_custom_world_agent_action(
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
json!({
|
||||
"operation": {
|
||||
"operationId": format!("publish-world-{session_id}"),
|
||||
"type": "publish_world",
|
||||
"status": "completed",
|
||||
"phaseLabel": "世界已发布",
|
||||
"phaseDetail": format!("正式世界档案已写入作品库:{}。", publish_result.entry.profile_id),
|
||||
"progress": 100,
|
||||
"error": Value::Null,
|
||||
}
|
||||
"operation": map_custom_world_agent_operation_response(result.operation),
|
||||
}),
|
||||
))
|
||||
}
|
||||
@@ -722,6 +743,37 @@ fn map_custom_world_gallery_card_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_work_summary_response(
|
||||
item: CustomWorldWorkSummaryRecord,
|
||||
) -> CustomWorldWorkSummaryResponse {
|
||||
CustomWorldWorkSummaryResponse {
|
||||
work_id: item.work_id,
|
||||
source_type: item.source_type,
|
||||
status: item.status,
|
||||
title: item.title,
|
||||
subtitle: item.subtitle,
|
||||
summary: item.summary,
|
||||
cover_image_src: item.cover_image_src,
|
||||
cover_render_mode: item.cover_render_mode,
|
||||
cover_character_image_srcs: item.cover_character_image_srcs,
|
||||
updated_at: item.updated_at,
|
||||
published_at: item.published_at,
|
||||
stage: item.stage,
|
||||
stage_label: item.stage_label,
|
||||
playable_npc_count: item.playable_npc_count,
|
||||
landmark_count: item.landmark_count,
|
||||
role_visual_ready_count: item.role_visual_ready_count,
|
||||
role_animation_ready_count: item.role_animation_ready_count,
|
||||
role_asset_summary_label: item.role_asset_summary_label,
|
||||
session_id: item.session_id,
|
||||
profile_id: item.profile_id,
|
||||
can_resume: item.can_resume,
|
||||
can_enter_world: item.can_enter_world,
|
||||
blocker_count: item.blocker_count,
|
||||
publish_ready: item.publish_ready,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_agent_session_response(
|
||||
session: CustomWorldAgentSessionRecord,
|
||||
) -> CustomWorldAgentSessionSnapshotResponse {
|
||||
@@ -763,11 +815,28 @@ 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),
|
||||
result_preview: session.result_preview,
|
||||
updated_at: session.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_publish_gate_response(
|
||||
gate: CustomWorldPublishGateRecord,
|
||||
) -> CustomWorldPublishGateResponse {
|
||||
CustomWorldPublishGateResponse {
|
||||
profile_id: gate.profile_id,
|
||||
blockers: gate
|
||||
.blockers
|
||||
.into_iter()
|
||||
.map(map_custom_world_result_preview_blocker_response)
|
||||
.collect(),
|
||||
blocker_count: gate.blocker_count,
|
||||
publish_ready: gate.publish_ready,
|
||||
can_enter_world: gate.can_enter_world,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_agent_message_response(
|
||||
message: CustomWorldAgentMessageRecord,
|
||||
) -> CustomWorldAgentMessageResponse {
|
||||
@@ -812,6 +881,38 @@ fn map_custom_world_draft_card_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_draft_card_detail_response(
|
||||
card: CustomWorldDraftCardDetailRecord,
|
||||
) -> CustomWorldDraftCardDetailResponse {
|
||||
CustomWorldDraftCardDetailResponse {
|
||||
id: card.card_id,
|
||||
kind: card.kind,
|
||||
title: card.title,
|
||||
sections: card
|
||||
.sections
|
||||
.into_iter()
|
||||
.map(map_custom_world_draft_card_detail_section_response)
|
||||
.collect(),
|
||||
linked_ids: card.linked_ids,
|
||||
locked: card.locked,
|
||||
editable: card.editable,
|
||||
editable_section_ids: card.editable_section_ids,
|
||||
warning_messages: card.warning_messages,
|
||||
asset_status: card.asset_status,
|
||||
asset_status_label: card.asset_status_label,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_draft_card_detail_section_response(
|
||||
section: CustomWorldDraftCardDetailSectionRecord,
|
||||
) -> CustomWorldDraftCardDetailSectionResponse {
|
||||
CustomWorldDraftCardDetailSectionResponse {
|
||||
id: section.section_id,
|
||||
label: section.label,
|
||||
value: section.value,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_agent_checkpoint_response(
|
||||
checkpoint: CustomWorldAgentCheckpointRecord,
|
||||
) -> CustomWorldAgentCheckpointResponse {
|
||||
@@ -832,6 +933,16 @@ fn map_custom_world_supported_action_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn map_custom_world_result_preview_blocker_response(
|
||||
blocker: CustomWorldResultPreviewBlockerRecord,
|
||||
) -> CustomWorldResultPreviewBlockerResponse {
|
||||
CustomWorldResultPreviewBlockerResponse {
|
||||
id: blocker.id,
|
||||
code: blocker.code,
|
||||
message: blocker.message,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_stream_reply_text(session: &CustomWorldAgentSessionSnapshotResponse) -> String {
|
||||
session
|
||||
.last_assistant_reply
|
||||
|
||||
Reference in New Issue
Block a user