This commit is contained in:
2026-04-23 00:22:57 +08:00
parent 84dc92646a
commit cd207dc237
452 changed files with 10980 additions and 6574 deletions

View File

@@ -5,6 +5,7 @@ version.workspace = true
license.workspace = true
[dependencies]
async-stream = "0.3"
axum = "0.8"
base64 = "0.22"
bytes = "1"

View File

@@ -41,6 +41,9 @@ use std::convert::Infallible;
use crate::{
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
custom_world_agent_turn::{
CustomWorldAgentTurnRequest, build_finalize_record_input, run_custom_world_agent_turn,
},
request_context::RequestContext, state::AppState,
};
@@ -528,21 +531,56 @@ pub async fn submit_custom_world_agent_message(
));
}
let owner_user_id = authenticated.claims().user_id().to_string();
let operation_id = build_prefixed_uuid_id("operation-");
let submitted_at_micros = current_utc_micros();
let operation = state
.spacetime_client()
.submit_custom_world_agent_message(CustomWorldAgentMessageSubmitRecordInput {
session_id,
owner_user_id: authenticated.claims().user_id().to_string(),
session_id: session_id.clone(),
owner_user_id: owner_user_id.clone(),
user_message_id: client_message_id,
user_message_text: message_text,
operation_id: build_prefixed_uuid_id("operation-"),
submitted_at_micros: current_utc_micros(),
operation_id: operation_id.clone(),
submitted_at_micros,
})
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
let session = state
.spacetime_client()
.get_custom_world_agent_session(session_id.clone(), owner_user_id.clone())
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
let turn_result = run_custom_world_agent_turn(
CustomWorldAgentTurnRequest {
llm_client: state.llm_client(),
session: &session,
quick_fill_requested: payload.quick_fill_requested.unwrap_or(false),
focus_card_id: payload.focus_card_id.clone(),
},
|_| {},
)
.await;
state
.spacetime_client()
.finalize_custom_world_agent_message(build_finalize_record_input(
session_id,
owner_user_id,
operation_id,
format!("assistant-{}", operation.operation_id),
turn_result,
current_utc_micros(),
))
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
Ok(json_success_body(
Some(&request_context),
json!({
@@ -591,7 +629,7 @@ pub async fn stream_custom_world_agent_message(
}
let owner_user_id = authenticated.claims().user_id().to_string();
state
let operation = state
.spacetime_client()
.submit_custom_world_agent_message(CustomWorldAgentMessageSubmitRecordInput {
session_id: session_id.clone(),
@@ -607,31 +645,63 @@ pub async fn stream_custom_world_agent_message(
})?;
let session = state
.spacetime_client()
.get_custom_world_agent_session(session_id.clone(), owner_user_id.clone())
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
let mut reply_updates = Vec::<String>::new();
let turn_result = run_custom_world_agent_turn(
CustomWorldAgentTurnRequest {
llm_client: state.llm_client(),
session: &session,
quick_fill_requested: payload.quick_fill_requested.unwrap_or(false),
focus_card_id: payload.focus_card_id.clone(),
},
|text| {
reply_updates.push(text.to_string());
},
)
.await;
state
.spacetime_client()
.finalize_custom_world_agent_message(build_finalize_record_input(
session_id.clone(),
owner_user_id.clone(),
operation.operation_id.clone(),
format!("assistant-{}", operation.operation_id),
turn_result,
current_utc_micros(),
))
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
let final_session = state
.spacetime_client()
.get_custom_world_agent_session(session_id, owner_user_id)
.await
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
let session_response = map_custom_world_agent_session_response(session);
let reply_text = resolve_stream_reply_text(&session_response);
let session_response = map_custom_world_agent_session_response(final_session);
// 这里仍保持“一次性返回完整事件序列”的兼容语义;
// SSE 编码、标准响应头与 body frame 交给 Axum 内建实现维护。
let events = vec![
custom_world_sse_json_event("reply_delta", json!({ "text": reply_text }))
.map_err(|error| custom_world_error_response(&request_context, error))?,
let mut events = reply_updates
.into_iter()
.map(|text| custom_world_sse_json_event("reply_delta", json!({ "text": text })))
.collect::<Result<Vec<_>, _>>()
.map_err(|error| custom_world_error_response(&request_context, error))?;
events.push(
custom_world_sse_json_event("session", json!({ "session": session_response }))
.map_err(|error| custom_world_error_response(&request_context, error))?,
);
events.push(
custom_world_sse_json_event("done", json!({ "ok": true }))
.map_err(|error| custom_world_error_response(&request_context, error))?,
];
let stream = tokio_stream::iter(
events
.into_iter()
.map(|event| Ok::<Event, Infallible>(event)),
);
let stream = tokio_stream::iter(events.into_iter().map(|event| Ok::<Event, Infallible>(event)));
Ok(Sse::new(stream).into_response())
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ mod character_animation_assets;
mod character_visual_assets;
mod config;
mod custom_world;
mod custom_world_agent_turn;
mod custom_world_ai;
mod error_middleware;
mod health;