mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
mesh-llm: B1 NIP-11 fetcher for iroh_relay_url
desktop/src-tauri/src/mesh_llm/nip11.rs: fetch_iroh_relay_url(ws_url) converts ws:// -> http://, GETs / with Accept: application/nostr+json, extracts the iroh_relay_url field from the NIP-11 JSON. Returns Ok(None) on unreachable relays / malformed responses / missing field so mesh-LLM silently disables itself rather than producing a deploy mystery; only returns Err for un-fixable caller mistakes (non-ws URL). Mirrors the probe_relay_supports_nip43 helper already in commands/pairing.rs; deliberately doesn't share code since this is a different decode shape with different graceful-failure semantics. desktop mesh_llm tests: 9 -> 11 (+2 nip11 helper). Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -16,9 +16,11 @@
|
||||
//! (VRAM/RAM/concurrency caps, models).
|
||||
|
||||
pub mod endpoint;
|
||||
pub mod nip11;
|
||||
pub mod nip98;
|
||||
pub mod offer;
|
||||
|
||||
pub use endpoint::{load_or_create_endpoint_key, EndpointKeyError};
|
||||
pub use nip11::{fetch_iroh_relay_url, Nip11Error};
|
||||
pub use nip98::{build_nip98_bearer, Nip98BearerError};
|
||||
pub use offer::{ComputeSharingPrefs, OfferPrefsError};
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
//! NIP-11 fetch helper specialised for mesh-LLM bootstrapping.
|
||||
//!
|
||||
//! At session start the desktop queries the connected Sprout relay's
|
||||
//! NIP-11 document and reads the `iroh_relay_url` field
|
||||
//! ([`sprout_relay::nip11::RelayInfo::iroh_relay_url`]). When set, this is
|
||||
//! the URL the desktop signs NIP-98 bearer tokens against and dials iroh
|
||||
//! connections through.
|
||||
//!
|
||||
//! Unreachable relays / malformed responses / missing field all return
|
||||
//! `Ok(None)` — the desktop falls back to "mesh-LLM disabled for this
|
||||
//! relay", same UX as a relay that simply doesn't host iroh.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
const NIP11_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
/// Errors that bubble up only for *infrastructural* failures the caller
|
||||
/// should surface (e.g. a malformed user-provided URL). Network and
|
||||
/// "field-not-present" cases collapse to `Ok(None)` because mesh-LLM is
|
||||
/// optional.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Nip11Error {
|
||||
/// The provided relay URL doesn't start with `ws://` or `wss://`.
|
||||
#[error("not a ws:// or wss:// URL: {0}")]
|
||||
NotWebsocketUrl(String),
|
||||
/// The `reqwest` client failed to construct.
|
||||
#[error("http client init: {0}")]
|
||||
ClientBuild(String),
|
||||
}
|
||||
|
||||
/// Convert a Nostr `ws(s)://` relay URL to its `http(s)://` NIP-11 base.
|
||||
fn to_http_base(relay_url: &str) -> Result<String, Nip11Error> {
|
||||
if let Some(rest) = relay_url.strip_prefix("wss://") {
|
||||
Ok(format!("https://{rest}"))
|
||||
} else if let Some(rest) = relay_url.strip_prefix("ws://") {
|
||||
Ok(format!("http://{rest}"))
|
||||
} else {
|
||||
Err(Nip11Error::NotWebsocketUrl(relay_url.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the relay's NIP-11 document and extract `iroh_relay_url`.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `Ok(Some(url))` — the relay advertises an iroh-relay endpoint.
|
||||
/// - `Ok(None)` — relay is reachable but does not advertise `iroh_relay_url`,
|
||||
/// OR the relay is unreachable / returned a malformed doc. Mesh-LLM is
|
||||
/// silently disabled in both cases (same UX).
|
||||
/// - `Err(_)` — the *caller* gave us something un-fixable (e.g. a non-ws
|
||||
/// URL). Should be surfaced as a developer error, not a runtime fallback.
|
||||
pub async fn fetch_iroh_relay_url(relay_url: &str) -> Result<Option<String>, Nip11Error> {
|
||||
let http_url = to_http_base(relay_url)?;
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(NIP11_TIMEOUT)
|
||||
.build()
|
||||
.map_err(|e| Nip11Error::ClientBuild(e.to_string()))?;
|
||||
|
||||
let resp = match client
|
||||
.get(&http_url)
|
||||
.header("Accept", "application/nostr+json")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Err(_) => return Ok(None), // unreachable — graceful no-mesh
|
||||
};
|
||||
|
||||
let json: serde_json::Value = match resp.json().await {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Ok(None), // malformed — graceful no-mesh
|
||||
};
|
||||
|
||||
Ok(json
|
||||
.get("iroh_relay_url")
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn http_base_strips_ws_prefix() {
|
||||
assert_eq!(
|
||||
to_http_base("wss://relay.example.com/iroh").unwrap(),
|
||||
"https://relay.example.com/iroh",
|
||||
);
|
||||
assert_eq!(
|
||||
to_http_base("ws://localhost:3000").unwrap(),
|
||||
"http://localhost:3000",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_base_rejects_non_ws() {
|
||||
assert!(matches!(
|
||||
to_http_base("https://relay.example.com"),
|
||||
Err(Nip11Error::NotWebsocketUrl(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
to_http_base("relay.example.com"),
|
||||
Err(Nip11Error::NotWebsocketUrl(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user