Preserve partial creation replies on stream failure
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
kdletters
2026-05-05 11:31:50 +08:00
parent 100fee7e7a
commit 995661e7cc
299 changed files with 13805 additions and 1429 deletions

View File

@@ -1575,6 +1575,110 @@ pub(crate) fn map_match3d_click_item_procedure_result(
})
}
pub(crate) fn map_square_hole_agent_session_procedure_result(
result: SquareHoleAgentSessionProcedureResult,
) -> Result<SquareHoleAgentSessionRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let session_json = result
.session_json
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole agent session 快照"))?;
let session =
serde_json::from_str::<SquareHoleAgentSessionJsonRecord>(&session_json).map_err(
|error| {
SpacetimeClientError::Runtime(format!(
"square hole session_json 非法: {error}"
))
},
)?;
Ok(map_square_hole_agent_session_snapshot(session))
}
pub(crate) fn map_square_hole_work_procedure_result(
result: SquareHoleWorkProcedureResult,
) -> Result<SquareHoleWorkProfileRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let work_json = result
.work_json
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole work 快照"))?;
let work = serde_json::from_str::<SquareHoleWorkJsonRecord>(&work_json).map_err(|error| {
SpacetimeClientError::Runtime(format!("square hole work_json 非法: {error}"))
})?;
Ok(map_square_hole_work_snapshot(work))
}
pub(crate) fn map_square_hole_works_procedure_result(
result: SquareHoleWorksProcedureResult,
) -> Result<Vec<SquareHoleWorkProfileRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let items_json = result
.items_json
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole works 快照"))?;
let items = serde_json::from_str::<Vec<SquareHoleWorkJsonRecord>>(&items_json).map_err(
|error| {
SpacetimeClientError::Runtime(format!("square hole works items_json 非法: {error}"))
},
)?;
Ok(items.into_iter().map(map_square_hole_work_snapshot).collect())
}
pub(crate) fn map_square_hole_run_procedure_result(
result: SquareHoleRunProcedureResult,
) -> Result<SquareHoleRunRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run_json = result
.run_json
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole run 快照"))?;
map_square_hole_run_json(run_json)
}
pub(crate) fn map_square_hole_drop_shape_procedure_result(
result: SquareHoleDropShapeProcedureResult,
) -> Result<SquareHoleDropConfirmationRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
let run_json = result
.run_json
.ok_or_else(|| SpacetimeClientError::missing_snapshot("square hole drop run 快照"))?;
let feedback_json = result.feedback_json.ok_or_else(|| {
SpacetimeClientError::missing_snapshot("square hole drop feedback 快照")
})?;
let run = map_square_hole_run_json(run_json)?;
let feedback =
serde_json::from_str::<SquareHoleDropFeedbackJsonRecord>(&feedback_json).map_err(
|error| {
SpacetimeClientError::Runtime(format!(
"square hole feedback_json 非法: {error}"
))
},
)?;
Ok(SquareHoleDropConfirmationRecord {
status: result.status,
accepted: feedback.accepted,
reject_reason: feedback.reject_reason.clone(),
failure_reason: result.failure_reason,
feedback: map_square_hole_feedback_snapshot(feedback),
run,
})
}
pub(crate) fn map_story_session_procedure_result(
result: StorySessionProcedureResult,
) -> Result<StorySessionResultRecord, SpacetimeClientError> {
@@ -2815,6 +2919,198 @@ fn build_match3d_anchor_item(key: &str, label: &str, value: &str) -> Match3DAnch
}
}
fn map_square_hole_agent_session_snapshot(
snapshot: SquareHoleAgentSessionJsonRecord,
) -> SquareHoleAgentSessionRecord {
let config = map_square_hole_creator_config(snapshot.config);
SquareHoleAgentSessionRecord {
session_id: snapshot.session_id,
current_turn: snapshot.current_turn,
progress_percent: snapshot.progress_percent,
stage: normalize_square_hole_stage(&snapshot.stage).to_string(),
anchor_pack: build_square_hole_anchor_pack(&config),
config,
draft: snapshot.draft.map(map_square_hole_result_draft),
messages: snapshot
.messages
.into_iter()
.map(map_square_hole_agent_message_snapshot)
.collect(),
last_assistant_reply: empty_string_to_none(snapshot.last_assistant_reply),
published_profile_id: snapshot.published_profile_id,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
}
}
fn map_square_hole_creator_config(
snapshot: SquareHoleCreatorConfigJsonRecord,
) -> SquareHoleCreatorConfigRecord {
SquareHoleCreatorConfigRecord {
theme_text: snapshot.theme_text,
twist_rule: snapshot.twist_rule,
shape_count: snapshot.shape_count,
difficulty: snapshot.difficulty,
}
}
fn map_square_hole_result_draft(
snapshot: SquareHoleDraftJsonRecord,
) -> SquareHoleResultDraftRecord {
SquareHoleResultDraftRecord {
profile_id: snapshot.profile_id,
game_name: snapshot.game_name,
theme_text: snapshot.theme_text,
twist_rule: snapshot.twist_rule,
summary: snapshot.summary_text,
tags: snapshot.tags,
shape_count: snapshot.shape_count,
difficulty: snapshot.difficulty,
publish_ready: false,
blockers: Vec::new(),
}
}
fn map_square_hole_agent_message_snapshot(
snapshot: SquareHoleAgentMessageJsonRecord,
) -> SquareHoleAgentMessageRecord {
SquareHoleAgentMessageRecord {
id: snapshot.message_id,
role: snapshot.role,
kind: normalize_square_hole_message_kind(&snapshot.kind).to_string(),
text: snapshot.text,
created_at: format_timestamp_micros(snapshot.created_at_micros),
}
}
fn map_square_hole_work_snapshot(
snapshot: SquareHoleWorkJsonRecord,
) -> SquareHoleWorkProfileRecord {
SquareHoleWorkProfileRecord {
work_id: snapshot.work_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
source_session_id: empty_string_to_none(snapshot.source_session_id),
author_display_name: snapshot.author_display_name,
game_name: snapshot.game_name,
theme_text: snapshot.theme_text,
twist_rule: snapshot.twist_rule,
summary: snapshot.summary_text,
tags: snapshot.tags,
cover_image_src: empty_string_to_none(snapshot.cover_image_src),
shape_count: snapshot.shape_count,
difficulty: snapshot.difficulty,
publication_status: normalize_square_hole_publication_status(&snapshot.publication_status)
.to_string(),
play_count: snapshot.play_count,
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
publish_ready: snapshot.publish_ready,
}
}
fn map_square_hole_run_json(run_json: String) -> Result<SquareHoleRunRecord, SpacetimeClientError> {
let run = serde_json::from_str::<SquareHoleRunJsonRecord>(&run_json).map_err(|error| {
SpacetimeClientError::Runtime(format!("square hole run_json 非法: {error}"))
})?;
Ok(map_square_hole_run_snapshot(run))
}
fn map_square_hole_run_snapshot(snapshot: SquareHoleRunJsonRecord) -> SquareHoleRunRecord {
SquareHoleRunRecord {
run_id: snapshot.run_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
status: normalize_square_hole_run_status(&snapshot.status).to_string(),
snapshot_version: snapshot.snapshot_version,
started_at_ms: i64_to_u64_ms(snapshot.started_at_ms),
duration_limit_ms: i64_to_u64_ms(snapshot.duration_limit_ms),
server_now_ms: Some(i64_to_u64_ms(snapshot.server_now_ms)),
remaining_ms: i64_to_u64_ms(snapshot.remaining_ms),
total_shape_count: snapshot.total_shape_count,
completed_shape_count: snapshot.completed_shape_count,
combo: snapshot.combo,
best_combo: snapshot.best_combo,
score: snapshot.score,
rule_label: snapshot.rule_label,
current_shape: snapshot.current_shape.map(map_square_hole_shape_snapshot),
holes: snapshot
.holes
.into_iter()
.map(map_square_hole_hole_snapshot)
.collect(),
last_feedback: snapshot
.last_feedback
.map(map_square_hole_feedback_snapshot),
last_confirmed_action_id: None,
}
}
fn map_square_hole_shape_snapshot(
snapshot: SquareHoleShapeJsonRecord,
) -> SquareHoleShapeSnapshotRecord {
SquareHoleShapeSnapshotRecord {
shape_id: snapshot.shape_id,
shape_kind: snapshot.shape_kind,
label: snapshot.label,
color: snapshot.color,
}
}
fn map_square_hole_hole_snapshot(
snapshot: SquareHoleHoleJsonRecord,
) -> SquareHoleHoleSnapshotRecord {
SquareHoleHoleSnapshotRecord {
hole_id: snapshot.hole_id,
hole_kind: snapshot.hole_kind,
label: snapshot.label,
x: snapshot.x,
y: snapshot.y,
}
}
fn map_square_hole_feedback_snapshot(
snapshot: SquareHoleDropFeedbackJsonRecord,
) -> SquareHoleDropFeedbackRecord {
SquareHoleDropFeedbackRecord {
accepted: snapshot.accepted,
reject_reason: snapshot
.reject_reason
.map(|value| normalize_square_hole_reject_reason(&value).to_string()),
message: snapshot.message,
}
}
fn build_square_hole_anchor_pack(
config: &SquareHoleCreatorConfigRecord,
) -> SquareHoleAnchorPackRecord {
let shape_count = config.shape_count.to_string();
let difficulty = config.difficulty.to_string();
SquareHoleAnchorPackRecord {
theme: build_square_hole_anchor_item("theme", "题材主题", config.theme_text.as_str()),
twist_rule: build_square_hole_anchor_item("twistRule", "反差规则", config.twist_rule.as_str()),
shape_count: build_square_hole_anchor_item("shapeCount", "形状数量", shape_count.as_str()),
difficulty: build_square_hole_anchor_item("difficulty", "难度", difficulty.as_str()),
}
}
fn build_square_hole_anchor_item(
key: &str,
label: &str,
value: &str,
) -> SquareHoleAnchorItemRecord {
SquareHoleAnchorItemRecord {
key: key.to_string(),
label: label.to_string(),
value: value.to_string(),
status: if value.trim().is_empty() {
"missing"
} else {
"confirmed"
}
.to_string(),
}
}
fn normalize_match3d_stage(value: &str) -> &str {
match value {
"Collecting" | "collecting" | "collecting_config" => "collecting_config",
@@ -2840,6 +3136,54 @@ fn normalize_match3d_message_kind(value: &str) -> &str {
}
}
fn normalize_square_hole_stage(value: &str) -> &str {
match value {
"Collecting" | "CollectingConfig" | "collecting" | "collecting_config" => {
"collecting_config"
}
"ReadyToCompile" | "ready_to_compile" => "ready_to_compile",
"DraftCompiled" | "DraftReady" | "draft_compiled" | "draft_ready" => "draft_ready",
"Published" | "published" => "published",
_ => value,
}
}
fn normalize_square_hole_publication_status(value: &str) -> &str {
match value {
"Draft" | "draft" => "draft",
"Published" | "published" => "published",
_ => value,
}
}
fn normalize_square_hole_run_status(value: &str) -> &str {
match value {
"Running" | "running" => "running",
"Won" | "won" => "won",
"Failed" | "failed" => "failed",
"Stopped" | "stopped" => "stopped",
_ => value,
}
}
fn normalize_square_hole_message_kind(value: &str) -> &str {
match value {
"text" => "chat",
_ => value,
}
}
fn normalize_square_hole_reject_reason(value: &str) -> &str {
match value {
"RunNotActive" | "run_not_active" => "run_not_active",
"SnapshotVersionMismatch" | "snapshot_version_mismatch" => "snapshot_version_mismatch",
"HoleNotFound" | "hole_not_found" => "hole_not_found",
"Incompatible" | "incompatible" => "incompatible",
"TimeUp" | "time_up" => "time_up",
_ => value,
}
}
fn empty_string_to_none(value: String) -> Option<String> {
let trimmed = value.trim();
if trimmed.is_empty() {
@@ -5592,6 +5936,378 @@ struct Match3DRunJsonRecord {
failure_reason: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentSessionCreateRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub seed_text: String,
pub welcome_message_id: String,
pub welcome_message_text: String,
pub config_json: Option<String>,
pub created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentMessageSubmitRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub user_message_id: String,
pub user_message_text: String,
pub submitted_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentMessageFinalizeRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub assistant_message_id: Option<String>,
pub assistant_reply_text: Option<String>,
pub config_json: Option<String>,
pub progress_percent: u32,
pub stage: String,
pub updated_at_micros: i64,
pub error_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleCompileDraftRecordInput {
pub session_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub author_display_name: String,
pub game_name: Option<String>,
pub summary_text: Option<String>,
pub tags_json: Option<String>,
pub cover_image_src: Option<String>,
pub compiled_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleWorkUpdateRecordInput {
pub profile_id: String,
pub owner_user_id: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary_text: String,
pub tags_json: String,
pub cover_image_src: String,
pub shape_count: u32,
pub difficulty: u32,
pub updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunStartRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub profile_id: String,
pub started_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunDropRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub hole_id: String,
pub client_snapshot_version: u64,
pub client_event_id: String,
pub dropped_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunStopRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub stopped_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunRestartRecordInput {
pub source_run_id: String,
pub next_run_id: String,
pub owner_user_id: String,
pub restarted_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleRunTimeUpRecordInput {
pub run_id: String,
pub owner_user_id: String,
pub finished_at_ms: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAnchorItemRecord {
pub key: String,
pub label: String,
pub value: String,
pub status: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAnchorPackRecord {
pub theme: SquareHoleAnchorItemRecord,
pub twist_rule: SquareHoleAnchorItemRecord,
pub shape_count: SquareHoleAnchorItemRecord,
pub difficulty: SquareHoleAnchorItemRecord,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleCreatorConfigRecord {
pub theme_text: String,
pub twist_rule: String,
pub shape_count: u32,
pub difficulty: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleResultDraftRecord {
pub profile_id: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
pub shape_count: u32,
pub difficulty: u32,
pub publish_ready: bool,
pub blockers: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentMessageRecord {
pub id: String,
pub role: String,
pub kind: String,
pub text: String,
pub created_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleAgentSessionRecord {
pub session_id: String,
pub current_turn: u32,
pub progress_percent: u32,
pub stage: String,
pub anchor_pack: SquareHoleAnchorPackRecord,
pub config: SquareHoleCreatorConfigRecord,
pub draft: Option<SquareHoleResultDraftRecord>,
pub messages: Vec<SquareHoleAgentMessageRecord>,
pub last_assistant_reply: Option<String>,
pub published_profile_id: Option<String>,
pub updated_at: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleWorkProfileRecord {
pub work_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub source_session_id: Option<String>,
pub author_display_name: String,
pub game_name: String,
pub theme_text: String,
pub twist_rule: String,
pub summary: String,
pub tags: Vec<String>,
pub cover_image_src: Option<String>,
pub shape_count: u32,
pub difficulty: u32,
pub publication_status: String,
pub play_count: u32,
pub updated_at: String,
pub published_at: Option<String>,
pub publish_ready: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleShapeSnapshotRecord {
pub shape_id: String,
pub shape_kind: String,
pub label: String,
pub color: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SquareHoleHoleSnapshotRecord {
pub hole_id: String,
pub hole_kind: String,
pub label: String,
pub x: f32,
pub y: f32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SquareHoleDropFeedbackRecord {
pub accepted: bool,
pub reject_reason: Option<String>,
pub message: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SquareHoleRunRecord {
pub run_id: String,
pub profile_id: String,
pub owner_user_id: String,
pub status: String,
pub snapshot_version: u64,
pub started_at_ms: u64,
pub duration_limit_ms: u64,
pub server_now_ms: Option<u64>,
pub remaining_ms: u64,
pub total_shape_count: u32,
pub completed_shape_count: u32,
pub combo: u32,
pub best_combo: u32,
pub score: u32,
pub rule_label: String,
pub current_shape: Option<SquareHoleShapeSnapshotRecord>,
pub holes: Vec<SquareHoleHoleSnapshotRecord>,
pub last_feedback: Option<SquareHoleDropFeedbackRecord>,
pub last_confirmed_action_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SquareHoleDropConfirmationRecord {
pub status: String,
pub accepted: bool,
pub reject_reason: Option<String>,
pub failure_reason: Option<String>,
pub feedback: SquareHoleDropFeedbackRecord,
pub run: SquareHoleRunRecord,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleCreatorConfigJsonRecord {
theme_text: String,
twist_rule: String,
shape_count: u32,
difficulty: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleAgentMessageJsonRecord {
message_id: String,
#[allow(dead_code)]
session_id: String,
role: String,
kind: String,
text: String,
created_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleDraftJsonRecord {
profile_id: String,
game_name: String,
theme_text: String,
twist_rule: String,
summary_text: String,
tags: Vec<String>,
shape_count: u32,
difficulty: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleAgentSessionJsonRecord {
session_id: String,
#[allow(dead_code)]
owner_user_id: String,
#[allow(dead_code)]
seed_text: String,
current_turn: u32,
progress_percent: u32,
stage: String,
config: SquareHoleCreatorConfigJsonRecord,
draft: Option<SquareHoleDraftJsonRecord>,
messages: Vec<SquareHoleAgentMessageJsonRecord>,
last_assistant_reply: String,
published_profile_id: Option<String>,
#[allow(dead_code)]
created_at_micros: i64,
updated_at_micros: i64,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleWorkJsonRecord {
work_id: String,
profile_id: String,
owner_user_id: String,
source_session_id: String,
author_display_name: String,
game_name: String,
theme_text: String,
twist_rule: String,
summary_text: String,
tags: Vec<String>,
cover_image_src: String,
shape_count: u32,
difficulty: u32,
#[allow(dead_code)]
config: SquareHoleCreatorConfigJsonRecord,
publication_status: String,
publish_ready: bool,
play_count: u32,
updated_at_micros: i64,
published_at_micros: Option<i64>,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleShapeJsonRecord {
shape_id: String,
shape_kind: String,
label: String,
color: String,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleHoleJsonRecord {
hole_id: String,
hole_kind: String,
label: String,
x: f32,
y: f32,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleDropFeedbackJsonRecord {
accepted: bool,
reject_reason: Option<String>,
message: String,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct SquareHoleRunJsonRecord {
run_id: String,
profile_id: String,
owner_user_id: String,
status: String,
snapshot_version: u64,
started_at_ms: i64,
duration_limit_ms: i64,
server_now_ms: i64,
remaining_ms: i64,
total_shape_count: u32,
completed_shape_count: u32,
combo: u32,
best_combo: u32,
score: u32,
rule_label: String,
current_shape: Option<SquareHoleShapeJsonRecord>,
holes: Vec<SquareHoleHoleJsonRecord>,
last_feedback: Option<SquareHoleDropFeedbackJsonRecord>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PuzzleAnchorItemRecord {
pub key: String,