#![allow(dead_code)]
use std::net::SocketAddr;
use std::sync::Arc;
use atd_protocol::{
BindingProtocol, SafetyLevel, ToolBinding, ToolCapability, ToolDefinition, ToolResources,
ToolSafety, ToolTrust, ToolVisibility, TrustLevel,
};
use atd_runtime::context::CallContext;
use atd_runtime::registry::{CallFuture, Registry, Tool};
use atd_runtime::secrets::{BearerIdentity, ResolveBearerFuture, ResolveFuture, TokenBroker};
use atd_server_http::{HttpServerConfig, Server};
use tokio::task::JoinHandle;
pub fn stub_def(id: &str) -> ToolDefinition {
ToolDefinition {
id: id.into(),
name: id.into(),
description: "stub".into(),
version: "0.0.0".into(),
capability: ToolCapability {
domain: "echo".into(),
actions: vec![],
tags: vec![],
intent_examples: vec![],
},
input_schema: serde_json::json!({"type":"object"}),
output_schema: serde_json::json!({"type":"object"}),
bindings: vec![ToolBinding {
protocol: BindingProtocol::Cli,
config: serde_json::json!({}),
}],
safety: ToolSafety {
level: SafetyLevel::Read,
dry_run: false,
side_effects: vec![],
data_sensitivity: None,
},
resources: ToolResources {
timeout_ms: 1000,
max_concurrent: 1,
rate_limit_per_min: None,
estimated_tokens: None,
},
trust: ToolTrust {
publisher: "test".into(),
trust_level: TrustLevel::L0Unverified,
signature: None,
},
visibility: ToolVisibility::Read,
required_capabilities: vec![],
tier: None,
errors: vec![],
}
}
pub struct EchoStub {
def: ToolDefinition,
}
impl Default for EchoStub {
fn default() -> Self {
Self {
def: stub_def("ref:echo.say"),
}
}
}
impl EchoStub {
pub fn new() -> Self {
Self::default()
}
}
impl Tool for EchoStub {
fn definition(&self) -> &ToolDefinition {
&self.def
}
fn call<'a>(&'a self, args: serde_json::Value, _ctx: &'a CallContext) -> CallFuture<'a> {
Box::pin(async move { Ok(serde_json::json!({"echoed": args})) })
}
}
pub fn echo_registry() -> Registry {
let mut reg = Registry::new();
reg.register(Arc::new(EchoStub::new()));
reg
}
pub struct RunningServer {
pub addr: SocketAddr,
pub handle: JoinHandle<()>,
}
pub async fn spawn_server(registry: Registry, cfg: HttpServerConfig) -> RunningServer {
let (router, mut server) = Server::builder(registry).config(cfg).build();
let listener = server.bind().await.expect("bind");
let addr = server.local_addr().expect("local_addr after bind");
let handle = tokio::spawn(async move {
let _ = server.serve_with_listener(listener, router).await;
});
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
RunningServer { addr, handle }
}
pub struct FixedBroker {
pub good_token: String,
pub identity: BearerIdentity,
}
impl FixedBroker {
pub fn new(token: &str, identity: BearerIdentity) -> Self {
Self {
good_token: token.to_string(),
identity,
}
}
}
impl TokenBroker for FixedBroker {
fn resolve<'a>(&'a self, _caller_id: Option<&'a str>) -> ResolveFuture<'a> {
Box::pin(async { Ok(None) })
}
fn resolve_bearer<'a>(&'a self, bearer: &'a str) -> ResolveBearerFuture<'a> {
let good = self.good_token.clone();
let identity = self.identity.clone();
let bearer = bearer.to_string();
Box::pin(async move {
if bearer == good {
Ok(Some(identity))
} else {
Ok(None)
}
})
}
}
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use ed25519_dalek::{Signer, SigningKey};
use serde_json::json;
use std::time::{SystemTime, UNIX_EPOCH};
pub fn signing_key_for_seed(seed: u8) -> SigningKey {
let mut bytes = [0u8; 32];
bytes[0] = seed;
SigningKey::from_bytes(&bytes)
}
pub fn did_key_for(sk: &SigningKey) -> String {
let raw = sk.verifying_key().to_bytes();
let mut prefixed = Vec::with_capacity(34);
prefixed.extend_from_slice(&[0xed, 0x01]);
prefixed.extend_from_slice(&raw);
let mb = multibase::encode(multibase::Base::Base58Btc, &prefixed);
format!("did:key:{mb}")
}
pub fn build_jwt(payload: serde_json::Value, sk: &SigningKey) -> String {
let header = json!({"alg": "EdDSA", "typ": "ucan/1.0+jwt", "ucv": "1.0"});
let h = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap());
let p = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload).unwrap());
let signed = format!("{h}.{p}");
let sig = sk.sign(signed.as_bytes());
let s = URL_SAFE_NO_PAD.encode(sig.to_bytes());
format!("{h}.{p}.{s}")
}
pub fn future_exp() -> i64 {
(SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
+ 3600) as i64
}
pub fn payload_with(
iss: &str,
aud: &str,
caps: &[&str],
prf: &[String],
exp: i64,
) -> serde_json::Value {
json!({
"iss": iss,
"aud": aud,
"sub": iss,
"cmd": "atd-cap",
"args": { "caps": caps, "with": [] },
"nonce": "phase-f-test-nonce",
"exp": exp,
"prf": prf
})
}
pub fn http_config_with_ucan_broker(
require_bearer: bool,
audiences: Vec<(String, String)>,
server_allow_list: Vec<String>,
) -> HttpServerConfig {
let mut broker = atd_runtime::InMemoryTokenBroker::new();
for (did, caller) in audiences {
broker.register_ucan_audience(did, caller);
}
let broker_arc: Arc<dyn atd_runtime::TokenBroker> = Arc::new(broker);
let shared = atd_runtime::dispatch::SharedServerConfig {
token_broker: Some(broker_arc),
granted_capabilities: server_allow_list,
..atd_runtime::dispatch::SharedServerConfig::for_test()
};
HttpServerConfig {
require_bearer,
shared: Arc::new(shared),
..HttpServerConfig::default()
}
}