use super::{
config::{Place, Seat},
tools::Identity,
};
use crate::{
client::{
Client, Ended, Reply,
network::{Address, Remote, Trust},
},
mcp::adapter::{Sender, ServiceRefusal},
serve::{cli::Config, protocol::Operation},
};
use serde::Deserialize;
use serde_json::{Value, json, value::RawValue};
use std::time::Duration;
pub(super) struct Native {
page: Option<u32>,
address: Address,
timeout: Duration,
issuer: String,
agent: String,
proof: String,
bound: usize,
}
#[derive(Deserialize)]
struct Wire {
method: String,
params: Option<Box<RawValue>>,
}
impl Native {
pub fn new(seat: &Seat, identity: &Identity) -> anyhow::Result<Self> {
let (page, address, timeout) = match &seat.place {
Place::Local { serve_config, .. } => {
let config: Config = serde_json::from_slice(&std::fs::read(serve_config)?)
.map_err(|_| anyhow::anyhow!("assistant_serve_config_invalid"))?;
let endpoint = config.endpoint(false)?;
anyhow::ensure!(endpoint.name == seat.store, "assistant_serve_store_differs");
(
Some(config.max_page.get()),
Address::Socket(endpoint.socket),
Duration::from_millis(config.timeout_ms.get()),
)
}
Place::Network { store, .. } => {
let trust = Trust::system().map_err(anyhow::Error::msg)?;
let address = Address::Network(
Remote::new(&store.endpoint, &store.server_name, &trust)
.map_err(anyhow::Error::msg)?,
);
let timeout = Duration::from_millis(store.timeout_ms.map_or(
crate::mcp::stdio::DEFAULT_TIMEOUT_MS.get(),
std::num::NonZeroU64::get,
));
(
store.max_page.map(std::num::NonZeroU32::get),
address,
timeout,
)
}
};
Ok(Self {
page,
address,
timeout,
issuer: seat.issuer.clone(),
agent: identity.agent.clone(),
proof: identity.launch.clone(),
bound: seat.harness.issuer_response_bytes,
})
}
pub fn page(&mut self) -> anyhow::Result<u32> {
if let Some(page) = self.page {
return Ok(page);
}
let page = Client::reach(&self.address, self.timeout)
.map_err(|ended| anyhow::anyhow!("assistant_store_unreached: {}", ended.why()))?
.max_page()
.get();
self.page = Some(page);
Ok(page)
}
pub fn read(&mut self, method: &str, params: &Value) -> Result<Value, ServiceRefusal> {
let request = serde_json::from_value(json!({"method":method,"params":params}))
.map_err(|error| encoding(&error))?;
serde_json::from_str(self.send(request)?.get()).map_err(|error| encoding(&error))
}
}
impl Sender for Native {
fn send(&mut self, request: Operation) -> Result<Box<RawValue>, ServiceRefusal> {
let pass = super::tools::acquire(&self.issuer, &self.agent, &self.proof, self.bound)
.map_err(|_| ServiceRefusal {
code: "assistant_record_pass_refused".to_owned(),
message: "could not acquire the agent's record pass".to_owned(),
receipt: None,
})?;
let mut client = Client::reach(&self.address, self.timeout).map_err(ended)?;
let raw = serde_json::value::to_raw_value(&json!({"pass":pass}))
.map_err(|error| encoding(&error))?;
answered(client.call("session.pass", Some(&raw)).map_err(ended)?)?;
let raw = serde_json::value::to_raw_value(&request).map_err(|error| encoding(&error))?;
let operation: Wire = serde_json::from_str(raw.get()).map_err(|error| encoding(&error))?;
answered(
client
.call(&operation.method, operation.params.as_deref())
.map_err(ended)?,
)
}
}
fn answered(reply: Reply) -> Result<Box<RawValue>, ServiceRefusal> {
match reply {
Reply::Answered(raw) => Ok(raw),
Reply::Refused(error) => Err(ServiceRefusal {
code: error.code,
message: error.message,
receipt: error.verified.map(|receipt| receipt.raw().to_owned()),
}),
}
}
fn encoding(error: &serde_json::Error) -> ServiceRefusal {
ServiceRefusal {
code: "assistant_record_encoding_failed".to_owned(),
message: error.to_string(),
receipt: None,
}
}
fn ended(error: Ended) -> ServiceRefusal {
let (code, message, receipt) = match error {
Ended::Usage(why) | Ended::NotAsked(why) => ("assistant_record_not_asked", why, None),
Ended::Unknown(why) => ("assistant_record_outcome_unknown", why, None),
Ended::Received { why, receipt } => (
"assistant_record_outcome_unknown",
why,
Some(receipt.raw().to_owned()),
),
};
ServiceRefusal {
code: code.to_owned(),
message,
receipt,
}
}