use serde::Deserialize;
use serde_json::{json, Value};
use crate::transport::api_error::ApiError;
use crate::AppState;
use super::NoParams;
const DEFAULT_LOGS_TAIL_N: usize = 100;
const MAX_LOGS_TAIL_N: usize = trusty_common::log_buffer::DEFAULT_LOG_CAPACITY;
fn default_logs_tail_n() -> usize {
DEFAULT_LOGS_TAIL_N
}
#[derive(Debug, Deserialize)]
pub struct LogsTailParams {
#[serde(default = "default_logs_tail_n")]
pub n: usize,
}
pub async fn logs_tail(state: &AppState, params: LogsTailParams) -> Result<Value, ApiError> {
let n = params.n.clamp(1, MAX_LOGS_TAIL_N);
Ok(json!({
"lines": state.log_buffer.tail(n),
"total": state.log_buffer.len(),
}))
}
pub async fn admin_stop(_state: &AppState, _params: NoParams) -> Result<Value, ApiError> {
tracing::warn!("admin_stop: shutdown requested via memory.admin_stop");
#[cfg(not(test))]
tokio::spawn(async {
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
std::process::exit(0);
});
Ok(json!({ "ok": true, "message": "shutting down" }))
}
#[derive(Debug, Deserialize)]
pub struct RememberAsyncParams {
pub content: String,
#[serde(default)]
pub palace: Option<String>,
#[serde(default)]
pub tags: Option<Vec<String>>,
}
const REMEMBER_MIN_WORDS: usize = 4;
pub async fn remember_async(
state: &AppState,
params: RememberAsyncParams,
) -> Result<Value, ApiError> {
let content = params.content.trim();
if content.is_empty() {
return Err(ApiError::bad_request("content must not be empty"));
}
let word_count = content.split_whitespace().count();
if word_count < REMEMBER_MIN_WORDS {
return Err(ApiError::unprocessable(format!(
"content too short: {word_count} word(s); minimum is {REMEMBER_MIN_WORDS} words"
)));
}
let mut args = serde_json::Map::new();
args.insert("text".to_string(), Value::String(content.to_string()));
if let Some(p) = params
.palace
.clone()
.or_else(|| state.default_palace.clone())
{
args.insert("palace".to_string(), Value::String(p));
}
if let Some(tags) = params.tags.clone() {
args.insert(
"tags".to_string(),
Value::Array(tags.into_iter().map(Value::String).collect()),
);
}
let tool_args = Value::Object(args);
let state_for_task = state.clone();
tokio::spawn(async move {
match crate::tools::dispatch_tool(&state_for_task, "memory_remember", tool_args).await {
Ok(v) => {
tracing::debug!(target: "trusty_memory::remember_async", result = %v, "queued remember succeeded");
}
Err(e) => {
tracing::warn!(
target: "trusty_memory::remember_async",
"queued remember failed: {e:#}"
);
}
}
});
Ok(json!({ "status": "queued" }))
}