diff --git a/Cargo.lock b/Cargo.lock index 5d9405e..c0ab960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,7 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anda_bot" -version = "0.4.0" +version = "0.4.1" dependencies = [ "agent-twitter-client", "anda_core", @@ -140,7 +140,7 @@ dependencies = [ [[package]] name = "anda_core" -version = "0.4.1" +version = "0.4.4" dependencies = [ "bytes", "candid", @@ -160,7 +160,7 @@ dependencies = [ [[package]] name = "anda_engine" -version = "0.4.3" +version = "0.4.4" dependencies = [ "anda_core", "bytes", @@ -191,7 +191,7 @@ dependencies = [ [[package]] name = "anda_engine_cli" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anda_core", "anda_web3_client", @@ -205,7 +205,7 @@ dependencies = [ [[package]] name = "anda_engine_server" -version = "0.1.0" +version = "0.1.2" dependencies = [ "anda_core", "anda_engine", diff --git a/agents/anda_bot/Cargo.toml b/agents/anda_bot/Cargo.toml index d511fbc..b3af486 100644 --- a/agents/anda_bot/Cargo.toml +++ b/agents/anda_bot/Cargo.toml @@ -3,7 +3,7 @@ name = "anda_bot" description = "I'm Anda ICP, Digital panda 🐼 by Anda framework. Secured in TEE, memories on ICP chain.✨" repository = "https://github.com/ldclabs/anda/tree/main/agents/anda_bot" publish = false -version = "0.4.0" +version = "0.4.1" edition.workspace = true keywords.workspace = true categories.workspace = true diff --git a/agents/anda_bot/nitro_enclave/amd64.Dockerfile b/agents/anda_bot/nitro_enclave/amd64.Dockerfile index 542eeb5..6926b39 100644 --- a/agents/anda_bot/nitro_enclave/amd64.Dockerfile +++ b/agents/anda_bot/nitro_enclave/amd64.Dockerfile @@ -25,7 +25,7 @@ RUN chmod +x ic_tee_daemon RUN wget -O ic_tee_nitro_gateway https://github.com/ldclabs/ic-tee/releases/download/v0.3.0/ic_tee_nitro_gateway RUN chmod +x ic_tee_nitro_gateway -RUN wget -O anda_bot https://github.com/ldclabs/anda/releases/download/v0.4.0/anda_bot +RUN wget -O anda_bot https://github.com/ldclabs/anda/releases/download/v0.4.1/anda_bot RUN chmod +x anda_bot FROM debian:bookworm-slim AS runtime diff --git a/agents/anda_bot/src/twitter.rs b/agents/anda_bot/src/twitter.rs index 194d56b..ed54fb3 100644 --- a/agents/anda_bot/src/twitter.rs +++ b/agents/anda_bot/src/twitter.rs @@ -3,7 +3,10 @@ use anda_core::{ Agent, BoxError, CacheFeatures, CompletionFeatures, Path, PutMode, StateFeatures, StoreFeatures, }; use anda_engine::{ - context::AgentCtx, engine::Engine, extension::character::CharacterAgent, rand_number, + context::{AgentCtx, ANONYMOUS}, + engine::Engine, + extension::character::CharacterAgent, + rand_number, }; use anda_lancedb::knowledge::KnowledgeStore; use ciborium::from_reader; @@ -85,7 +88,7 @@ impl TwitterDaemon { pub async fn run(&self, cancel_token: CancellationToken) -> Result<(), BoxError> { { - let ctx = self.engine.ctx_with(self.agent.as_ref(), None, None)?; + let ctx = self.engine.ctx_with(self.agent.as_ref(), ANONYMOUS, None)?; // load seen_tweet_ids from store let count = self.init_seen_tweet_ids(&ctx).await; @@ -180,8 +183,8 @@ impl TwitterDaemon { log::info!(target: LOG_TARGET, "post new tweet with {} knowledges", knowledges.len()); let ctx = self.engine.ctx_with( self.agent.as_ref(), + ANONYMOUS, Some(self.agent.character.username.clone()), - None, )?; let req = self .agent @@ -213,8 +216,8 @@ impl TwitterDaemon { async fn handle_home_timeline(&self) -> Result<(), BoxError> { let ctx = self.engine.ctx_with( self.agent.as_ref(), + ANONYMOUS, Some(self.agent.character.username.clone()), - None, )?; let mut seen_tweet_ids: Vec = self.get_seen_tweet_ids(&ctx).await; @@ -299,7 +302,7 @@ impl TwitterDaemon { } let ctx = self .engine - .ctx_with(self.agent.as_ref(), Some(tweet_user.clone()), None)?; + .ctx_with(self.agent.as_ref(), ANONYMOUS, Some(tweet_user.clone()))?; let mut seen_tweet_ids: Vec = self.get_seen_tweet_ids(&ctx).await; if seen_tweet_ids.contains(&tweet_id) { diff --git a/anda_core/Cargo.toml b/anda_core/Cargo.toml index 6e52d34..11605e5 100644 --- a/anda_core/Cargo.toml +++ b/anda_core/Cargo.toml @@ -3,7 +3,7 @@ name = "anda_core" description = "Core types and traits for Anda -- an AI agent framework built with Rust, powered by ICP and TEEs." repository = "https://github.com/ldclabs/anda/tree/main/anda_core" publish = true -version = "0.4.1" +version = "0.4.4" edition.workspace = true keywords.workspace = true categories.workspace = true diff --git a/anda_core/src/context.rs b/anda_core/src/context.rs index 56a0cbe..c3cb1fe 100644 --- a/anda_core/src/context.rs +++ b/anda_core/src/context.rs @@ -112,17 +112,17 @@ pub trait BaseContext: pub trait StateFeatures: Sized { /// Gets the engine ID, which comes from the TEE host fn id(&self) -> Principal; + /// Gets the verified caller principal if available. + /// A non anonymous Principal indicates the request has been verified + /// using ICP blockchain's signature verification algorithm. + fn caller(&self) -> Principal; + /// Gets the username from request context. /// Note: This is not verified and should not be used as a trusted identifier. /// For example, if triggered by a bot of X platform, this might be the username /// of the user interacting with the bot. fn user(&self) -> Option; - /// Gets the verified caller principal if available. - /// A non-None value indicates the request has been verified - /// using ICP blockchain's signature verification algorithm. - fn caller(&self) -> Option; - /// Gets the cancellation token for the current execution context. /// Each call level has its own token scope. /// For example, when an agent calls a tool, the tool receives diff --git a/anda_engine/Cargo.toml b/anda_engine/Cargo.toml index 32b88b4..1f7a45f 100644 --- a/anda_engine/Cargo.toml +++ b/anda_engine/Cargo.toml @@ -3,7 +3,7 @@ name = "anda_engine" description = "Agents engine for Anda -- an AI agent framework built with Rust, powered by ICP and TEEs." repository = "https://github.com/ldclabs/anda/tree/main/anda_engine" publish = true -version = "0.4.3" +version = "0.4.4" edition.workspace = true keywords.workspace = true categories.workspace = true diff --git a/anda_engine/src/context/agent.rs b/anda_engine/src/context/agent.rs index fc7fd9f..1280849 100644 --- a/anda_engine/src/context/agent.rs +++ b/anda_engine/src/context/agent.rs @@ -105,13 +105,13 @@ impl AgentCtx { pub(crate) fn child_with( &self, agent_name: &str, + caller: Principal, user: Option, - caller: Option, ) -> Result { Ok(Self { base: self .base - .child_with(format!("A:{}", agent_name), user, caller)?, + .child_with(format!("A:{}", agent_name), caller, user)?, model: self.model.clone(), tools: self.tools.clone(), agents: self.agents.clone(), @@ -122,16 +122,17 @@ impl AgentCtx { /// /// # Arguments /// * `tool_name` - Name of the tool - /// * `user` - Optional user identifier /// * `caller` - Optional caller principal + /// * `user` - Optional user identifier + /// pub(crate) fn child_base_with( &self, tool_name: &str, + caller: Principal, user: Option, - caller: Option, ) -> Result { self.base - .child_with(format!("T:{}", tool_name), user, caller) + .child_with(format!("T:{}", tool_name), caller, user) } } @@ -355,19 +356,21 @@ impl EmbeddingFeatures for AgentCtx { impl BaseContext for AgentCtx {} impl StateFeatures for AgentCtx { + /// agent ID fn id(&self) -> Principal { self.base.id() } + + /// caller ID + fn caller(&self) -> Principal { + self.base.caller() + } + /// Gets the current user identifier, if available fn user(&self) -> Option { self.base.user() } - /// Gets the current caller principal, if available - fn caller(&self) -> Option { - self.base.caller() - } - /// Gets the cancellation token for the current context fn cancellation_token(&self) -> CancellationToken { self.base.cancellation_token() diff --git a/anda_engine/src/context/base.rs b/anda_engine/src/context/base.rs index 0cbfafb..a156866 100644 --- a/anda_engine/src/context/base.rs +++ b/anda_engine/src/context/base.rs @@ -43,6 +43,8 @@ use std::{ const CONTEXT_MAX_DEPTH: u8 = 42; const CACHE_MAX_CAPACITY: u64 = 1000000; +pub const ANONYMOUS: Principal = Principal::anonymous(); + use super::{ cache::CacheService, web3::{Web3Client, Web3SDK}, @@ -52,8 +54,8 @@ use crate::store::Store; #[derive(Clone)] pub struct BaseCtx { pub(crate) id: Principal, + pub(crate) caller: Principal, pub(crate) user: Option, - pub(crate) caller: Option, pub(crate) path: Path, pub(crate) cancellation_token: CancellationToken, pub(crate) start_at: Instant, @@ -93,7 +95,7 @@ impl BaseCtx { Self { id, user: None, - caller: None, + caller: ANONYMOUS, path: Path::default(), cancellation_token, start_at: Instant::now(), @@ -145,22 +147,22 @@ impl BaseCtx { /// /// # Arguments /// * `path` - New path for the child context + /// * `caller` - caller principal (or ANONYMOUS) /// * `user` - Optional user identifier - /// * `caller` - Optional caller principal /// /// # Errors /// Returns an error if the context depth exceeds CONTEXT_MAX_DEPTH pub(crate) fn child_with( &self, path: String, + caller: Principal, user: Option, - caller: Option, ) -> Result { let path = Path::parse(path)?; let child = Self { id: self.id, - user, caller, + user, path, cancellation_token: self.cancellation_token.child_token(), start_at: Instant::now(), @@ -184,12 +186,12 @@ impl StateFeatures for BaseCtx { self.id } - fn user(&self) -> Option { - self.user.clone() + fn caller(&self) -> Principal { + self.caller } - fn caller(&self) -> Option { - self.caller + fn user(&self) -> Option { + self.user.clone() } fn cancellation_token(&self) -> CancellationToken { diff --git a/anda_engine/src/engine.rs b/anda_engine/src/engine.rs index e525c76..999cdf5 100644 --- a/anda_engine/src/engine.rs +++ b/anda_engine/src/engine.rs @@ -122,8 +122,8 @@ impl Engine { pub fn ctx_with( &self, agent: &A, + caller: Principal, user: Option, - caller: Option, ) -> Result where A: Agent, @@ -132,11 +132,12 @@ impl Engine { if !self.ctx.agents.contains(&name) { return Err(format!("agent {} not found", name).into()); } + if let Some(user) = &user { validate_path_part(user)?; } - self.ctx.child_with(&name, user, caller) + self.ctx.child_with(&name, caller, user) } /// Executes an agent with the specified parameters. @@ -147,8 +148,8 @@ impl Engine { agent_name: Option, prompt: String, attachment: Option, + caller: Principal, user: Option, - caller: Option, ) -> Result { let name = agent_name.unwrap_or(self.default_agent.clone()); if !self.ctx.agents.contains(&name) { @@ -159,7 +160,7 @@ impl Engine { validate_path_part(user)?; } - let ctx = self.ctx.child_with(&name, user, caller)?; + let ctx = self.ctx.child_with(&name, caller, user)?; let mut res = self .ctx .agents @@ -175,8 +176,8 @@ impl Engine { &self, name: String, args: String, + caller: Principal, user: Option, - caller: Option, ) -> Result<(String, bool), BoxError> { if !self.ctx.tools.contains(&name) { return Err(format!("tool {} not found", name).into()); @@ -186,7 +187,7 @@ impl Engine { validate_path_part(user)?; } - let ctx = self.ctx.child_base_with(&name, user, caller)?; + let ctx = self.ctx.child_base_with(&name, caller, user)?; self.ctx.tools.call(&name, ctx, args).await } diff --git a/anda_engine_cli/Cargo.toml b/anda_engine_cli/Cargo.toml index 8cdf64c..064808b 100644 --- a/anda_engine_cli/Cargo.toml +++ b/anda_engine_cli/Cargo.toml @@ -3,7 +3,7 @@ name = "anda_engine_cli" description = "The command line interface for Anda engine server." repository = "https://github.com/ldclabs/anda/tree/main/anda_engine_cli" publish = false # can't publish this crate because `anda_web3_client` is not published -version = "0.1.1" +version = "0.1.2" edition.workspace = true keywords.workspace = true categories.workspace = true diff --git a/anda_engine_server/Cargo.toml b/anda_engine_server/Cargo.toml index a5c51f6..7652bdc 100644 --- a/anda_engine_server/Cargo.toml +++ b/anda_engine_server/Cargo.toml @@ -3,7 +3,7 @@ name = "anda_engine_server" description = "A http server to serve multiple Anda engines." repository = "https://github.com/ldclabs/anda/tree/main/anda_engine_server" publish = false -version = "0.1.0" +version = "0.1.2" edition.workspace = true keywords.workspace = true categories.workspace = true diff --git a/anda_engine_server/src/handler.rs b/anda_engine_server/src/handler.rs index d898811..5314372 100644 --- a/anda_engine_server/src/handler.rs +++ b/anda_engine_server/src/handler.rs @@ -103,7 +103,7 @@ async fn engine_run( from_reader(req.params.as_slice()) .map_err(|err| format!("failed to decode params: {err:?}"))?; let res = engine - .agent_run(args.0, args.1, args.2, None, Some(caller)) + .agent_run(args.0, args.1, args.2, caller, None) .await .map_err(|err| format!("failed to run agent: {err:?}"))?; Ok(to_cbor_bytes(&res).into()) @@ -112,7 +112,7 @@ async fn engine_run( let args: (String, String) = from_reader(req.params.as_slice()) .map_err(|err| format!("failed to decode params: {err:?}"))?; let res = engine - .tool_call(args.0, args.1, None, Some(caller)) + .tool_call(args.0, args.1, caller, None) .await .map_err(|err| format!("failed to call tool: {err:?}"))?; Ok(to_cbor_bytes(&res).into()) diff --git a/examples/icp_ledger_agent/src/agent.rs b/examples/icp_ledger_agent/src/agent.rs index 0fdf577..9baf005 100644 --- a/examples/icp_ledger_agent/src/agent.rs +++ b/examples/icp_ledger_agent/src/agent.rs @@ -2,7 +2,7 @@ use anda_core::{ Agent, AgentContext, AgentOutput, BoxError, CanisterCaller, CompletionFeatures, CompletionRequest, StateFeatures, ToolSet, }; -use anda_engine::context::{AgentCtx, BaseCtx}; +use anda_engine::context::{AgentCtx, BaseCtx, ANONYMOUS}; use anda_icp::ledger::{BalanceOfTool, ICPLedgers, TransferTool}; use candid::Principal; use std::{collections::BTreeSet, sync::Arc}; @@ -87,7 +87,11 @@ impl Agent for ICPLedgerAgent { prompt: String, _attachment: Option>, ) -> Result { - let caller = ctx.caller().ok_or("missing caller")?; + let caller = ctx.caller(); + if caller == ANONYMOUS { + return Err("anonymous caller not allowed".into()); + } + let req = CompletionRequest { system: Some( "\