Archived
909c1577f1
- crates/ → engrams/ (Rust engrams live here) - bindings/ → receptors/ (cross-language access points into the graph) - Cargo.toml workspace paths updated
123 lines
4.0 KiB
Rust
123 lines
4.0 KiB
Rust
/// HTTP client for talking to a remote Engram peer.
|
|
///
|
|
/// All peer-to-peer communication goes through this client.
|
|
/// Authentication is via `Authorization: Bearer {api_key}` on every request.
|
|
use crate::types::{Peer, SerializableActivatedNode, SyncDelta};
|
|
use anyhow::Context;
|
|
use uuid::Uuid;
|
|
|
|
pub struct SyncClient {
|
|
peer: Peer,
|
|
http: reqwest::Client,
|
|
our_id: Uuid,
|
|
}
|
|
|
|
impl SyncClient {
|
|
pub fn new(peer: Peer, our_id: Uuid) -> Self {
|
|
let http = reqwest::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(30))
|
|
.build()
|
|
.expect("failed to build reqwest client");
|
|
Self { peer, http, our_id }
|
|
}
|
|
|
|
/// Pull a delta from the remote peer containing everything since `since` (Unix ms).
|
|
///
|
|
/// GET {address}/sync/delta?since={since}&peer_id={our_id}
|
|
pub async fn pull_delta(&self, since: i64) -> anyhow::Result<SyncDelta> {
|
|
let url = format!(
|
|
"{}/sync/delta?since={}&peer_id={}",
|
|
self.peer.address, since, self.our_id
|
|
);
|
|
let resp = self
|
|
.http
|
|
.get(&url)
|
|
.header("Authorization", format!("Bearer {}", self.peer.api_key))
|
|
.send()
|
|
.await
|
|
.context("pull_delta: request failed")?;
|
|
|
|
if !resp.status().is_success() {
|
|
let status = resp.status();
|
|
let body = resp.text().await.unwrap_or_default();
|
|
anyhow::bail!("pull_delta: peer returned {}: {}", status, body);
|
|
}
|
|
|
|
resp.json::<SyncDelta>()
|
|
.await
|
|
.context("pull_delta: failed to decode response")
|
|
}
|
|
|
|
/// Push our delta to the remote peer.
|
|
///
|
|
/// POST {address}/sync/push
|
|
pub async fn push_delta(&self, delta: &SyncDelta) -> anyhow::Result<()> {
|
|
let url = format!("{}/sync/push", self.peer.address);
|
|
let resp = self
|
|
.http
|
|
.post(&url)
|
|
.header("Authorization", format!("Bearer {}", self.peer.api_key))
|
|
.json(delta)
|
|
.send()
|
|
.await
|
|
.context("push_delta: request failed")?;
|
|
|
|
if !resp.status().is_success() {
|
|
let status = resp.status();
|
|
let body = resp.text().await.unwrap_or_default();
|
|
anyhow::bail!("push_delta: peer returned {}: {}", status, body);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Fan spreading activation out to a remote peer.
|
|
///
|
|
/// POST {address}/swarm/activate — the remote peer runs activation locally
|
|
/// (include_peers=false so it does not fan out further, preventing cycles).
|
|
pub async fn remote_activate(
|
|
&self,
|
|
seeds: &[Uuid],
|
|
query_embedding: &[f32],
|
|
max_depth: u8,
|
|
limit: usize,
|
|
) -> anyhow::Result<Vec<SerializableActivatedNode>> {
|
|
use crate::types::SwarmActivateRequest;
|
|
|
|
let url = format!("{}/swarm/activate", self.peer.address);
|
|
let req = SwarmActivateRequest {
|
|
seeds: seeds.to_vec(),
|
|
query_embedding: query_embedding.to_vec(),
|
|
max_depth,
|
|
limit,
|
|
include_peers: false, // no further fan-out — prevents cycles
|
|
};
|
|
|
|
let resp = self
|
|
.http
|
|
.post(&url)
|
|
.header("Authorization", format!("Bearer {}", self.peer.api_key))
|
|
.json(&req)
|
|
.send()
|
|
.await
|
|
.context("remote_activate: request failed")?;
|
|
|
|
if !resp.status().is_success() {
|
|
let status = resp.status();
|
|
let body = resp.text().await.unwrap_or_default();
|
|
anyhow::bail!("remote_activate: peer returned {}: {}", status, body);
|
|
}
|
|
|
|
// The remote returns a SwarmActivateResponse; we only want local_results
|
|
let response: crate::types::SwarmActivateResponse = resp
|
|
.json()
|
|
.await
|
|
.context("remote_activate: failed to decode response")?;
|
|
Ok(response.local_results)
|
|
}
|
|
|
|
pub fn peer(&self) -> &Peer {
|
|
&self.peer
|
|
}
|
|
}
|