Refactor and clean up code across multiple modules
- Simplified token type assignment in OAuth service. - Removed unused session_lock module and re-exported Session from zesdex_entities. - Cleaned up session entity by removing unnecessary comments and code. - Consolidated session handling in HTTP handlers for better readability. - Improved formatting and readability in OAuth repository tests. - Enhanced session lock repository with clearer match statements. - Streamlined session repository error handling. - Refined RNG tests for better clarity. - Adjusted module visibility and organization in lib.rs. - Updated IPC client and connection code for better error handling and clarity. - Improved frame handling in IPC for better readability. - Organized module imports and added test utilities for IPC. - Enhanced database connection error handling. - Simplified JWT token creation error handling. - Improved password verification error handling. - Cleaned up state management code for better readability. - Refactored middleware for session authentication and rate limiting. - Simplified clipboard utility for better error handling. - Enhanced logging initialization for better error reporting. - Improved pagination utility with clearer method annotations. - Cleaned up sanitization functions for filenames and paths. - Enhanced slug generation functions for better clarity and usability.
This commit is contained in:
@@ -47,16 +47,22 @@ impl<R: ConversationRepository> ConversationService for ConversationServiceImpl<
|
||||
|
||||
fn save_conversation(&self, conv: &Conversation) -> Result<()> {
|
||||
let dir = self.session_dir(&conv.session_id);
|
||||
self.repo
|
||||
.save(&dir, conv)
|
||||
.with_context(|| format!("failed to save conversation for session '{}'", conv.session_id))
|
||||
self.repo.save(&dir, conv).with_context(|| {
|
||||
format!(
|
||||
"failed to save conversation for session '{}'",
|
||||
conv.session_id
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn add_message(&self, conv: &mut Conversation, msg: ChatMessage) -> Result<()> {
|
||||
conv.push(msg);
|
||||
let dir = self.session_dir(&conv.session_id);
|
||||
self.repo
|
||||
.save(&dir, conv)
|
||||
.with_context(|| format!("failed to persist conversation after adding message for session '{}'", conv.session_id))
|
||||
self.repo.save(&dir, conv).with_context(|| {
|
||||
format!(
|
||||
"failed to persist conversation after adding message for session '{}'",
|
||||
conv.session_id
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,11 @@ pub struct SettingsServiceImpl<S, C> {
|
||||
|
||||
impl<S: SettingsRepository, C: AppConfigRepository> SettingsServiceImpl<S, C> {
|
||||
/// Create a new service with the given repositories and base directory.
|
||||
pub fn new(settings_repo: S, app_config_repo: C, base_dir: impl Into<std::path::PathBuf>) -> Self {
|
||||
pub fn new(
|
||||
settings_repo: S,
|
||||
app_config_repo: C,
|
||||
base_dir: impl Into<std::path::PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
settings_repo,
|
||||
app_config_repo,
|
||||
@@ -46,7 +50,9 @@ impl<S: SettingsRepository, C: AppConfigRepository> SettingsService for Settings
|
||||
|
||||
fn update_provider(&self, name: &str, config: &ProviderConfig) -> Result<()> {
|
||||
let mut app_config: AppConfig = self.app_config_repo.load(&self.base_dir)?;
|
||||
app_config.providers.insert(name.to_string(), config.clone());
|
||||
app_config
|
||||
.providers
|
||||
.insert(name.to_string(), config.clone());
|
||||
self.app_config_repo.save(&self.base_dir, &app_config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,6 @@
|
||||
//! Pure Conversation entity — in-memory message history plus system prompt
|
||||
//! and LLM generation parameters.
|
||||
//! Pure Conversation entity
|
||||
//!
|
||||
//! # Architecture
|
||||
//! This is a pure data structure with **no I/O logic**. Load/save
|
||||
//! responsibilities live in [`ConversationRepository`](super::repository::ConversationRepository).
|
||||
|
||||
#![allow(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_possible_wrap
|
||||
)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
//! Re-exported from zesdex_entities for consistency.
|
||||
|
||||
pub use zesdex_entities::seaorm::common::message::{ChatMessage, Role};
|
||||
|
||||
/// A single conversation's message history and generation settings.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Conversation {
|
||||
pub messages: Vec<ChatMessage>,
|
||||
pub system_prompt: String,
|
||||
pub session_id: String,
|
||||
pub model: String,
|
||||
pub max_tokens: Option<u32>,
|
||||
pub temperature: Option<f32>,
|
||||
}
|
||||
|
||||
impl Conversation {
|
||||
/// Create an empty conversation with the given system prompt and
|
||||
/// session id, using default model / token / temperature settings.
|
||||
pub fn new(system_prompt: String, session_id: String) -> Self {
|
||||
Self {
|
||||
messages: Vec::new(),
|
||||
system_prompt,
|
||||
session_id,
|
||||
model: "anthropic/claude-opus-4-8".to_string(),
|
||||
max_tokens: None,
|
||||
temperature: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a message to the conversation history.
|
||||
pub fn push(&mut self, msg: ChatMessage) {
|
||||
self.messages.push(msg);
|
||||
}
|
||||
|
||||
/// Replace the system prompt and strip any prior `System`-role messages
|
||||
/// from history.
|
||||
pub fn rebuild_system(&mut self, new_prompt: String) {
|
||||
self.system_prompt = new_prompt;
|
||||
self.messages.retain(|m| !matches!(m.role, Role::System));
|
||||
}
|
||||
|
||||
/// Build the message list to send to the LLM API, with the system
|
||||
/// prompt prepended as the first message.
|
||||
pub fn to_api_messages(&self) -> Vec<ChatMessage> {
|
||||
let mut msgs = Vec::with_capacity(self.messages.len() + 1);
|
||||
msgs.push(ChatMessage::system(&self.system_prompt));
|
||||
msgs.extend(self.messages.iter().cloned());
|
||||
msgs
|
||||
}
|
||||
|
||||
/// Number of messages in the conversation history (excluding the
|
||||
/// synthesized system message).
|
||||
pub fn len(&self) -> usize {
|
||||
self.messages.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the conversation has no messages.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.messages.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn chat_message_is_the_canonical_entities_type() {
|
||||
let canonical = zesdex_entities::seaorm::common::message::ChatMessage::user("hi");
|
||||
let via_cms: ChatMessage = canonical;
|
||||
assert_eq!(via_cms.content.as_deref(), Some("hi"));
|
||||
}
|
||||
}
|
||||
pub use zesdex_entities::seaorm::common::conversation::Conversation;
|
||||
|
||||
@@ -67,7 +67,13 @@ pub trait MemoryRepository {
|
||||
/// caller-supplied key (e.g. a tool-call id) within a session.
|
||||
pub trait RewindBlobRepository {
|
||||
/// Store (or overwrite) a blob under `blob_key` for this session.
|
||||
fn store_blob(&self, session_dir: &Path, blob_key: &str, data: &[u8], mime_type: Option<&str>) -> anyhow::Result<()>;
|
||||
fn store_blob(
|
||||
&self,
|
||||
session_dir: &Path,
|
||||
blob_key: &str,
|
||||
data: &[u8],
|
||||
mime_type: Option<&str>,
|
||||
) -> anyhow::Result<()>;
|
||||
|
||||
/// Retrieve a blob's bytes by key, or `None` if not found.
|
||||
fn retrieve_blob(&self, session_dir: &Path, blob_key: &str) -> anyhow::Result<Option<Vec<u8>>>;
|
||||
|
||||
@@ -25,7 +25,8 @@ pub trait SettingsService {
|
||||
fn save_settings(&self, settings: &Settings) -> Result<()>;
|
||||
|
||||
/// Update the provider configuration (name and details).
|
||||
fn update_provider(&self, name: &str, config: &super::app_config::ProviderConfig) -> Result<()>;
|
||||
fn update_provider(&self, name: &str, config: &super::app_config::ProviderConfig)
|
||||
-> Result<()>;
|
||||
}
|
||||
|
||||
/// Conversation management use cases.
|
||||
|
||||
@@ -18,17 +18,13 @@ use crate::domain::memory::Memory;
|
||||
use crate::domain::service::{MemoryService, SettingsService};
|
||||
use crate::domain::settings::Settings;
|
||||
|
||||
use super::dto::{
|
||||
MemoryCreateRequest, MemoryResponse, SettingsResponse, SettingsUpdateRequest,
|
||||
};
|
||||
use super::dto::{MemoryCreateRequest, MemoryResponse, SettingsResponse, SettingsUpdateRequest};
|
||||
|
||||
/// Handle `GET /settings`
|
||||
///
|
||||
/// Returns the current settings as a `SettingsResponse`.
|
||||
pub fn handle_get_settings<S: SettingsService>(service: &S) -> Result<SettingsResponse> {
|
||||
let settings = service
|
||||
.load_settings()
|
||||
.context("failed to load settings")?;
|
||||
let settings = service.load_settings().context("failed to load settings")?;
|
||||
Ok(SettingsResponse::from(settings))
|
||||
}
|
||||
|
||||
@@ -115,9 +111,7 @@ pub fn handle_update_settings<S: SettingsService>(
|
||||
///
|
||||
/// Lists all memory slugs, then loads each memory to return full responses.
|
||||
pub fn handle_list_memories<M: MemoryService>(service: &M) -> Result<Vec<MemoryResponse>> {
|
||||
let slugs = service
|
||||
.list_memories()
|
||||
.context("failed to list memories")?;
|
||||
let slugs = service.list_memories().context("failed to list memories")?;
|
||||
|
||||
// We can't load individual memories without a load_memory method on the
|
||||
// service. For now, list returns summary info; callers who need full
|
||||
|
||||
@@ -135,8 +135,8 @@ impl AppConfigRepository for JsonAppConfigRepository {
|
||||
.with_context(|| format!("failed to create base dir '{}'", base_dir.display()))?;
|
||||
let path = base_dir.join("app_config.json");
|
||||
let tmp = base_dir.join("app_config.json.tmp");
|
||||
let json = serde_json::to_string_pretty(config)
|
||||
.context("failed to serialize app config")?;
|
||||
let json =
|
||||
serde_json::to_string_pretty(config).context("failed to serialize app config")?;
|
||||
{
|
||||
let mut f = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
@@ -147,8 +147,13 @@ impl AppConfigRepository for JsonAppConfigRepository {
|
||||
f.write_all(json.as_bytes())?;
|
||||
f.sync_all()?;
|
||||
}
|
||||
std::fs::rename(&tmp, &path)
|
||||
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
|
||||
std::fs::rename(&tmp, &path).with_context(|| {
|
||||
format!(
|
||||
"failed to rename '{}' -> '{}'",
|
||||
tmp.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Ok(d) = std::fs::File::open(parent) {
|
||||
let _ = d.sync_all();
|
||||
|
||||
@@ -57,8 +57,13 @@ impl ConversationRepository for JsonConversationRepository {
|
||||
f.write_all(json.as_bytes())?;
|
||||
f.sync_all()?;
|
||||
}
|
||||
std::fs::rename(&tmp, &path)
|
||||
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
|
||||
std::fs::rename(&tmp, &path).with_context(|| {
|
||||
format!(
|
||||
"failed to rename '{}' -> '{}'",
|
||||
tmp.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Ok(d) = std::fs::File::open(parent) {
|
||||
let _ = d.sync_all();
|
||||
|
||||
@@ -74,9 +74,8 @@ impl EditLogRepository for JsonlEditLogRepository {
|
||||
|
||||
fn append(&self, session_dir: &Path, log: &mut EditLog, entry: EditLogEntry) -> Result<()> {
|
||||
let path = session_dir.join("edits.jsonl");
|
||||
let line = serde_json::to_string(&entry)
|
||||
.context("failed to serialize edit log entry")?
|
||||
+ "\n";
|
||||
let line =
|
||||
serde_json::to_string(&entry).context("failed to serialize edit log entry")? + "\n";
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.with_context(|| format!("failed to create session dir '{}'", parent.display()))?;
|
||||
@@ -89,8 +88,7 @@ impl EditLogRepository for JsonlEditLogRepository {
|
||||
.with_context(|| format!("failed to open edits.jsonl at '{}'", path.display()))?;
|
||||
file.write_all(line.as_bytes())
|
||||
.context("failed to write edit log entry")?;
|
||||
file.sync_all()
|
||||
.context("failed to fsync edit log")?;
|
||||
file.sync_all().context("failed to fsync edit log")?;
|
||||
}
|
||||
log.entries.push(entry);
|
||||
// Enforce in-memory cap
|
||||
|
||||
@@ -84,10 +84,7 @@ impl MarkdownMemoryRepository {
|
||||
.lines()
|
||||
.filter_map(|l| {
|
||||
let mut it = l.splitn(2, ':');
|
||||
Some((
|
||||
it.next()?.trim().to_string(),
|
||||
it.next()?.trim().to_string(),
|
||||
))
|
||||
Some((it.next()?.trim().to_string(), it.next()?.trim().to_string()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -155,7 +152,8 @@ impl MemoryRepository for MarkdownMemoryRepository {
|
||||
if name == "MEMORY.md" {
|
||||
return None;
|
||||
}
|
||||
name.strip_suffix(".md").map(std::string::ToString::to_string)
|
||||
name.strip_suffix(".md")
|
||||
.map(std::string::ToString::to_string)
|
||||
})
|
||||
.collect();
|
||||
Ok(slugs)
|
||||
@@ -190,8 +188,13 @@ impl MemoryRepository for MarkdownMemoryRepository {
|
||||
f.write_all(content.as_bytes())?;
|
||||
f.sync_all()?;
|
||||
}
|
||||
std::fs::rename(&tmp, &path)
|
||||
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
|
||||
std::fs::rename(&tmp, &path).with_context(|| {
|
||||
format!(
|
||||
"failed to rename '{}' -> '{}'",
|
||||
tmp.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(p) = path.parent() {
|
||||
if let Ok(d) = std::fs::File::open(p) {
|
||||
let _ = d.sync_all();
|
||||
@@ -204,11 +207,15 @@ impl MemoryRepository for MarkdownMemoryRepository {
|
||||
fn delete(&self, memory_dir: &Path, name: &str) -> Result<()> {
|
||||
let path = Memory::path(memory_dir, name);
|
||||
if path.exists() {
|
||||
std::fs::remove_file(&path)
|
||||
.with_context(|| format!("failed to delete memory '{name}' at '{}'", path.display()))?;
|
||||
std::fs::remove_file(&path).with_context(|| {
|
||||
format!("failed to delete memory '{name}' at '{}'", path.display())
|
||||
})?;
|
||||
tracing::debug!("memory deleted: '{}'", path.display());
|
||||
} else {
|
||||
tracing::warn!("memory '{name}' not found at '{}', skipping delete", path.display());
|
||||
tracing::warn!(
|
||||
"memory '{name}' not found at '{}', skipping delete",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -128,10 +128,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn tmp_dir() -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"zesdex-cms-blob-test-{}",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("zesdex-cms-blob-test-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
@@ -179,10 +177,7 @@ mod tests {
|
||||
vec!["k".to_string()],
|
||||
"key must appear exactly once even after being overwritten"
|
||||
);
|
||||
assert_eq!(
|
||||
repo.retrieve_blob(&dir, "k").unwrap(),
|
||||
Some(b"v2".to_vec())
|
||||
);
|
||||
assert_eq!(repo.retrieve_blob(&dir, "k").unwrap(), Some(b"v2".to_vec()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ impl SettingsRepository for JsonSettingsRepository {
|
||||
.with_context(|| format!("failed to create base dir '{}'", base_dir.display()))?;
|
||||
let path = base_dir.join("settings.json");
|
||||
let tmp = base_dir.join("settings.json.tmp");
|
||||
let json = serde_json::to_string_pretty(settings)
|
||||
.context("failed to serialize settings")?;
|
||||
let json =
|
||||
serde_json::to_string_pretty(settings).context("failed to serialize settings")?;
|
||||
{
|
||||
let mut f = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
@@ -69,8 +69,13 @@ impl SettingsRepository for JsonSettingsRepository {
|
||||
f.write_all(json.as_bytes())?;
|
||||
f.sync_all()?;
|
||||
}
|
||||
std::fs::rename(&tmp, &path)
|
||||
.with_context(|| format!("failed to rename '{}' -> '{}'", tmp.display(), path.display()))?;
|
||||
std::fs::rename(&tmp, &path).with_context(|| {
|
||||
format!(
|
||||
"failed to rename '{}' -> '{}'",
|
||||
tmp.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Ok(d) = std::fs::File::open(parent) {
|
||||
let _ = d.sync_all();
|
||||
@@ -87,7 +92,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn load_defaults_hive_mind_timeout_when_field_missing_from_old_settings_json() {
|
||||
let dir = std::env::temp_dir().join(format!("zesdex-cms-settings-test-{}", uuid::Uuid::new_v4()));
|
||||
let dir =
|
||||
std::env::temp_dir().join(format!("zesdex-cms-settings-test-{}", uuid::Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
// Simulate a settings.json written before `hive_mind_node_timeout_ms` existed.
|
||||
std::fs::write(
|
||||
@@ -96,7 +102,9 @@ mod tests {
|
||||
).unwrap();
|
||||
|
||||
let repo = JsonSettingsRepository::new();
|
||||
let settings = repo.load(&dir).expect("load must not fail on a pre-existing settings.json missing the new field");
|
||||
let settings = repo
|
||||
.load(&dir)
|
||||
.expect("load must not fail on a pre-existing settings.json missing the new field");
|
||||
assert_eq!(settings.hive_mind_node_timeout_ms, 600_000);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
clippy::cast_possible_wrap
|
||||
)]
|
||||
|
||||
pub mod domain;
|
||||
pub mod application;
|
||||
pub mod domain;
|
||||
pub mod infrastructure;
|
||||
|
||||
Reference in New Issue
Block a user