pub mod pipeline;
pub mod render;
pub mod rpc;
pub mod source;
pub mod tools;
use color_eyre::Result;
use serde_json::{json, Value};
use std::io::{BufRead, Write};
#[derive(Default)]
pub struct Server {
pub cache: Option<source::Cached>,
}
impl Server {
pub fn new() -> Self {
Self::default()
}
}
pub fn handle_message(server: &mut Server, line: &str) -> Option<Value> {
let request = match rpc::parse(line) {
rpc::Incoming::Call(r) => r,
rpc::Incoming::Ignore => return None,
rpc::Incoming::Invalid { id, code } => {
let message = if code == rpc::PARSE_ERROR {
"Parse error: message is not valid JSON"
} else {
"Invalid request"
};
return Some(rpc::error(id, code, message));
}
};
let id = request.id?;
let result: Result<Value, String> = match request.method.as_str() {
"initialize" => Ok(initialize_result(&request.params)),
"ping" => Ok(json!({})),
"tools/list" => Ok(json!({"tools": tools::definitions()})),
"tools/call" => return Some(call_tool(server, id, &request.params)),
other => {
return Some(rpc::error(
Some(id),
rpc::METHOD_NOT_FOUND,
format!("Unknown method: {}", other),
))
}
};
match result {
Ok(value) => Some(rpc::success(id, value)),
Err(message) => Some(rpc::error(Some(id), rpc::INTERNAL_ERROR, message)),
}
}
fn initialize_result(params: &Value) -> Value {
let requested = params.get("protocolVersion").and_then(Value::as_str);
json!({
"protocolVersion": rpc::negotiate_version(requested),
"capabilities": {"tools": {}},
"serverInfo": {
"name": "tuitab",
"title": "tuitab — tabular data engine",
"version": env!("CARGO_PKG_VERSION"),
},
"instructions": tools::INSTRUCTIONS,
})
}
fn call_tool(server: &mut Server, id: Value, params: &Value) -> Value {
let name = match params.get("name").and_then(Value::as_str) {
Some(n) => n,
None => {
return rpc::error(
Some(id),
rpc::INVALID_PARAMS,
"tools/call requires a 'name' parameter",
)
}
};
let arguments = params.get("arguments").cloned().unwrap_or(json!({}));
match tools::call(server, name, &arguments) {
Ok(payload) => rpc::success(id, rpc::tool_success(payload)),
Err(tools::CallError::UnknownTool(n)) => rpc::error(
Some(id),
rpc::INVALID_PARAMS,
format!("Unknown tool: {}", n),
),
Err(tools::CallError::Failed(message)) => rpc::success(id, rpc::tool_error(message)),
}
}
pub fn serve() -> Result<()> {
let stdin = std::io::stdin();
let mut stdout = std::io::stdout();
let mut server = Server::new();
for line in stdin.lock().lines() {
let line = line?;
if line.trim().is_empty() {
continue;
}
if let Some(response) = handle_message(&mut server, &line) {
writeln!(stdout, "{}", serde_json::to_string(&response)?)?;
stdout.flush()?;
}
}
Ok(())
}