use crate::agent::{run_inference, InferenceBackend, InferenceReadiness, InferenceRequest, InferenceResult};
use crate::analyzer::fix::{CheckReferences, FixReport, FixSuggestionRequest};
use crate::io::api::webhooks::store::OperationQueue;
use crate::io::workflow::{RepositoryChangeSet, RepositoryInput, RepositoryWorkflowRegistry};
use crate::io::ApiResult;
use crate::util::constants::app::{APPLICATION, JSON_RPC_VERSION};
use acorn_core::prelude::{Box, String, Vec};
use acorn_schema::validation::Validate;
use alloc::sync::Arc;
use color_eyre::eyre::eyre;
use core::fmt;
use core::future::Future;
use core::pin::Pin;
use futures::StreamExt;
use schemars::{schema_for, JsonSchema};
use serde::de::{DeserializeOwned, Error as DeError, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[cfg(feature = "agentic")]
pub mod http;
pub const INTERNAL_ERROR: i32 = -32603;
pub const INVALID_PARAMS: i32 = -32602;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const PARSE_ERROR: i32 = -32700;
pub const POLICY_DENIED: i32 = -32001;
type OperationFuture = Pin<Box<dyn Future<Output = Result<Value, RpcError>> + Send>>;
type OperationHandler = Arc<dyn Fn(Value, InvocationContext) -> OperationFuture + Send + Sync>;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum InvocationOrigin {
Direct,
Grpc,
JsonRpc,
Webhook,
}
enum ParsedCall {
Invalid { id: Value, error: RpcError },
Notification { method: MethodName, params: Value },
Request { id: RequestId, method: MethodName, params: Value },
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum RequestId {
Number(i64),
String(String),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Dispatch {
pub body: Option<Vec<u8>>,
pub status_code: u16,
}
#[derive(Serialize)]
#[serde(deny_unknown_fields)]
struct ErrorResponse {
error: RpcError,
id: Value,
jsonrpc: &'static str,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct InvocationContext {
pub allow_inference: bool,
pub allow_mutation: bool,
pub correlation_id: Option<String>,
pub max_output_bytes: usize,
pub offline: bool,
pub origin: InvocationOrigin,
pub principal: Principal,
}
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, Validate)]
#[serde(try_from = "String", into = "String")]
pub struct MethodName {
#[validate(length(min = 1), each(nonempty))]
pub parts: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Notification {
pub jsonrpc: String,
pub method: MethodName,
pub params: Value,
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OperationDefinition {
pub effects: OperationEffects,
pub name: MethodName,
pub parameters_schema: Value,
pub result_schema: Value,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OperationEffects {
pub credentials: bool,
pub destructive: bool,
pub filesystem_read: bool,
pub filesystem_write: bool,
pub inference: bool,
pub mutation: bool,
pub network_read: bool,
pub process: bool,
}
#[derive(Clone, Default)]
pub struct OperationRegistry {
operations: Vec<RegisteredOperation>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Principal {
pub identifier: String,
}
#[derive(Clone)]
struct RegisteredOperation {
definition: OperationDefinition,
handler: OperationHandler,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RpcError {
pub code: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
pub message: String,
}
struct StrictValue(Value);
#[derive(Serialize)]
#[serde(deny_unknown_fields)]
struct SuccessResponse {
id: RequestId,
jsonrpc: &'static str,
result: Value,
}
impl Dispatch {
fn serialize(value: Value) -> Self {
match serde_json::to_vec(&value) {
| Ok(body) => Self {
body: Some(body),
status_code: 200,
},
| Err(_) => Self {
body: Some(
format!(r#"{{"jsonrpc":"{JSON_RPC_VERSION}","id":null,"error":{{"code":-32603,"message":"Internal error"}}}}"#).into_bytes(),
),
status_code: 500,
},
}
}
}
impl Default for InvocationContext {
fn default() -> Self {
Self {
allow_inference: false,
allow_mutation: false,
correlation_id: None,
max_output_bytes: 1024 * 1024,
offline: false,
origin: InvocationOrigin::Direct,
principal: Principal {
identifier: "local".to_string(),
},
}
}
}
impl fmt::Display for MethodName {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{APPLICATION}.{}", self.parts.join("."))
}
}
impl<'a, const N: usize> From<[&'a str; N]> for MethodName {
fn from(parts: [&'a str; N]) -> Self {
Self {
parts: parts.into_iter().map(str::to_string).collect(),
}
}
}
impl TryFrom<&str> for MethodName {
type Error = color_eyre::Report;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::try_from(value.to_string())
}
}
impl TryFrom<String> for MethodName {
type Error = color_eyre::Report;
fn try_from(value: String) -> Result<Self, Self::Error> {
let prefix = format!("{APPLICATION}.");
match value.strip_prefix(&prefix) {
| Some(value) => {
let name = Self {
parts: value.split('.').map(str::to_string).collect(),
};
name.validate()
.map(|()| name)
.map_err(|why| eyre!("Invalid JSON-RPC method name — {why}"))
}
| None => Err(eyre!("Operation name '{value}' must use the non-reserved '{APPLICATION}.' namespace")),
}
}
}
impl Notification {
pub fn new(method: MethodName, params: Value) -> ApiResult<Self> {
method
.validate()
.map_err(|why| eyre!("Invalid JSON-RPC method name — {why}"))
.map(|()| Self {
jsonrpc: JSON_RPC_VERSION.to_string(),
method,
params,
})
}
}
impl OperationDefinition {
pub fn new<I: JsonSchema, O: JsonSchema>(name: MethodName, effects: OperationEffects) -> ApiResult<Self> {
name.validate()
.map_err(|why| eyre!("Invalid JSON-RPC method name — {why}"))
.map(|()| Self {
effects,
name,
parameters_schema: serde_json::to_value(schema_for!(I)).unwrap_or(Value::Null),
result_schema: serde_json::to_value(schema_for!(O)).unwrap_or(Value::Null),
})
}
}
impl fmt::Debug for OperationRegistry {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let definitions: Vec<OperationDefinition> = self.operations.iter().map(|operation| operation.definition.clone()).collect();
formatter.debug_struct("OperationRegistry").field("definitions", &definitions).finish()
}
}
impl OperationRegistry {
pub fn acorn() -> ApiResult<Self> {
#[derive(Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
struct OperationInput {
operation_key: String,
}
#[derive(JsonSchema, Serialize)]
#[serde(deny_unknown_fields)]
struct OperationStatusOutput {
state: Option<String>,
}
#[derive(JsonSchema, Serialize)]
#[serde(deny_unknown_fields)]
struct ReplayOutput {
queued: bool,
}
#[derive(Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
struct ProcessInput {
input: RepositoryInput,
workflow: String,
}
#[derive(Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
struct VersionInput {}
#[derive(JsonSchema, Serialize)]
#[serde(deny_unknown_fields)]
struct VersionOutput {
version: String,
}
let registry = OperationDefinition::new::<VersionInput, VersionOutput>(MethodName::from(["version"]), OperationEffects::default())
.and_then(|definition| {
Self::default().register(definition, |_: VersionInput, _| async {
Ok(VersionOutput {
version: env!("CARGO_PKG_VERSION").to_string(),
})
})
})
.and_then(|registry| {
OperationDefinition::new::<ProcessInput, RepositoryChangeSet>(MethodName::from(["workflows", "process"]), OperationEffects::default())
.and_then(|definition| {
registry.register(definition, |request: ProcessInput, _| async move {
RepositoryWorkflowRegistry::acorn().and_then(|registry| registry.process(&request.workflow, request.input))
})
})
});
registry
.and_then(|registry| {
OperationDefinition::new::<OperationInput, OperationStatusOutput>(
MethodName::from(["operations", "status"]),
OperationEffects::default(),
)
.and_then(|status| {
registry.register(status, |request: OperationInput, _| async move {
OperationQueue::configured()
.state(&request.operation_key)
.map(|state| OperationStatusOutput {
state: state.map(|state| state.to_string()),
})
})
})
})
.and_then(|registry| {
OperationDefinition::new::<OperationInput, ReplayOutput>(
MethodName::from(["operations", "replay"]),
OperationEffects {
filesystem_read: true,
filesystem_write: true,
mutation: true,
..OperationEffects::default()
},
)
.and_then(|replay| {
registry.register(replay, |request: OperationInput, _| async move {
OperationQueue::configured()
.replay(&request.operation_key)
.map(|queued| ReplayOutput { queued })
})
})
})
}
pub async fn dispatch(&self, body: &[u8], context: InvocationContext) -> Dispatch {
match serde_json::from_slice::<StrictValue>(body).map(|StrictValue(value)| value) {
| Ok(Value::Array(values)) if values.is_empty() => RpcError::new(INVALID_REQUEST, "Invalid Request").dispatch(Value::Null),
| Ok(Value::Array(values)) => {
let responses = self.dispatch_calls(values, context).await;
match responses.is_empty() {
| true => Dispatch {
body: None,
status_code: 204,
},
| false => Dispatch::serialize(Value::Array(responses)),
}
}
| Ok(value) => match self.dispatch_call(value, context).await {
| Some(response) => Dispatch::serialize(response),
| None => Dispatch {
body: None,
status_code: 204,
},
},
| Err(_) => RpcError::new(PARSE_ERROR, "Parse error").dispatch(Value::Null),
}
}
async fn dispatch_call(&self, value: Value, context: InvocationContext) -> Option<Value> {
match ParsedCall::from(value) {
| ParsedCall::Invalid { id, error } => Some(error.value(id)),
| ParsedCall::Notification { method, params } => {
self.invoke(&method, params, context).await.ok();
None
}
| ParsedCall::Request { id, method, params } => {
let context = InvocationContext {
correlation_id: Some(id.as_string()),
..context
};
Some(match self.invoke(&method, params, context).await {
| Ok(result) => serde_json::to_value(SuccessResponse {
id,
jsonrpc: JSON_RPC_VERSION,
result,
})
.unwrap_or_else(|_| RpcError::internal().value(Value::Null)),
| Err(error) => error.value(serde_json::to_value(id).unwrap_or(Value::Null)),
})
}
}
}
async fn dispatch_calls(&self, values: Vec<Value>, context: InvocationContext) -> Vec<Value> {
futures::stream::iter(values)
.then(|value| self.dispatch_call(value, context.clone()))
.filter_map(core::future::ready)
.collect()
.await
}
pub async fn invoke(&self, method: &MethodName, params: Value, context: InvocationContext) -> Result<Value, RpcError> {
match self.operations.iter().find(|operation| operation.definition.name == *method) {
| Some(operation) => match policy_error(&operation.definition.effects, &context) {
| Some(error) => Err(error),
| None => (operation.handler)(params, context.clone()).await.and_then(|result| {
serde_json::to_vec(&result).map_err(|_| RpcError::internal()).and_then(|encoded| {
match encoded.len() <= context.max_output_bytes.max(1) {
| true => Ok(result),
| false => Err(RpcError::new(INTERNAL_ERROR, "Operation result exceeded the configured output limit")),
}
})
}),
},
| None => Err(RpcError::new(METHOD_NOT_FOUND, "Method not found")),
}
}
pub fn register<I, O, F, Fut>(self, definition: OperationDefinition, handler: F) -> ApiResult<Self>
where
I: DeserializeOwned + Send + 'static,
O: Serialize + Send + 'static,
F: Fn(I, InvocationContext) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ApiResult<O>> + Send + 'static,
{
match self.operations.iter().any(|operation| operation.definition.name == definition.name) {
| true => Err(eyre!("Duplicate operation name '{}'", definition.name)),
| false => {
let name = definition.name.clone();
let handler = Arc::new(handler);
let typed_handler = move |params: Value, context: InvocationContext| -> OperationFuture {
let input = serde_json::from_value::<I>(params).map_err(|_| RpcError::new(INVALID_PARAMS, "Invalid method parameters"));
let handler = Arc::clone(&handler);
let name = name.clone();
Box::pin(async move {
match input {
| Ok(input) => handler(input, context)
.await
.map_err(|_| RpcError::new(INTERNAL_ERROR, format!("Operation '{name}' failed")))
.and_then(|output| serde_json::to_value(output).map_err(|_| RpcError::internal())),
| Err(error) => Err(error),
}
})
};
let mut operations = self.operations;
operations.push(RegisteredOperation {
definition,
handler: Arc::new(typed_handler),
});
operations.sort_by(|left, right| left.definition.name.cmp(&right.definition.name));
Ok(Self { operations })
}
}
}
pub fn register_inference<B>(self, backend: B) -> ApiResult<Self>
where
B: InferenceBackend + 'static,
{
let backend = Arc::new(backend);
OperationDefinition::new::<InferenceRequest, InferenceResult>(
MethodName::from(["inference", "run"]),
OperationEffects {
inference: true,
..OperationEffects::default()
},
)
.and_then(|definition| {
let backend = Arc::clone(&backend);
self.register(definition, move |request: InferenceRequest, _| {
let backend = Arc::clone(&backend);
async move { run_inference(backend.as_ref(), &request).await }
})
})
.and_then(|registry| {
OperationDefinition::new::<InferenceRequest, InferenceReadiness>(
MethodName::from(["inference", "readiness"]),
OperationEffects {
inference: true,
..OperationEffects::default()
},
)
.and_then(|definition| {
let backend = Arc::clone(&backend);
registry.register(definition, move |request: InferenceRequest, _| {
let backend = Arc::clone(&backend);
async move { InferenceReadiness::run(backend.as_ref(), &request).await }
})
})
})
.and_then(|registry| {
OperationDefinition::new::<FixSuggestionRequest, FixReport>(
MethodName::from(["fixes", "suggest"]),
OperationEffects {
inference: true,
..OperationEffects::default()
},
)
.and_then(|definition| {
let backend = Arc::clone(&backend);
registry.register(definition, move |request: FixSuggestionRequest, _| {
let backend = Arc::clone(&backend);
async move { request.references.suggest(backend.as_ref(), &request.options).await }
})
})
})
}
}
impl From<Value> for ParsedCall {
fn from(value: Value) -> Self {
match value {
| Value::Object(object) => Self::from_object(object),
| _ => Self::Invalid {
id: Value::Null,
error: RpcError::new(INVALID_REQUEST, "Invalid Request"),
},
}
}
}
impl ParsedCall {
fn from_object(mut object: Map<String, Value>) -> Self {
let id_value = object.remove("id");
let jsonrpc = object.remove("jsonrpc");
let method = object.remove("method").and_then(|value| match value {
| Value::String(method) => MethodName::try_from(method).ok(),
| _ => None,
});
let params = object.remove("params").unwrap_or_else(|| Value::Object(Map::new()));
let valid_shape = object.is_empty()
&& matches!(jsonrpc, Some(Value::String(ref version)) if version == JSON_RPC_VERSION)
&& method.is_some()
&& matches!(params, Value::Object(_) | Value::Array(_));
match (valid_shape, method) {
| (false, _) | (_, None) => Self::Invalid {
id: id_value.as_ref().map_or(Value::Null, RequestId::valid_response_value),
error: RpcError::new(INVALID_REQUEST, "Invalid Request"),
},
| (true, Some(method)) => match id_value {
| None => Self::Notification { method, params },
| Some(value) => match serde_json::from_value::<RequestId>(value) {
| Ok(id) => Self::Request { id, method, params },
| Err(_) => Self::Invalid {
id: Value::Null,
error: RpcError::new(INVALID_REQUEST, "Invalid Request"),
},
},
},
}
}
}
impl RequestId {
fn as_string(&self) -> String {
match self {
| Self::Number(value) => value.to_string(),
| Self::String(value) => value.clone(),
}
}
fn valid_response_value(value: &Value) -> Value {
match value {
| value @ (Value::String(_) | Value::Number(_)) => value.clone(),
| _ => Value::Null,
}
}
}
impl core::error::Error for RpcError {}
impl fmt::Display for RpcError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{} ({})", self.message, self.code)
}
}
impl RpcError {
fn dispatch(self, id: Value) -> Dispatch {
Dispatch::serialize(self.value(id))
}
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
data: None,
message: message.into(),
}
}
fn internal() -> Self {
Self::new(INTERNAL_ERROR, "Internal error")
}
fn value(self, id: Value) -> Value {
serde_json::to_value(ErrorResponse {
error: self,
id,
jsonrpc: JSON_RPC_VERSION,
})
.unwrap_or(Value::Null)
}
}
impl From<MethodName> for String {
fn from(value: MethodName) -> Self {
value.to_string()
}
}
impl<'de> Deserialize<'de> for StrictValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct StrictVisitor;
impl<'de> Visitor<'de> for StrictVisitor {
type Value = StrictValue;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a JSON value without duplicate object fields")
}
fn visit_bool<E: DeError>(self, value: bool) -> Result<Self::Value, E> {
Ok(StrictValue(Value::Bool(value)))
}
fn visit_f64<E: DeError>(self, value: f64) -> Result<Self::Value, E> {
serde_json::Number::from_f64(value)
.map(Value::Number)
.map(StrictValue)
.ok_or_else(|| E::custom("invalid JSON number"))
}
fn visit_i64<E: DeError>(self, value: i64) -> Result<Self::Value, E> {
Ok(StrictValue(Value::Number(value.into())))
}
fn visit_map<A: MapAccess<'de>>(self, mut values: A) -> Result<Self::Value, A::Error> {
let mut object = Map::new();
let mut result = Ok(());
loop {
match values.next_entry::<String, StrictValue>() {
| Ok(Some((key, StrictValue(_)))) if object.contains_key(&key) => {
result = Err(A::Error::custom(format!("duplicate field '{key}'")));
break;
}
| Ok(Some((key, StrictValue(value)))) => {
object.insert(key, value);
}
| Ok(None) => break,
| Err(why) => {
result = Err(why);
break;
}
}
}
result.map(|()| StrictValue(Value::Object(object)))
}
fn visit_none<E: DeError>(self) -> Result<Self::Value, E> {
Ok(StrictValue(Value::Null))
}
fn visit_seq<A: SeqAccess<'de>>(self, mut values: A) -> Result<Self::Value, A::Error> {
let mut array = Vec::new();
let mut result = Ok(());
loop {
match values.next_element::<StrictValue>() {
| Ok(Some(StrictValue(value))) => array.push(value),
| Ok(None) => break,
| Err(why) => {
result = Err(why);
break;
}
}
}
result.map(|()| StrictValue(Value::Array(array)))
}
fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
Ok(StrictValue(Value::String(value.to_string())))
}
fn visit_string<E: DeError>(self, value: String) -> Result<Self::Value, E> {
Ok(StrictValue(Value::String(value)))
}
fn visit_u64<E: DeError>(self, value: u64) -> Result<Self::Value, E> {
Ok(StrictValue(Value::Number(value.into())))
}
fn visit_unit<E: DeError>(self) -> Result<Self::Value, E> {
Ok(StrictValue(Value::Null))
}
}
deserializer.deserialize_any(StrictVisitor)
}
}
fn policy_error(effects: &OperationEffects, context: &InvocationContext) -> Option<RpcError> {
let requires_mutation = effects.destructive || effects.filesystem_write || effects.mutation;
let unavailable = effects.credentials || effects.process || (effects.inference && !context.allow_inference);
let mutation_denied = requires_mutation && !context.allow_mutation;
let offline = effects.network_read && context.offline;
match (unavailable, mutation_denied, offline) {
| (true, _, _) => Some(RpcError::new(POLICY_DENIED, "Operation declares an unavailable effect")),
| (_, true, _) => Some(RpcError::new(POLICY_DENIED, "Operation requires mutation approval")),
| (_, _, true) => Some(RpcError::new(POLICY_DENIED, "Operation is unavailable while offline")),
| _ => None,
}
}
#[cfg(test)]
mod tests;