Files
Genarrative/server-rs/crates/shared-contracts/src/ai.rs
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

224 lines
7.4 KiB
Rust

use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateAiTaskRequest {
pub task_kind: String,
pub request_label: String,
pub source_module: String,
#[serde(default)]
pub source_entity_id: Option<String>,
#[serde(default)]
pub request_payload_json: Option<String>,
#[serde(default)]
pub stage_kinds: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AppendAiTextChunkRequest {
pub stage_kind: String,
pub sequence: u32,
pub delta_text: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CompleteAiStageRequest {
#[serde(default)]
pub text_output: Option<String>,
#[serde(default)]
pub structured_payload_json: Option<String>,
#[serde(default)]
pub warning_messages: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AttachAiResultReferenceRequest {
pub reference_kind: String,
pub reference_id: String,
#[serde(default)]
pub label: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct FailAiTaskRequest {
pub failure_message: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AiTaskStagePayload {
pub stage_kind: String,
pub label: String,
pub detail: String,
pub order: u32,
pub status: String,
#[serde(default)]
pub text_output: Option<String>,
#[serde(default)]
pub structured_payload_json: Option<String>,
pub warning_messages: Vec<String>,
#[serde(default)]
pub started_at: Option<String>,
#[serde(default)]
pub completed_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AiResultReferencePayload {
pub result_ref_id: String,
pub task_id: String,
pub reference_kind: String,
pub reference_id: String,
#[serde(default)]
pub label: Option<String>,
pub created_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AiTextChunkPayload {
pub chunk_id: String,
pub task_id: String,
pub stage_kind: String,
pub sequence: u32,
pub delta_text: String,
pub created_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AiTaskPayload {
pub task_id: String,
pub task_kind: String,
pub owner_user_id: String,
pub request_label: String,
pub source_module: String,
#[serde(default)]
pub source_entity_id: Option<String>,
#[serde(default)]
pub request_payload_json: Option<String>,
pub status: String,
#[serde(default)]
pub failure_message: Option<String>,
pub stages: Vec<AiTaskStagePayload>,
pub result_references: Vec<AiResultReferencePayload>,
#[serde(default)]
pub latest_text_output: Option<String>,
#[serde(default)]
pub latest_structured_payload_json: Option<String>,
pub version: u32,
pub created_at: String,
#[serde(default)]
pub started_at: Option<String>,
#[serde(default)]
pub completed_at: Option<String>,
pub updated_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AiTaskMutationResponse {
pub ai_task: AiTaskPayload,
#[serde(default)]
pub ai_text_chunk: Option<AiTextChunkPayload>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AiTaskAcceptedResponse {
pub accepted: bool,
pub task_id: String,
pub action: String,
#[serde(default)]
pub stage_kind: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn create_ai_task_request_uses_camel_case_fields() {
let payload = serde_json::to_value(CreateAiTaskRequest {
task_kind: "story_generation".to_string(),
request_label: "营地开场".to_string(),
source_module: "story".to_string(),
source_entity_id: Some("storysess_001".to_string()),
request_payload_json: Some("{\"scene\":\"camp\"}".to_string()),
stage_kinds: vec!["prepare_prompt".to_string(), "request_model".to_string()],
})
.expect("payload should serialize");
assert_eq!(
payload,
json!({
"taskKind": "story_generation",
"requestLabel": "营地开场",
"sourceModule": "story",
"sourceEntityId": "storysess_001",
"requestPayloadJson": "{\"scene\":\"camp\"}",
"stageKinds": ["prepare_prompt", "request_model"]
})
);
}
#[test]
fn ai_task_mutation_response_uses_camel_case_fields() {
let payload = serde_json::to_value(AiTaskMutationResponse {
ai_task: AiTaskPayload {
task_id: "aitask_001".to_string(),
task_kind: "npc_chat".to_string(),
owner_user_id: "user_001".to_string(),
request_label: "试探问话".to_string(),
source_module: "npc".to_string(),
source_entity_id: Some("npc_001".to_string()),
request_payload_json: None,
status: "running".to_string(),
failure_message: None,
stages: vec![AiTaskStagePayload {
stage_kind: "request_model".to_string(),
label: "请求模型".to_string(),
detail: "向上游模型发起正式推理请求。".to_string(),
order: 1,
status: "running".to_string(),
text_output: Some("你盯着对方的刀柄。".to_string()),
structured_payload_json: None,
warning_messages: vec![],
started_at: Some("2026-04-22T12:00:00Z".to_string()),
completed_at: None,
}],
result_references: vec![],
latest_text_output: Some("你盯着对方的刀柄。".to_string()),
latest_structured_payload_json: None,
version: 2,
created_at: "2026-04-22T12:00:00Z".to_string(),
started_at: Some("2026-04-22T12:00:01Z".to_string()),
completed_at: None,
updated_at: "2026-04-22T12:00:02Z".to_string(),
},
ai_text_chunk: Some(AiTextChunkPayload {
chunk_id: "aichunk_001".to_string(),
task_id: "aitask_001".to_string(),
stage_kind: "request_model".to_string(),
sequence: 1,
delta_text: "".to_string(),
created_at: "2026-04-22T12:00:02Z".to_string(),
}),
})
.expect("payload should serialize");
assert_eq!(payload["aiTask"]["taskId"], json!("aitask_001"));
assert_eq!(
payload["aiTask"]["stages"][0]["stageKind"],
json!("request_model")
);
assert_eq!(payload["aiTextChunk"]["deltaText"], json!(""));
}
}