use crate::models::field_names;
use axum::{
Json,
extract::{Path, Query, State},
http::{HeaderMap, StatusCode},
response::IntoResponse,
};
use serde::Deserialize;
use serde_json::json;
use super::AppState;
const SKILLS_TRACE_TARGET: &str = "ai_memory::handlers::skills";
pub async fn skill_register_route(
State(app): State<AppState>,
headers: HeaderMap,
Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_register")
{
return resp;
}
let lock = app.db.lock().await;
let kp = (*app.active_keypair).as_ref();
match crate::mcp::handle_skill_register(&lock.0, &body, kp) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => (StatusCode::BAD_REQUEST, Json(json!({"error": e}))).into_response(),
}
}
#[derive(Deserialize)]
pub struct SkillListQuery {
pub namespace: Option<String>,
pub filter: Option<String>,
}
pub async fn skill_list_route(
State(app): State<AppState>,
headers: HeaderMap,
Query(q): Query<SkillListQuery>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_list") {
return resp;
}
let mut params = json!({});
if let Some(ns) = q.namespace {
params["namespace"] = json!(ns);
}
if let Some(f) = q.filter {
params["filter"] = json!(f);
}
let lock = app.db.lock().await;
match crate::mcp::handle_skill_list(&lock.0, ¶ms) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => {
tracing::error!(
target: SKILLS_TRACE_TARGET,
error = %e,
"skill_list_route: substrate error (sanitized for wire response, #1261)"
);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": crate::errors::msg::INTERNAL_SERVER_ERROR})),
)
.into_response()
}
}
}
pub async fn skill_get_route(
State(app): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_get") {
return resp;
}
let params = json!({"skill_id": id});
let lock = app.db.lock().await;
match crate::mcp::handle_skill_get(&lock.0, ¶ms) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => {
if e.starts_with(crate::errors::msg::SKILL_NOT_FOUND) {
(StatusCode::NOT_FOUND, Json(json!({"error": e}))).into_response()
} else {
tracing::error!(
target: SKILLS_TRACE_TARGET,
error = %e,
"skill_get_route: substrate error (sanitized for wire response, #1261)"
);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": crate::errors::msg::INTERNAL_SERVER_ERROR})),
)
.into_response()
}
}
}
}
#[derive(Deserialize)]
pub struct SkillResourceQuery {
pub path: String,
}
pub async fn skill_resource_route(
State(app): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
Query(q): Query<SkillResourceQuery>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_resource")
{
return resp;
}
let params = json!({
"skill_id": id,
(field_names::RESOURCE_PATH): q.path,
});
let lock = app.db.lock().await;
match crate::mcp::handle_skill_resource(&lock.0, ¶ms) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => {
if e.starts_with("resource not found") {
(StatusCode::NOT_FOUND, Json(json!({"error": e}))).into_response()
} else {
(StatusCode::BAD_REQUEST, Json(json!({"error": e}))).into_response()
}
}
}
}
#[derive(Deserialize)]
pub struct SkillExportBody {
pub target_folder: String,
}
pub async fn skill_export_route(
State(app): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
Json(body): Json<SkillExportBody>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_export") {
return resp;
}
let params = json!({
"skill_id": id,
(field_names::TARGET_FOLDER): body.target_folder,
});
let lock = app.db.lock().await;
let kp = (*app.active_keypair).as_ref();
match crate::mcp::handle_skill_export(&lock.0, ¶ms, kp) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => {
if e.starts_with(crate::errors::msg::SKILL_NOT_FOUND) {
(StatusCode::NOT_FOUND, Json(json!({"error": e}))).into_response()
} else {
(StatusCode::BAD_REQUEST, Json(json!({"error": e}))).into_response()
}
}
}
}
#[derive(Deserialize)]
pub struct SkillPromoteBody {
pub name: String,
pub description: String,
pub parameters_schema: Option<serde_json::Value>,
}
pub async fn skill_promote_route(
State(app): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
Json(body): Json<SkillPromoteBody>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_promote") {
return resp;
}
let mut params = json!({
(field_names::REFLECTION_ID): id,
(field_names::SKILL_NAME): body.name,
(field_names::SKILL_DESCRIPTION): body.description,
});
if let Some(ps) = body.parameters_schema {
params[field_names::PARAMETERS_SCHEMA] = ps;
}
let lock = app.db.lock().await;
let kp = (*app.active_keypair).as_ref();
match crate::mcp::handle_skill_promote_from_reflection(&lock.0, ¶ms, kp) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => {
if e.contains("not found") {
(StatusCode::NOT_FOUND, Json(json!({"error": e}))).into_response()
} else {
(StatusCode::BAD_REQUEST, Json(json!({"error": e}))).into_response()
}
}
}
}
#[derive(Deserialize, Default)]
pub struct SkillComposeBody {
pub budget_tokens: Option<u64>,
}
pub async fn skill_compose_route(
State(app): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
body: Option<Json<SkillComposeBody>>,
) -> impl IntoResponse {
if let Err(resp) = crate::handlers::admin_role::require_admin(&app, &headers, "skill_compose") {
return resp;
}
let Json(body) = body.unwrap_or(Json(SkillComposeBody::default()));
let mut params = json!({"skill_id": id});
if let Some(b) = body.budget_tokens {
params[field_names::BUDGET_TOKENS] = json!(b);
}
let lock = app.db.lock().await;
match crate::mcp::handle_skill_compositional_context(&lock.0, ¶ms) {
Ok(v) => (StatusCode::OK, Json(v)).into_response(),
Err(e) => {
if e.starts_with(crate::errors::msg::SKILL_NOT_FOUND) {
(StatusCode::NOT_FOUND, Json(json!({"error": e}))).into_response()
} else {
tracing::error!(
target: SKILLS_TRACE_TARGET,
error = %e,
"skill_compose_route: substrate error (sanitized for wire response, #1261)"
);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": crate::errors::msg::INTERNAL_SERVER_ERROR})),
)
.into_response()
}
}
}
}