Skip to main content

anda_core/
lib.rs

1//! Core traits, data models, and utility helpers for the Anda agent framework.
2//!
3//! `anda_core` defines the stable interfaces shared by Anda runtimes,
4//! agents, tools, model adapters, and clients. It intentionally keeps runtime
5//! orchestration and provider integrations out of this crate; those pieces are
6//! implemented by higher-level crates such as `anda_engine`.
7//!
8//! The main building blocks are:
9//! - [`Agent`] and [`AgentSet`] for registering and running AI agents.
10//! - [`Tool`] and [`ToolSet`] for type-safe tool implementations.
11//! - [`BaseContext`] and [`AgentContext`] for execution capabilities.
12//! - [`Message`], [`ContentPart`], [`CompletionRequest`], and related model
13//!   types for LLM provider adapters.
14//! - HTTP and CBOR/Candid RPC helpers for remote engine and canister calls.
15
16use object_store::path::DELIMITER;
17use std::{future::Future, pin::Pin};
18
19pub mod agent;
20pub mod context;
21pub mod http;
22pub mod json;
23pub mod model;
24pub mod tool;
25
26pub use agent::*;
27pub use context::*;
28pub use http::*;
29pub use json::*;
30pub use model::*;
31pub use tool::*;
32
33/// A type alias for a boxed error that is thread-safe and sendable across threads.
34/// This is commonly used as a return type for functions that can return various error types.
35pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
36
37/// A type alias for a boxed future that is thread-safe and sendable across threads.
38pub type BoxPinFut<T> = Pin<Box<dyn Future<Output = T> + Send>>;
39
40/// Returns a lowercase copy of an object-store path.
41pub fn path_lowercase(path: &Path) -> Path {
42    let mut path = path.to_string();
43    path.make_ascii_lowercase();
44    path.into()
45}
46
47/// Joins two object-store paths without percent-encoding and lowercases the result.
48///
49/// Root paths are treated as empty namespaces: joining root with `b` returns
50/// `b`, and joining `a` with root returns `a`.
51pub fn path_join(a: &Path, b: &Path) -> Path {
52    let mut path = if a.is_root() {
53        b.to_string()
54    } else if b.is_root() {
55        a.to_string()
56    } else {
57        format!("{}{}{}", a, DELIMITER, b)
58    };
59    path.make_ascii_lowercase();
60    path.into()
61}
62
63/// Validates a single path component used in agent, tool, or user namespaces.
64///
65/// The value must be non-empty, must not contain the object-store path
66/// delimiter, and must round-trip through [`Path`] without normalization.
67pub fn validate_path_part(part: &str) -> Result<(), BoxError> {
68    if part.is_empty() || part.contains(DELIMITER) || Path::from(part).as_ref() != part {
69        return Err(format!("invalid path part: {}", part).into());
70    }
71
72    Ok(())
73}
74
75/// Validates an agent or tool function name.
76///
77/// # Rules
78/// - Must not be empty
79/// - Must not exceed 64 characters
80/// - Must start with a lowercase letter
81/// - Can only contain: lowercase letters (a-z), digits (0-9), and underscores (_)
82pub fn validate_function_name(name: &str) -> Result<(), BoxError> {
83    if name.is_empty() {
84        return Err("empty string".into());
85    }
86
87    if name.len() > 64 {
88        return Err("string length exceeds the limit 64".into());
89    }
90
91    let mut iter = name.chars();
92    if !matches!(iter.next(), Some('a'..='z')) {
93        return Err("name must start with a lowercase letter".into());
94    }
95
96    for c in iter {
97        if !matches!(c, 'a'..='z' | '0'..='9' | '_' ) {
98            return Err(format!("invalid character: {}", c).into());
99        }
100    }
101    Ok(())
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_path_lowercase() {
110        let a = Path::from("a/Foo");
111        assert_eq!(path_lowercase(&a).as_ref(), "a/foo");
112    }
113
114    #[test]
115    fn test_validate_path_part() {
116        assert!(validate_path_part("foo").is_ok());
117        assert!(validate_path_part("fOO").is_ok());
118        assert!(validate_path_part("").is_err());
119        assert!(validate_path_part("foo/").is_err());
120        assert!(validate_path_part("/foo").is_err());
121        assert!(validate_path_part("foo/bar").is_err());
122        assert!(validate_path_part("foo/bar/").is_err());
123    }
124}