migrate the api usage

This commit is contained in:
2026-07-10 09:10:33 +08:00
parent db2e8cca8d
commit a2babca6a6
5 changed files with 187 additions and 9 deletions
@@ -6,23 +6,39 @@ use module_editor_agent::agent::agent_builder::AgentBuilder;
use module_editor_agent::agent::error::PromptError;
use module_editor_agent::agent::memory::VecMemory;
use module_editor_agent::agent::run::PromptOutput;
use module_editor_agent::derive_conversation_title;
use module_editor_agent::{
derive_conversation_title, editor_agent_messages_object_key,
EDITOR_AGENT_CONVERSATION_ID_PREFIX, EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE,
};
use platform_llm::LlmMessage;
use serde::Serialize;
use serde_json::{Value, json};
use shared_contracts::editor_agent::{
EditorAgentConversationMessagesDocument, EditorAgentMessage, EditorAgentMessageRole,
CreateEditorAgentConversationRequest, EditorAgentConversationListResponse,
EditorAgentConversationMessagesDocument, EditorAgentConversationResponse,
EditorAgentConversationSummary, EditorAgentMessage, EditorAgentMessageRole,
EditorAgentToolCall, EditorAgentToolCallStatus, StreamEditorAgentMessageRequest,
};
use spacetime_client::EditorAgentConversationTouchRecordInput;
use spacetime_client::{
EditorAgentConversationCreateRecordInput, EditorAgentConversationDeleteRecordInput,
EditorAgentConversationRecord, EditorAgentConversationTouchRecordInput,
};
use crate::auth::AuthenticatedAccessToken;
use crate::editor_agent::agent::LlmChatAgentBuilder;
use crate::editor_agent::editor_tools::edit_image::EditImageTool;
use crate::editor_agent::utils::{EditorAgentMessageResponse, ImageId, ImageMetadata, now_rfc3339, require_editor_agent_sidebar_enabled, write_messages_document, normalize_editor_agent_attachments, read_messages_document};
use crate::editor_project::current_utc_micros;
use crate::editor_agent::utils::{
conversation_detail_from_record, conversation_summary_from_record, empty_messages_document,
ensure_editor_project_access, EditorAgentMessageResponse, ImageId, ImageMetadata,
normalize_editor_agent_attachments, now_rfc3339, read_messages_document,
require_editor_agent_sidebar_enabled, write_messages_document,
};
use crate::editor_project::{current_utc_micros, map_editor_project_error};
use crate::http_error::AppError;
use crate::request_context::RequestContext;
use crate::api_response::json_success_body;
use crate::state::AppState;
use shared_kernel::{build_prefixed_uuid_id, normalize_optional_string};
pub async fn editor_agent_message(
State(state): State<AppState>,
@@ -72,6 +88,7 @@ pub async fn editor_agent_message(
// Save user message to document
let was_empty = document.messages.is_empty();
let user_now = now_rfc3339();
// TODO tell agent info about attachments
let user_message = EditorAgentMessage {
id: document.messages.len(),
role: EditorAgentMessageRole::User,
@@ -205,3 +222,140 @@ fn build_tool_context(document: &EditorAgentConversationMessagesDocument) -> Val
json!({ "images": images })
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct EditorAgentConversationDeleteResponse {
deleted_conversation_id: String,
conversation: EditorAgentConversationSummary,
}
pub async fn list_editor_agent_conversations(
State(state): State<AppState>,
Path(project_id): Path<String>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, AppError> {
let owner_user_id = authenticated.claims().user_id().to_string();
require_editor_agent_sidebar_enabled(&state, owner_user_id.as_str()).await?;
ensure_editor_project_access(&state, project_id.as_str(), owner_user_id.as_str()).await?;
let conversations = state
.spacetime_client()
.list_editor_agent_conversations(project_id, owner_user_id)
.await
.map_err(map_editor_project_error)?
.into_iter()
.map(conversation_summary_from_record)
.collect();
Ok(json_success_body(
Some(&request_context),
EditorAgentConversationListResponse { conversations },
))
}
pub async fn create_editor_agent_conversation(
State(state): State<AppState>,
Path(project_id): Path<String>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
Json(payload): Json<CreateEditorAgentConversationRequest>,
) -> Result<Json<Value>, AppError> {
let owner_user_id = authenticated.claims().user_id().to_string();
require_editor_agent_sidebar_enabled(&state, owner_user_id.as_str()).await?;
ensure_editor_project_access(&state, project_id.as_str(), owner_user_id.as_str()).await?;
let conversation_id = build_prefixed_uuid_id(EDITOR_AGENT_CONVERSATION_ID_PREFIX);
let messages_object_key = editor_agent_messages_object_key(conversation_id.as_str());
let title = normalize_optional_string(payload.title)
.unwrap_or_else(|| EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE.to_string());
let now_micros = current_utc_micros();
let seed_record = EditorAgentConversationRecord {
conversation_id: conversation_id.clone(),
project_id: project_id.clone(),
owner_user_id: owner_user_id.clone(),
title: title.clone(),
messages_object_key: messages_object_key.clone(),
deleted: false,
created_at: now_rfc3339(),
updated_at: now_rfc3339(),
updated_at_micros: now_micros,
};
write_messages_document(
&state,
&seed_record,
&empty_messages_document(conversation_id.as_str()),
)
.await?;
let conversation = state
.spacetime_client()
.create_editor_agent_conversation(EditorAgentConversationCreateRecordInput {
conversation_id,
project_id,
owner_user_id,
title,
messages_object_key,
created_at_micros: now_micros,
})
.await
.map_err(map_editor_project_error)?;
let document = read_messages_document(&state, &conversation).await?;
Ok(json_success_body(
Some(&request_context),
EditorAgentConversationResponse {
conversation: conversation_detail_from_record(conversation, document.messages),
},
))
}
pub async fn get_editor_agent_conversation(
State(state): State<AppState>,
Path(conversation_id): Path<String>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, AppError> {
let owner_user_id = authenticated.claims().user_id().to_string();
require_editor_agent_sidebar_enabled(&state, owner_user_id.as_str()).await?;
let conversation = state
.spacetime_client()
.get_editor_agent_conversation(conversation_id, owner_user_id)
.await
.map_err(map_editor_project_error)?;
let document = read_messages_document(&state, &conversation).await?;
Ok(json_success_body(
Some(&request_context),
EditorAgentConversationResponse {
conversation: conversation_detail_from_record(conversation, document.messages),
},
))
}
pub async fn delete_editor_agent_conversation(
State(state): State<AppState>,
Path(conversation_id): Path<String>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Result<Json<Value>, AppError> {
let owner_user_id = authenticated.claims().user_id().to_string();
require_editor_agent_sidebar_enabled(&state, owner_user_id.as_str()).await?;
let conversation = state
.spacetime_client()
.delete_editor_agent_conversation(EditorAgentConversationDeleteRecordInput {
conversation_id,
owner_user_id,
updated_at_micros: current_utc_micros(),
})
.await
.map_err(map_editor_project_error)?;
Ok(json_success_body(
Some(&request_context),
EditorAgentConversationDeleteResponse {
deleted_conversation_id: conversation.conversation_id.clone(),
conversation: conversation_summary_from_record(conversation),
},
))
}
@@ -181,6 +181,25 @@ impl EditImageTool {
Ok(())
}
// TODO should share with format in run.rs
fn format_execute_message(&self, args: Self::Args, result: EditorImageEditResult) -> String {
let name = Self::NAME.to_string();
let arg_json = args.to_string();
let image_id: ImageId = ImageId {
id: result
.object_key
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.map(|value| value.trim_start_matches('/').to_string())
.unwrap_or_else(|| result.image_src.clone()),
};
format!(
"[tool_call:{name}] args: {arg_json} output: edited result saved as image: {image_id}"
)
}
pub async fn execute(
&self,
state: &AppState,
@@ -2,3 +2,8 @@ mod editor_tools;
mod utils;
pub mod api;
mod agent;
pub use api::{
create_editor_agent_conversation, delete_editor_agent_conversation,
get_editor_agent_conversation, list_editor_agent_conversations,
};
+1 -1
View File
@@ -36,7 +36,6 @@ mod custom_world_asset_prompts;
mod custom_world_foundation_draft;
mod custom_world_result_prompts;
mod custom_world_rpg_draft_prompts;
mod editor_agent;
mod editor_generation_config;
mod editor_generation_queue;
mod editor_green_screen;
@@ -110,6 +109,7 @@ mod wechat;
mod wooden_fish;
mod work_author;
mod work_play_tracking;
mod editor_agent;
use shared_logging::{OtelConfig, init_tracing};
use socket2::{Domain, Protocol, Socket, Type};
@@ -7,10 +7,10 @@ use axum::{
use crate::{
auth::require_bearer_auth,
editor_agent::api::editor_agent_message,
editor_agent::{
create_editor_agent_conversation, delete_editor_agent_conversation,
get_editor_agent_conversation, list_editor_agent_conversations,
stream_editor_agent_message,
},
editor_project::{
create_editor_asset, create_editor_asset_folder, create_editor_project,
@@ -98,8 +98,8 @@ pub fn router(state: AppState) -> Router<AppState> {
)),
)
.route(
"/api/editor/agent-conversations/{conversation_id}/messages/stream",
post(stream_editor_agent_message)
"/api/editor/agent-conversations/{conversation_id}/messages",
post(editor_agent_message)
.layer(DefaultBodyLimit::max(EDITOR_AGENT_MESSAGE_BODY_LIMIT_BYTES))
.route_layer(middleware::from_fn_with_state(
state.clone(),