|
|
|
@@ -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),
|
|
|
|
|
},
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|