Skip to main content

wave_dispatch/
lib.rs

1//! wave Dispatch — thin Rust client. Route each request to the cheapest capable model (local-first;
2//! escalate to your frontier only when needed). BYO keys + infra; the service returns a routing decision.
3use serde_json::{json, Value};
4use std::error::Error;
5
6pub struct Dispatch {
7    license: Option<String>,
8    endpoint: String,
9    agents: String,
10}
11
12impl Dispatch {
13    /// `license`: your `wv_...` key, or `None` to read `WAVE_LICENSE` (omit for x402 pay-per-use).
14    pub fn new(license: Option<String>) -> Self {
15        Dispatch {
16            license: license.or_else(|| std::env::var("WAVE_LICENSE").ok()),
17            endpoint: std::env::var("DISPATCH_ENDPOINT").unwrap_or_else(|_| "https://dispatch.wave.online".into()),
18            agents: std::env::var("WAVE_AGENTS_ENDPOINT").unwrap_or_else(|_| "https://dispatch-agents.wave.online".into()),
19        }
20    }
21
22    /// Classify a prompt (no execution): `{route, probability, margin, forward}`.
23    pub fn route(&self, prompt: &str) -> Result<Value, Box<dyn Error>> {
24        self.post(&self.endpoint, json!({ "prompt": prompt }))
25    }
26
27    /// Classify and run on the edge if your plan allows it.
28    pub fn execute(&self, prompt: &str) -> Result<Value, Box<dyn Error>> {
29        self.post(&self.endpoint, json!({ "prompt": prompt, "execute": true }))
30    }
31
32    /// This license's savings ledger (decisions, saved_usd, saved_pct, ...). Requires a license.
33    pub fn savings(&self) -> Result<Value, Box<dyn Error>> {
34        self.get(&format!("{}/ledger/summary?license={}", self.agents, self.lic()?))
35    }
36
37    /// This license's agent-subscription status.
38    pub fn subscription(&self) -> Result<Value, Box<dyn Error>> {
39        self.get(&format!("{}/subscription/status?license={}", self.agents, self.lic()?))
40    }
41
42    /// Start/replace a programmatic subscription (plan: agent_starter|agent_pro|agent_scale).
43    pub fn subscribe(&self, plan: &str) -> Result<Value, Box<dyn Error>> {
44        self.post(&format!("{}/subscription/create", self.agents),
45                  json!({ "license": self.license, "plan": plan }))
46    }
47
48    fn lic(&self) -> Result<String, Box<dyn Error>> {
49        self.license.clone().ok_or_else(|| "dispatch: a license is required for savings()/subscription()".into())
50    }
51
52    fn auth(&self, mut req: ureq::Request) -> ureq::Request {
53        if let Some(l) = &self.license {
54            req = req.set("authorization", &format!("Bearer {}", l));
55        }
56        req
57    }
58
59    fn post(&self, url: &str, body: Value) -> Result<Value, Box<dyn Error>> {
60        let req = self.auth(ureq::post(url).set("content-type", "application/json"));
61        Ok(serde_json::from_str(&req.send_string(&body.to_string())?.into_string()?)?)
62    }
63
64    fn get(&self, url: &str) -> Result<Value, Box<dyn Error>> {
65        let req = self.auth(ureq::get(url).set("content-type", "application/json"));
66        Ok(serde_json::from_str(&req.call()?.into_string()?)?)
67    }
68}