use std::time::Duration;
use net_sdk::macros::tool;
use net_sdk::mesh::{Mesh, MeshBuilder};
use net_sdk::tool::formats::openai;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Deserialize, Serialize, Debug)]
struct WebSearchReq {
query: String,
}
#[derive(JsonSchema, Deserialize, Serialize, Debug)]
struct WebSearchResp {
results: Vec<String>,
}
#[tool(
description = "Search the web for relevant pages.",
tag = "web",
tag = "research",
estimated_time_ms = 500
)]
async fn web_search(req: WebSearchReq) -> Result<WebSearchResp, String> {
Ok(WebSearchResp {
results: vec![format!("first hit for '{}'", req.query)],
})
}
#[derive(JsonSchema, Deserialize, Serialize, Debug)]
struct CalcReq {
expression: String,
}
#[derive(JsonSchema, Deserialize, Serialize, Debug)]
struct CalcResp {
answer: String,
}
#[tool(
description = "Evaluate a simple arithmetic expression.",
tag = "math",
estimated_time_ms = 10
)]
async fn calculator(req: CalcReq) -> Result<CalcResp, String> {
Ok(CalcResp {
answer: format!("(would compute `{}`)", req.expression),
})
}
fn pretend_llm_reply(_lowered_tools: &[serde_json::Value]) -> serde_json::Value {
serde_json::json!({
"id": "call_demo_abc123",
"type": "function",
"function": {
"name": "web_search",
"arguments": "{\"query\":\"how does net's capability fold work\"}"
}
})
}
const PSK: [u8; 32] = [0x42u8; 32];
async fn build_pair() -> (Mesh, Mesh) {
let host = MeshBuilder::new("127.0.0.1:0", &PSK)
.unwrap()
.build()
.await
.unwrap();
let agent = MeshBuilder::new("127.0.0.1:0", &PSK)
.unwrap()
.build()
.await
.unwrap();
let host_addr = host.inner().local_addr();
let host_pub = *host.inner().public_key();
let host_id = host.inner().node_id();
let agent_id = agent.inner().node_id();
let (r1, r2) = tokio::join!(
host.inner().accept(agent_id),
agent.inner().connect(host_addr, &host_pub, host_id),
);
r1.unwrap();
r2.unwrap();
host.inner().start();
agent.inner().start();
(host, agent)
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
let (host, agent) = build_pair().await;
let _h1 = web_search_register(&host).expect("register web_search");
let _h2 = calculator_register(&host).expect("register calculator");
host.announce_capabilities(Default::default())
.await
.unwrap();
println!("[host] registered 2 tools, announced capabilities");
let deadline = std::time::Instant::now() + Duration::from_secs(3);
while std::time::Instant::now() < deadline {
if agent.list_tools(None).len() >= 2 {
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
let tools = agent.list_tools(None);
println!("[agent] list_tools() = {} tool(s):", tools.len());
for t in &tools {
println!(
" - {} v{} ({}; tags={:?})",
t.tool_id,
t.version,
t.description.as_deref().unwrap_or("(no description)"),
t.tags,
);
}
let lowered: Vec<_> = tools.iter().map(openai::to_openai_tool).collect();
println!(
"[agent] lowered to OpenAI tools array (first one shown):\n{}",
serde_json::to_string_pretty(&lowered[0]).unwrap(),
);
let llm_reply = pretend_llm_reply(&lowered);
println!(
"[llm] replied with tool_call:\n{}",
serde_json::to_string_pretty(&llm_reply).unwrap(),
);
let spec = openai::lower_openai_tool_call(&llm_reply).expect("parse tool_call");
println!(
"[agent] dispatching call_tool({:?}, {:?})",
spec.name, spec.arguments_json,
);
let req: WebSearchReq = serde_json::from_str(&spec.arguments_json)?;
let resp: WebSearchResp = agent.call_tool(&spec.name, &req).await?;
println!("[agent] response: {:?}", resp);
println!(
"[agent] would feed back: {{role: \"tool\", tool_call_id: {:?}, content: {:?}}}",
spec.provider_call_id, resp,
);
Ok(())
}