Skip to content

Commit

Permalink
feat: implement ICP tokens transfer tool
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Jan 19, 2025
1 parent f3b6ef4 commit 599e616
Show file tree
Hide file tree
Showing 9 changed files with 433 additions and 10 deletions.
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["anda_core", "anda_engine", "anda_lancedb", "agents/*"]
members = ["anda_core", "anda_engine", "anda_lancedb", "agents/*", "tools/*"]

[workspace.package]
description = "Anda is a framework for AI agent development, designed to build a highly composable, autonomous, and perpetually memorizing network of AI agents."
Expand All @@ -17,6 +17,7 @@ edition = "2021"
license = "MIT OR Apache-2.0"

[workspace.dependencies]
async-trait = "0.1"
axum = { version = "0.8", features = [
"http1",
"http2",
Expand Down Expand Up @@ -65,6 +66,7 @@ ed25519-consensus = "2.1"
log = "0.4"
chrono = "0.4"
dotenv = "0.15"
schemars = { version = "0.8" }

[profile.release]
debug = false
Expand Down
12 changes: 6 additions & 6 deletions anda_core/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub trait Tool<C>: Send + Sync
where
C: BaseContext + Send + Sync,
{
/// A constant flag indicating whether the agent should continue processing the tool result
/// with completion model after execution
const CONTINUE: bool;
/// The arguments type of the tool.
type Args: DeserializeOwned + Send;
Expand All @@ -27,6 +29,9 @@ where
/// - Can only contain: lowercase letters (a-z), digits (0-9), and underscores (_)
fn name(&self) -> String;

/// Returns the tool's capabilities description in a short string
fn description(&self) -> String;

/// Provides the tool's definition including its parameters schema.
///
/// # Returns
Expand Down Expand Up @@ -74,12 +79,7 @@ where
args: String,
) -> impl Future<Output = Result<(String, bool), BoxError>> + Send {
async move {
let args: Self::Args = serde_json::from_str(&args)
.map_err(|err| format!("tool {}, invalid args: {}", self.name(), err))?;
let result = self
.call(ctx, args)
.await
.map_err(|err| format!("tool {}, call failed: {}", self.name(), err))?;
let result = self.call_string(ctx, args).await?;
Ok((serde_json::to_string(&result)?, Self::CONTINUE))
}
}
Expand Down
2 changes: 1 addition & 1 deletion anda_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ toml = { workspace = true }
tokio = { workspace = true }
log = { workspace = true }
chrono = { workspace = true }
schemars = { version = "0.8" }
schemars = { workspace = true }

[dev-dependencies]
dotenv = { workspace = true }
152 changes: 152 additions & 0 deletions anda_engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,155 @@ pub use agent::*;
pub use base::*;
pub use cache::*;
pub use tee::*;

pub mod mock {
use anda_core::{BoxError, CanisterCaller};
use candid::{encode_args, utils::ArgumentEncoder, CandidType, Decode, Principal};

/// A mock implementation of CanisterCaller for testing purposes.
///
/// This struct allows you to simulate canister calls by providing a transformation function
/// that takes the canister ID, method name, and arguments, and returns a response.
///
/// # Example
/// ```rust
/// use anda_engine::context::mock::MockCanisterCaller;
/// use anda_core::CanisterCaller;
/// use candid::{encode_args, CandidType, Deserialize, Principal};
///
/// #[derive(CandidType, Deserialize, Debug, PartialEq)]
/// struct TestResponse {
/// canister: Principal,
/// method: String,
/// args: Vec<u8>,
/// }
///
/// #[tokio::test]
/// async fn test_mock_canister_caller() {
/// let canister_id = Principal::anonymous();
/// let empty_args = encode_args(()).unwrap();
///
/// let caller = MockCanisterCaller::new(|canister, method, args| {
/// let response = TestResponse {
/// canister: canister.clone(),
/// method: method.to_string(),
/// args,
/// };
/// candid::encode_args((response,)).unwrap()
/// });
///
/// let res: TestResponse = caller
/// .canister_query(&canister_id, "canister_query", ())
/// .await
/// .unwrap();
/// assert_eq!(res.canister, canister_id);
/// assert_eq!(res.method, "canister_query");
/// assert_eq!(res.args, empty_args);
///
/// let res: TestResponse = caller
/// .canister_update(&canister_id, "canister_update", ())
/// .await
/// .unwrap();
/// assert_eq!(res.canister, canister_id);
/// assert_eq!(res.method, "canister_update");
/// assert_eq!(res.args, empty_args);
/// }
/// ```
pub struct MockCanisterCaller<F: Fn(&Principal, &str, Vec<u8>) -> Vec<u8> + Send + Sync> {
transform: F,
}

impl<F> MockCanisterCaller<F>
where
F: Fn(&Principal, &str, Vec<u8>) -> Vec<u8> + Send + Sync,
{
/// Creates a new MockCanisterCaller with the provided transformation function.
///
/// # Arguments
/// * `transform` - A function that takes (canister_id, method_name, args) and returns
/// a serialized response
pub fn new(transform: F) -> Self {
Self { transform }
}
}

impl<F> CanisterCaller for MockCanisterCaller<F>
where
F: Fn(&Principal, &str, Vec<u8>) -> Vec<u8> + Send + Sync,
{
async fn canister_query<
In: ArgumentEncoder + Send,
Out: CandidType + for<'a> candid::Deserialize<'a>,
>(
&self,
canister: &Principal,
method: &str,
args: In,
) -> Result<Out, BoxError> {
let args = encode_args(args)?;
let res = (self.transform)(canister, method, args);
let output = Decode!(res.as_slice(), Out)?;
Ok(output)
}

async fn canister_update<
In: ArgumentEncoder + Send,
Out: CandidType + for<'a> candid::Deserialize<'a>,
>(
&self,
canister: &Principal,
method: &str,
args: In,
) -> Result<Out, BoxError> {
let args = encode_args(args)?;
let res = (self.transform)(canister, method, args);
let output = Decode!(res.as_slice(), Out)?;
Ok(output)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use anda_core::CanisterCaller;
use candid::{encode_args, CandidType, Deserialize, Principal};

#[derive(CandidType, Deserialize, Debug, PartialEq)]
struct TestResponse {
canister: Principal,
method: String,
args: Vec<u8>,
}

#[tokio::test(flavor = "current_thread")]
async fn test_mock_canister_caller() {
let canister_id = Principal::anonymous();
let empty_args = encode_args(()).unwrap();

let caller = mock::MockCanisterCaller::new(|canister, method, args| {
let response = TestResponse {
canister: *canister,
method: method.to_string(),
args,
};
candid::encode_args((response,)).unwrap()
});

let res: TestResponse = caller
.canister_query(&canister_id, "canister_query", ())
.await
.unwrap();
assert_eq!(res.canister, canister_id);
assert_eq!(res.method, "canister_query");
assert_eq!(res.args, empty_args);

let res: TestResponse = caller
.canister_update(&canister_id, "canister_update", ())
.await
.unwrap();
assert_eq!(res.canister, canister_id);
assert_eq!(res.method, "canister_update");
assert_eq!(res.args, empty_args);
}
}
7 changes: 5 additions & 2 deletions anda_engine/src/extension/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,14 @@ where
format!("submit_{}", self.name)
}

fn description(&self) -> String {
"Submit the structured data you extracted from the provided text.".to_string()
}

fn definition(&self) -> FunctionDefinition {
FunctionDefinition {
name: self.name(),
description: "Submit the structured data you extracted from the provided text."
.to_string(),
description: self.description(),
parameters: self.schema.clone(),
strict: Some(true),
}
Expand Down
41 changes: 41 additions & 0 deletions tools/anda_icp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[package]
name = "anda_icp"
description = "Anda agent tools offers integration with the Internet Computer (ICP)."
repository = "https://github.com/ldclabs/anda/tree/main/tools/anda_icp"
publish = true
version = "0.1.0"
edition.workspace = true
keywords.workspace = true
categories.workspace = true
license.workspace = true

[dependencies]
anda_core = { path = "../../anda_core", version = "0.3" }
anda_engine = { path = "../../anda_engine", version = "0.3" }
async-trait = { workspace = true }
candid = { workspace = true }
bytes = { workspace = true }
ciborium = { workspace = true }
futures = { workspace = true }
futures-util = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_bytes = { workspace = true }
http = { workspace = true }
object_store = { workspace = true }
ic_cose = { workspace = true }
ic_cose_types = { workspace = true }
tokio-util = { workspace = true }
structured-logger = { workspace = true }
reqwest = { workspace = true }
rand = { workspace = true }
moka = { workspace = true }
toml = { workspace = true }
tokio = { workspace = true }
log = { workspace = true }
chrono = { workspace = true }
schemars = { workspace = true }
icrc-ledger-types = "0.1"

[dev-dependencies]
dotenv = { workspace = true }
Loading

0 comments on commit 599e616

Please sign in to comment.