Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions LINES_OF_CODE.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Language Files Lines Code Comments Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Go 11 3332 2464 397 471
Python 151 32677 25473 1560 5644
TypeScript 9 2407 1352 842 213
Go 13 4292 3144 538 610
Python 154 33765 26371 1613 5781
TypeScript 9 2467 1365 889 213
─────────────────────────────────────────────────────────────────────────────────
Rust 337 85972 70992 4926 10054
|- Markdown 238 23660 565 17413 5682
(Total) 109632 71557 22339 15736
Rust 338 88907 73300 5155 10452
|- Markdown 241 24447 635 17946 5866
(Total) 113354 73935 23101 16318
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total 508 148048 100846 25138 22064
Total 514 153878 104815 26141 22922
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6 changes: 3 additions & 3 deletions binding-core/src/hai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ impl HaiClient {
.get_agent_json()
.map_err(|e| HaiError::RegistrationFailed(e.to_string()))?;

let url = format!("{}/v1/agents/register", self.endpoint);
let url = format!("{}/api/v1/agents/register", self.endpoint);

let request = RegisterRequest { agent_json };

Expand Down Expand Up @@ -470,7 +470,7 @@ impl HaiClient {
.ok_or_else(|| HaiError::InvalidResponse("Agent JSON missing jacsId field".to_string()))?
.to_string();

let url = format!("{}/v1/agents/{}/status", self.endpoint, agent_id);
let url = format!("{}/api/v1/agents/{}/status", self.endpoint, agent_id);

let response = self
.client
Expand Down Expand Up @@ -556,7 +556,7 @@ impl HaiClient {
.ok_or_else(|| HaiError::InvalidResponse("Agent JSON missing jacsId field".to_string()))?
.to_string();

let url = format!("{}/v1/benchmarks/run", self.endpoint);
let url = format!("{}/api/v1/benchmarks/run", self.endpoint);

let request = BenchmarkRequest {
agent_id,
Expand Down
112 changes: 112 additions & 0 deletions binding-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ pub enum ErrorKind {
InvalidArgument,
/// Trust store operation failed
TrustFailed,
/// Network operation failed
NetworkFailed,
/// Key not found
KeyNotFound,
/// Generic failure
Generic,
}
Expand Down Expand Up @@ -105,6 +109,14 @@ impl BindingCoreError {
Self::new(ErrorKind::TrustFailed, message)
}

pub fn network_failed(message: impl Into<String>) -> Self {
Self::new(ErrorKind::NetworkFailed, message)
}

pub fn key_not_found(message: impl Into<String>) -> Self {
Self::new(ErrorKind::KeyNotFound, message)
}

pub fn generic(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Generic, message)
}
Expand Down Expand Up @@ -608,6 +620,106 @@ pub fn handle_config_create() -> BindingResult<()> {
.map_err(|e| BindingCoreError::generic(e.to_string()))
}

// =============================================================================
// Remote Key Fetch Functions
// =============================================================================

/// Information about a public key fetched from HAI key service.
///
/// This struct contains the public key data and metadata returned by
/// the HAI key distribution service.
#[derive(Debug, Clone)]
pub struct RemotePublicKeyInfo {
/// The raw public key bytes (DER encoded).
pub public_key: Vec<u8>,
/// The cryptographic algorithm (e.g., "ed25519", "rsa-pss-sha256").
pub algorithm: String,
/// The hash of the public key (SHA-256).
pub public_key_hash: String,
/// The agent ID the key belongs to.
pub agent_id: String,
/// The version of the key.
pub version: String,
}

/// Fetch a public key from HAI's key distribution service.
///
/// This function retrieves the public key for a specific agent and version
/// from the HAI key distribution service. It is used to obtain trusted public
/// keys for verifying agent signatures without requiring local key storage.
///
/// # Arguments
///
/// * `agent_id` - The unique identifier of the agent whose key to fetch.
/// * `version` - The version of the agent's key to fetch. Use "latest" for
/// the most recent version.
///
/// # Returns
///
/// Returns `Ok(RemotePublicKeyInfo)` containing the public key, algorithm, and hash
/// on success.
///
/// # Errors
///
/// * `ErrorKind::KeyNotFound` - The agent or key version was not found (404).
/// * `ErrorKind::NetworkFailed` - Connection, timeout, or other HTTP errors.
/// * `ErrorKind::Generic` - The returned key has invalid encoding.
///
/// # Environment Variables
///
/// * `HAI_KEYS_BASE_URL` - Base URL for the key service. Defaults to `https://keys.hai.ai`.
/// * `JACS_KEY_RESOLUTION` - Controls key resolution order. Options:
/// - "hai-only" - Only use HAI key service (default when set)
/// - "local-first" - Try local trust store, fall back to HAI
/// - "hai-first" - Try HAI first, fall back to local trust store
///
/// # Example
///
/// ```rust,ignore
/// use jacs_binding_core::fetch_remote_key;
///
/// let key_info = fetch_remote_key(
/// "550e8400-e29b-41d4-a716-446655440000",
/// "latest"
/// )?;
///
/// println!("Algorithm: {}", key_info.algorithm);
/// println!("Hash: {}", key_info.public_key_hash);
/// ```
#[cfg(not(target_arch = "wasm32"))]
pub fn fetch_remote_key(agent_id: &str, version: &str) -> BindingResult<RemotePublicKeyInfo> {
use jacs::agent::loaders::fetch_public_key_from_hai;

let key_info = fetch_public_key_from_hai(agent_id, version).map_err(|e| {
// Map JacsError to appropriate BindingCoreError
let error_str = e.to_string();
if error_str.contains("not found") || error_str.contains("404") {
BindingCoreError::key_not_found(format!(
"Public key not found for agent '{}' version '{}': {}",
agent_id, version, e
))
} else if error_str.contains("network")
|| error_str.contains("connect")
|| error_str.contains("timeout")
{
BindingCoreError::network_failed(format!(
"Failed to fetch public key from HAI: {}",
e
))
} else {
BindingCoreError::generic(format!("Failed to fetch public key: {}", e))
}
})?;

Ok(RemotePublicKeyInfo {
public_key: key_info.public_key,
algorithm: key_info.algorithm,
public_key_hash: key_info.hash,
agent_id: agent_id.to_string(),
version: version.to_string(),
})
}

// =============================================================================
// Re-exports for convenience
// =============================================================================
Expand Down
6 changes: 5 additions & 1 deletion jacs-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ http = []
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main", optional = true }
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main", features = ["server", "transport-io", "macros"], optional = true }
tokio = { version = "1", features = ["rt-multi-thread", "macros"], optional = true }
jacs = { path = "../jacs", default-features = true }
jacs-binding-core = { path = "../binding-core", features = ["hai"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1.0"
uuid = { version = "1", features = ["v4"] }
url = "2"

[dev-dependencies]
assert_cmd = "2"
Expand Down
Loading
Loading