use axum::{
extract::{Path, State},
http::StatusCode,
Json,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::server::AppState;
#[derive(Debug, Serialize)]
pub struct PersonaSummary {
id: String,
name: String,
role: String,
description: String,
enabled: bool,
personality_traits: Vec<String>,
}
pub async fn handle_personas_list(state: State<Arc<AppState>>) -> Json<Vec<PersonaSummary>> {
let personas = state.kernel.persona.list();
Json(
personas
.into_iter()
.map(|p| PersonaSummary {
id: p.id,
name: p.name,
role: p.role,
description: p.description,
enabled: p.enabled,
personality_traits: p.personality_traits,
})
.collect(),
)
}
pub async fn handle_persona_get(
state: State<Arc<AppState>>,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>, StatusCode> {
match state.kernel.persona.get(&id) {
Some(p) => Ok(Json(serde_json::json!({
"id": p.id,
"name": p.name,
"role": p.role,
"description": p.description,
"system_prompt": p.system_prompt,
"enabled": p.enabled,
"model": p.model,
"personality_traits": p.personality_traits,
}))),
None => Err(StatusCode::NOT_FOUND),
}
}
#[derive(Debug, Deserialize)]
pub struct PersonaCreateRequest {
name: String,
role: String,
description: String,
#[serde(default)]
system_prompt: String,
#[serde(default = "default_true")]
enabled: bool,
#[serde(default)]
model: Option<String>,
#[serde(default)]
personality_traits: Vec<String>,
}
fn default_true() -> bool {
true
}
pub async fn handle_persona_create(
state: State<Arc<AppState>>,
Json(body): Json<PersonaCreateRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
use oxios_kernel::Persona;
let persona = Persona {
id: uuid::Uuid::new_v4().to_string(),
name: body.name,
role: body.role,
description: body.description,
system_prompt: body.system_prompt,
enabled: body.enabled,
model: body.model,
personality_traits: body.personality_traits,
};
state.kernel.persona.create(persona.clone());
tracing::info!(persona = %persona.name, "Persona created via API");
Ok(Json(serde_json::json!({
"status": "created",
"id": persona.id,
"name": persona.name,
})))
}
#[derive(Debug, Deserialize)]
pub struct PersonaUpdateRequest {
name: Option<String>,
role: Option<String>,
description: Option<String>,
system_prompt: Option<String>,
enabled: Option<bool>,
model: Option<String>,
personality_traits: Option<Vec<String>>,
}
pub async fn handle_persona_update(
state: State<Arc<AppState>>,
Path(id): Path<String>,
Json(body): Json<PersonaUpdateRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
use oxios_kernel::Persona;
let existing = state
.kernel
.persona
.get(&id)
.ok_or_else(|| (StatusCode::NOT_FOUND, format!("Persona '{}' not found", id)))?;
let updated = Persona {
id: existing.id,
name: body.name.unwrap_or(existing.name),
role: body.role.unwrap_or(existing.role),
description: body.description.unwrap_or(existing.description),
system_prompt: body.system_prompt.unwrap_or(existing.system_prompt),
enabled: body.enabled.unwrap_or(existing.enabled),
model: body.model.or(existing.model),
personality_traits: body
.personality_traits
.unwrap_or(existing.personality_traits),
};
state
.kernel
.persona
.update(&id, updated.clone())
.map_err(|e: anyhow::Error| (StatusCode::BAD_REQUEST, e.to_string()))?;
tracing::info!(persona_id = %id, "Persona updated via API");
Ok(Json(serde_json::json!({
"status": "updated",
"id": id,
})))
}
pub async fn handle_persona_delete(
state: State<Arc<AppState>>,
Path(id): Path<String>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
if state.kernel.persona.count() <= 1 {
return Err((
StatusCode::BAD_REQUEST,
"Cannot delete the last persona".to_string(),
));
}
state
.kernel
.persona
.delete(&id)
.map_err(|e: anyhow::Error| (StatusCode::NOT_FOUND, e.to_string()))?;
if let Some(active) = state.kernel.persona.active() {
if active.id == id {
if let Some(next) = state.kernel.persona.list_enabled().into_iter().next() {
let _ = state.kernel.persona.set_active(&next.id);
}
}
}
tracing::info!(persona_id = %id, "Persona deleted via API");
Ok(Json(serde_json::json!({
"status": "deleted",
"id": id,
})))
}
pub async fn handle_persona_active_get(state: State<Arc<AppState>>) -> Json<serde_json::Value> {
match state.kernel.persona.active() {
Some(p) => Json(serde_json::json!({
"id": p.id,
"name": p.name,
"role": p.role,
"description": p.description,
"system_prompt": p.system_prompt,
"enabled": p.enabled,
})),
None => Json(serde_json::json!({
"active": false,
"message": "No active persona set"
})),
}
}
#[derive(Debug, Deserialize)]
pub struct PersonaActiveRequest {
id: String,
}
pub async fn handle_persona_active_set(
state: State<Arc<AppState>>,
Json(body): Json<PersonaActiveRequest>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
state
.kernel
.persona
.set_active(&body.id)
.map_err(|e: anyhow::Error| (StatusCode::BAD_REQUEST, e.to_string()))?;
let persona = state.kernel.persona.active();
Ok(Json(serde_json::json!({
"status": "active",
"id": body.id,
"name": persona.map(|p| p.name).unwrap_or_default(),
})))
}