Wire Axon /axon/message to NeuronRuntime dispatch
- axon.rs: replace stub AxonHandler with live NeuronRuntime dispatch; tool calls now route through Engram VM when modules are loaded - state.rs: load .el modules from NEURON_LANG_DIR (default ~/.neuron/lang); falls back to empty dir gracefully when not present - Cargo.toml: add engram-core dependency for EngramDb
This commit is contained in:
@@ -11,6 +11,8 @@ path = "src/main.rs"
|
||||
neuron-domain = { path = "../neuron-domain" }
|
||||
neuron-store = { path = "../neuron-store" }
|
||||
neuron-protocol = { path = "../neuron-protocol" }
|
||||
neuron-runtime = { path = "../neuron-runtime" }
|
||||
chrono = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
@@ -4,31 +4,53 @@ use axum::{
|
||||
Json,
|
||||
};
|
||||
use futures_util::Stream;
|
||||
use neuron_protocol::{AxonHandler, AxonMessage, AxonResponse};
|
||||
use neuron_protocol::{AxonMessage, AxonMethod, AxonResponse};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
/// POST /axon/message — receive an Axon protocol message and return a response.
|
||||
/// POST /axon/message — dispatch to NeuronRuntime.
|
||||
pub async fn post_message(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(msg): Json<AxonMessage>,
|
||||
) -> Json<AxonResponse> {
|
||||
let mut handler = AxonHandler::new();
|
||||
match msg.method {
|
||||
AxonMethod::ToolCall => {
|
||||
let tool_name = msg
|
||||
.params
|
||||
.get("tool")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let params = msg
|
||||
.params
|
||||
.get("params")
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
|
||||
// Register stub handlers for all known tool names.
|
||||
for &tool in neuron_protocol::tools::ALL_TOOLS {
|
||||
let name = tool.to_string();
|
||||
handler.register(name, |params| {
|
||||
serde_json::json!({
|
||||
"status": "ok",
|
||||
"params": params
|
||||
})
|
||||
});
|
||||
if tool_name.is_empty() {
|
||||
return Json(AxonResponse::err(msg.id, "missing 'tool' field in params"));
|
||||
}
|
||||
|
||||
let result = state.runtime.dispatch(&tool_name, params).await;
|
||||
|
||||
// If the result contains an "error" field, surface it as an error response.
|
||||
if let Some(err_msg) = result.get("error").and_then(|v| v.as_str()) {
|
||||
Json(AxonResponse::err(msg.id, err_msg))
|
||||
} else {
|
||||
Json(AxonResponse::ok(msg.id, result))
|
||||
}
|
||||
}
|
||||
|
||||
AxonMethod::Ping => Json(AxonResponse::ok(
|
||||
msg.id,
|
||||
serde_json::json!({ "ts": chrono::Utc::now().to_rfc3339() }),
|
||||
)),
|
||||
|
||||
_ => Json(AxonResponse::err(msg.id, "unsupported method")),
|
||||
}
|
||||
|
||||
Json(handler.dispatch(msg))
|
||||
}
|
||||
|
||||
/// GET /axon/sse — Server-Sent Events stream for Axon events.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use neuron_runtime::NeuronRuntime;
|
||||
use neuron_store::{BacklogStore, ContextStore, IseStore, KnowledgeStore, MemoryStore, open_db};
|
||||
|
||||
/// Shared application state — one store per domain.
|
||||
@@ -12,17 +15,45 @@ pub struct AppState {
|
||||
pub context: ContextStore,
|
||||
pub ise: IseStore,
|
||||
pub api_key: String,
|
||||
pub runtime: Arc<NeuronRuntime>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(db_path: &Path, api_key: String) -> Result<Self, neuron_domain::NeuronError> {
|
||||
// Module directory defaults to "~/.neuron/lang" (or wherever .el files live).
|
||||
// Falls back to a non-existent directory — runtime will have zero modules
|
||||
// and fall back to native bindings for all tools.
|
||||
let module_dir_raw =
|
||||
std::env::var("NEURON_LANG_DIR").unwrap_or_else(|_| "~/.neuron/lang".to_string());
|
||||
let module_dir = std::path::PathBuf::from(
|
||||
module_dir_raw
|
||||
.replace('~', &std::env::var("HOME").unwrap_or_else(|_| ".".to_string())),
|
||||
);
|
||||
|
||||
// If the module directory doesn't exist, create an empty temp dir so
|
||||
// load_directory doesn't error out.
|
||||
let effective_dir = if module_dir.exists() {
|
||||
module_dir
|
||||
} else {
|
||||
// Use a tmp dir — runtime will have zero modules (safe fallback).
|
||||
std::env::temp_dir().join("neuron-lang-empty")
|
||||
};
|
||||
let _ = std::fs::create_dir_all(&effective_dir);
|
||||
|
||||
// Open the sled DB exactly once — Clone shares the same file lock.
|
||||
let db = open_db(db_path)?;
|
||||
|
||||
let runtime = NeuronRuntime::from_dir(&effective_dir, db.clone())
|
||||
.map_err(|e| neuron_domain::NeuronError::Storage(e.to_string()))?;
|
||||
|
||||
Ok(Self {
|
||||
memory: MemoryStore::new(open_db(db_path)?),
|
||||
knowledge: KnowledgeStore::new(open_db(db_path)?),
|
||||
backlog: BacklogStore::new(open_db(db_path)?),
|
||||
context: ContextStore::new(open_db(db_path)?),
|
||||
ise: IseStore::new(open_db(db_path)?),
|
||||
memory: MemoryStore::new(db.clone()),
|
||||
knowledge: KnowledgeStore::new(db.clone()),
|
||||
backlog: BacklogStore::new(db.clone()),
|
||||
context: ContextStore::new(db.clone()),
|
||||
ise: IseStore::new(db),
|
||||
api_key,
|
||||
runtime: Arc::new(runtime),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user