feat(server): refactor handlers, router, state management, and memory tools
This commit is contained in:
1 parent
87ddb01063
commit
a083719cf1
36 files changed
+1899
-597
No files matched your search
+68
-10
@@ -9,6 +9,7 @@ use axum::{
|
||||
routing::{get, post},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
@@ -24,11 +25,78 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
.route("/ws", get(ws_handler))
|
||||
.route("/health", get(health_handler))
|
||||
.route("/ping", get(|| async move { (axum::http::StatusCode::OK, "pong") }))
|
||||
.route(
|
||||
"/metrics",
|
||||
get({
|
||||
let state_clone = app_state.handler.state.clone();
|
||||
move || async move {
|
||||
let graph = state_clone.graph.cache.read().unwrap();
|
||||
let entity_count = graph.entities.len();
|
||||
let relation_count = graph.relations.len();
|
||||
let tasks = state_clone.project.tasks.cache.read().unwrap();
|
||||
let active_tasks = tasks.iter().filter(|t| t.status != "completed" && t.status != "done").count();
|
||||
let adrs = state_clone.code.adrs.cache.read().unwrap();
|
||||
let adr_count = adrs.len();
|
||||
let tech_debts = state_clone.code.tech_debts.cache.read().unwrap();
|
||||
let debt_count = tech_debts.iter().filter(|d| !d.is_resolved).count();
|
||||
|
||||
let metrics = format!(
|
||||
"# HELP mcp_memory_entities_total Total entities in knowledge graph\n\
|
||||
# TYPE mcp_memory_entities_total gauge\n\
|
||||
mcp_memory_entities_total {}\n\
|
||||
# HELP mcp_memory_relations_total Total relations in knowledge graph\n\
|
||||
# TYPE mcp_memory_relations_total gauge\n\
|
||||
mcp_memory_relations_total {}\n\
|
||||
# HELP mcp_memory_active_tasks Active tasks count\n\
|
||||
# TYPE mcp_memory_active_tasks gauge\n\
|
||||
mcp_memory_active_tasks {}\n\
|
||||
# HELP mcp_memory_adrs_total Total ADR decisions\n\
|
||||
# TYPE mcp_memory_adrs_total gauge\n\
|
||||
mcp_memory_adrs_total {}\n\
|
||||
# HELP mcp_memory_unresolved_tech_debt Unresolved tech debt count\n\
|
||||
# TYPE mcp_memory_unresolved_tech_debt gauge\n\
|
||||
mcp_memory_unresolved_tech_debt {}\n",
|
||||
entity_count, relation_count, active_tasks, adr_count, debt_count
|
||||
);
|
||||
([(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4")], metrics)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route("/nvim/telemetry", post(nvim_telemetry_handler))
|
||||
.route("/api/terminal/history", get(crate::api::telemetry::get_terminal_history_handler))
|
||||
.route("/terminal/telemetry", post(crate::api::telemetry::terminal_telemetry_handler))
|
||||
.route("/events/wait", get(crate::api::events::wait_for_event_handler))
|
||||
.route("/events", post(crate::api::events::post_event_handler))
|
||||
.route(
|
||||
"/api/activity",
|
||||
get({
|
||||
let state_clone = app_state.handler.state.clone();
|
||||
move || async move {
|
||||
let activity_json = state_clone
|
||||
.telemetry
|
||||
.recent_activities
|
||||
.read_with(|act| serde_json::to_string(act).unwrap_or_else(|_| "[]".to_string()));
|
||||
([(axum::http::header::CONTENT_TYPE, "application/json")], activity_json)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/activity/stream",
|
||||
get({
|
||||
let state_clone = app_state.handler.state.clone();
|
||||
move || async move {
|
||||
let rx = state_clone.activity_tx.subscribe();
|
||||
let stream = tokio_stream::wrappers::BroadcastStream::new(rx)
|
||||
.filter_map(|msg| match msg {
|
||||
Ok(data) => Some(Ok::<_, std::convert::Infallible>(
|
||||
axum::response::sse::Event::default().data(data),
|
||||
)),
|
||||
Err(_) => None,
|
||||
});
|
||||
axum::response::sse::Sse::new(stream)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route("/gate/verify", get(gate_verify_handler))
|
||||
.route("/gate/set", post(gate_set_handler))
|
||||
.route(
|
||||
@@ -140,16 +208,6 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/activity",
|
||||
get({
|
||||
let state_clone = app_state.handler.state.clone();
|
||||
move || async move {
|
||||
let activities_json = state_clone.telemetry.recent_activities.read_with(|a| serde_json::to_string(a).unwrap_or_else(|_| "[]".to_string()));
|
||||
([(axum::http::header::CONTENT_TYPE, "application/json")], activities_json)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/tech_debts",
|
||||
get({
|
||||
|
||||
+37
-15
@@ -71,6 +71,10 @@ pub async fn nvim_telemetry_handler(
|
||||
let _ = state.handler.state.event_bus_tx.send(event);
|
||||
}
|
||||
|
||||
if let Some(ref f) = payload.file {
|
||||
state.handler.state.record_activity("editor", &format!("Neovim {}: {}", payload.event, f), None);
|
||||
}
|
||||
|
||||
axum::Json(serde_json::json!({"status": "ok"}))
|
||||
}
|
||||
|
||||
@@ -79,20 +83,35 @@ use crate::models::TerminalHistory;
|
||||
pub async fn get_terminal_history_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl axum::response::IntoResponse {
|
||||
let history_json = state.handler.state.telemetry.terminal_history.read_with(|h| serde_json::to_string(h).unwrap_or_else(|_| "[]".to_string()));
|
||||
([(axum::http::header::CONTENT_TYPE, "application/json")], history_json)
|
||||
let history_json = state
|
||||
.handler
|
||||
.state
|
||||
.telemetry
|
||||
.terminal_history
|
||||
.read_with(|h| serde_json::to_string(h).unwrap_or_else(|_| "[]".to_string()));
|
||||
(
|
||||
[(axum::http::header::CONTENT_TYPE, "application/json")],
|
||||
history_json,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn terminal_telemetry_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
axum::Json(payload): axum::Json<TerminalHistory>,
|
||||
) -> impl axum::response::IntoResponse {
|
||||
state.handler.state.telemetry.terminal_history.modify(|history| {
|
||||
history.push_front(payload.clone());
|
||||
if history.len() > 100 {
|
||||
history.pop_back();
|
||||
}
|
||||
});
|
||||
state.handler.state.record_activity("terminal", &format!("Ran command: {}", payload.command), Some(&format!("exit_code: {}, cwd: {}", payload.exit_code, payload.cwd)));
|
||||
|
||||
state
|
||||
.handler
|
||||
.state
|
||||
.telemetry
|
||||
.terminal_history
|
||||
.modify(|history| {
|
||||
history.push_front(payload.clone());
|
||||
if history.len() > 100 {
|
||||
history.pop_back();
|
||||
}
|
||||
});
|
||||
|
||||
let ws_msg = serde_json::json!({
|
||||
"type": "terminal_telemetry",
|
||||
@@ -120,12 +139,12 @@ mod tests {
|
||||
use crate::router::MemoryHandler;
|
||||
use crate::state::MemoryState;
|
||||
use axum::http::Request;
|
||||
use tower::ServiceExt;
|
||||
use axum::routing::get;
|
||||
use axum::routing::post;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use tempfile::tempdir;
|
||||
use axum::routing::post;
|
||||
use axum::routing::get;
|
||||
use tower::ServiceExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_terminal_history() {
|
||||
@@ -158,7 +177,9 @@ mod tests {
|
||||
.method("POST")
|
||||
.uri("/terminal/telemetry")
|
||||
.header("content-type", "application/json")
|
||||
.body(axum::body::Body::from(serde_json::to_string(&payload).unwrap()))
|
||||
.body(axum::body::Body::from(
|
||||
serde_json::to_string(&payload).unwrap(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let response = app.clone().oneshot(req).await.unwrap();
|
||||
@@ -173,11 +194,12 @@ mod tests {
|
||||
|
||||
let response2 = app.oneshot(req2).await.unwrap();
|
||||
assert_eq!(response2.status(), 200);
|
||||
|
||||
let body_bytes = axum::body::to_bytes(response2.into_body(), usize::MAX).await.unwrap();
|
||||
|
||||
let body_bytes = axum::body::to_bytes(response2.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let history: Vec<TerminalHistory> = serde_json::from_slice(&body_bytes).unwrap();
|
||||
assert_eq!(history.len(), 1);
|
||||
assert_eq!(history[0].command, "echo test");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user