rename crates/ → vessels/ — El's word for buildable units

Per the consolidation onto El: 'crates' is the Rust word, 'vessel' is
El's (per spec/language.md §15). The directory rename is the structural
marker that this slot holds an El buildable unit, even if its current
contents are still Rust pending port.

Mechanical: git mv crates vessels, sed workspace members and any path
dependencies, update CI workflow paths, update README references.
Cross-repo path dependencies (`../foo/crates/bar`) updated workspace-
wide so cargo metadata still resolves where the Rust still builds.
This commit is contained in:
Will Anderson
2026-04-30 15:34:20 -05:00
parent c16b6ed602
commit be013d2b42
34 changed files with 5 additions and 5 deletions
+135
View File
@@ -0,0 +1,135 @@
//! LSP API endpoints — completions, hover, errors, activate-preview.
use axum::{
extract::{Query, State},
http::StatusCode,
Json,
};
use serde::{Deserialize, Serialize};
use el_lsp::{Completion, Diagnostic, HoverInfo, LanguageServer};
use crate::AppState;
type ApiResult<T> = Result<Json<T>, (StatusCode, Json<serde_json::Value>)>;
// ── Query types ───────────────────────────────────────────────────────────────
#[derive(Debug, Deserialize)]
pub struct SourceQuery {
pub source: String,
pub pos: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub struct ActivatePreviewQuery {
pub type_name: String,
pub query: String,
pub source: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ActivatePreviewResponse {
pub count: usize,
pub nodes: Vec<ActivateNode>,
pub connected: bool,
}
#[derive(Debug, Serialize)]
pub struct ActivateNode {
pub id: String,
pub label: String,
pub score: f64,
}
// ── Handlers ──────────────────────────────────────────────────────────────────
/// GET /api/lsp/complete?source=...&pos=...
pub async fn complete(
Query(q): Query<SourceQuery>,
) -> ApiResult<Vec<Completion>> {
let lsp = LanguageServer::new();
let pos = q.pos.unwrap_or(0);
let completions = lsp.complete(&q.source, pos);
Ok(Json(completions))
}
/// GET /api/lsp/hover?source=...&pos=...
pub async fn hover(
Query(q): Query<SourceQuery>,
) -> ApiResult<Option<HoverInfo>> {
let lsp = LanguageServer::new();
let pos = q.pos.unwrap_or(0);
let info = lsp.hover(&q.source, pos);
Ok(Json(info))
}
/// GET /api/lsp/errors?source=...
pub async fn errors(
Query(q): Query<SourceQuery>,
) -> ApiResult<Vec<Diagnostic>> {
let lsp = LanguageServer::new();
let diags = lsp.diagnostics(&q.source);
Ok(Json(diags))
}
/// GET /api/lsp/activate-preview?type_name=...&query=...&source=...
///
/// Returns a live preview of what nodes would activate for the given
/// `activate TypeName where "query"` expression.
pub async fn activate_preview(
State(state): State<AppState>,
Query(q): Query<ActivatePreviewQuery>,
) -> ApiResult<ActivatePreviewResponse> {
let engram_url = &state.config.engram_url;
// Try to query the Engram DB for matching nodes
let client = reqwest::Client::new();
let url = format!("{engram_url}/api/activate-preview");
let body = serde_json::json!({
"type_name": q.type_name,
"query": q.query,
"limit": 5,
});
match client
.post(&url)
.json(&body)
.timeout(std::time::Duration::from_secs(5))
.send()
.await
{
Ok(resp) if resp.status().is_success() => {
let json: serde_json::Value = resp.json().await.unwrap_or_default();
let nodes: Vec<ActivateNode> = json["nodes"]
.as_array()
.map(|arr| {
arr.iter()
.map(|n| ActivateNode {
id: n["id"].as_str().unwrap_or("").to_string(),
label: n["label"].as_str().unwrap_or("").to_string(),
score: n["score"].as_f64().unwrap_or(0.0),
})
.collect()
})
.unwrap_or_default();
let count = json["count"].as_u64().unwrap_or(nodes.len() as u64) as usize;
Ok(Json(ActivatePreviewResponse {
count,
nodes,
connected: true,
}))
}
_ => {
// Engram not connected — return stub indicating disconnected state
Ok(Json(ActivatePreviewResponse {
count: 0,
nodes: vec![],
connected: false,
}))
}
}
}