- Wire WASM plugin tools into all_tools_with_runtime() behind cfg(feature = "plugins-wasm"), discovering and registering tool-capable plugins from the configured plugins directory at startup. - Add /api/plugins gateway endpoint (cfg-gated) for listing plugin status. - Add mod plugins declaration to main.rs binary crate so crate::plugins resolves when the feature is enabled. - Add unit tests for PluginHost: empty dir, manifest discovery, capability filtering, lookup, and removal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
2.6 KiB
Rust
78 lines
2.6 KiB
Rust
//! Plugin management API routes (requires `plugins-wasm` feature).
|
|
|
|
#[cfg(feature = "plugins-wasm")]
|
|
pub mod plugin_routes {
|
|
use axum::{
|
|
extract::State,
|
|
http::{header, HeaderMap, StatusCode},
|
|
response::{IntoResponse, Json},
|
|
};
|
|
|
|
use super::super::AppState;
|
|
|
|
/// `GET /api/plugins` — list loaded plugins and their status.
|
|
pub async fn list_plugins(
|
|
State(state): State<AppState>,
|
|
headers: HeaderMap,
|
|
) -> impl IntoResponse {
|
|
// Auth check
|
|
if state.pairing.require_pairing() {
|
|
let token = headers
|
|
.get(header::AUTHORIZATION)
|
|
.and_then(|v| v.to_str().ok())
|
|
.and_then(|auth| auth.strip_prefix("Bearer "))
|
|
.unwrap_or("");
|
|
if !state.pairing.is_authenticated(token) {
|
|
return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response();
|
|
}
|
|
}
|
|
|
|
let config = state.config.lock();
|
|
let plugins_enabled = config.plugins.enabled;
|
|
let plugins_dir = config.plugins.plugins_dir.clone();
|
|
drop(config);
|
|
|
|
let plugins: Vec<serde_json::Value> = if plugins_enabled {
|
|
let plugin_path = if plugins_dir.starts_with("~/") {
|
|
directories::UserDirs::new()
|
|
.map(|u| u.home_dir().join(&plugins_dir[2..]))
|
|
.unwrap_or_else(|| std::path::PathBuf::from(&plugins_dir))
|
|
} else {
|
|
std::path::PathBuf::from(&plugins_dir)
|
|
};
|
|
|
|
if plugin_path.exists() {
|
|
match crate::plugins::host::PluginHost::new(
|
|
plugin_path.parent().unwrap_or(&plugin_path),
|
|
) {
|
|
Ok(host) => host
|
|
.list_plugins()
|
|
.into_iter()
|
|
.map(|p| {
|
|
serde_json::json!({
|
|
"name": p.name,
|
|
"version": p.version,
|
|
"description": p.description,
|
|
"capabilities": p.capabilities,
|
|
"loaded": p.loaded,
|
|
})
|
|
})
|
|
.collect(),
|
|
Err(_) => vec![],
|
|
}
|
|
} else {
|
|
vec![]
|
|
}
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
Json(serde_json::json!({
|
|
"plugins_enabled": plugins_enabled,
|
|
"plugins_dir": plugins_dir,
|
|
"plugins": plugins,
|
|
}))
|
|
.into_response()
|
|
}
|
|
}
|