Skip to main content

adk_tool/
simple_context.rs

1//! Lightweight [`ToolContext`] implementation for use outside the agent loop.
2//!
3//! [`SimpleToolContext`] provides sensible defaults for all trait methods so
4//! that callers in MCP server mode, testing, or sub-agent delegation can
5//! invoke tools without constructing a full invocation context.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use adk_tool::SimpleToolContext;
11//! use std::sync::Arc;
12//!
13//! let ctx = SimpleToolContext::new("my-caller");
14//! let ctx: Arc<dyn adk_core::ToolContext> = Arc::new(ctx);
15//! ```
16
17use adk_core::context::{Artifacts, CallbackContext, MemoryEntry, ReadonlyContext};
18use adk_core::types::Content;
19use adk_core::{EventActions, Result, ToolContext};
20use async_trait::async_trait;
21use std::sync::{Arc, Mutex};
22
23/// A lightweight [`ToolContext`] with sensible defaults for non-agent callers.
24///
25/// Implements [`ReadonlyContext`], [`CallbackContext`], and [`ToolContext`]
26/// with minimal configuration. Construct via [`SimpleToolContext::new`] with
27/// a caller name; all other fields use safe defaults.
28pub struct SimpleToolContext {
29    caller_name: String,
30    session_id: String,
31    invocation_id: String,
32    function_call_id: String,
33    user_content: Content,
34    actions: Mutex<EventActions>,
35}
36
37impl SimpleToolContext {
38    /// Create a new context with the given caller name.
39    ///
40    /// Generates unique UUIDs for `invocation_id` and `function_call_id`.
41    /// The caller name is returned by both [`agent_name()`](ReadonlyContext::agent_name)
42    /// and [`app_name()`](ReadonlyContext::app_name).
43    pub fn new(caller_name: impl Into<String>) -> Self {
44        Self {
45            caller_name: caller_name.into(),
46            session_id: String::new(),
47            invocation_id: uuid::Uuid::new_v4().to_string(),
48            function_call_id: uuid::Uuid::new_v4().to_string(),
49            user_content: Content::new("user"),
50            actions: Mutex::new(EventActions::default()),
51        }
52    }
53
54    /// Override the default function call ID.
55    ///
56    /// By default a UUID is generated at construction. Use this builder
57    /// method to provide a specific ID instead.
58    pub fn with_function_call_id(mut self, id: impl Into<String>) -> Self {
59        self.function_call_id = id.into();
60        self
61    }
62
63    /// Attach the session that owns this tool call.
64    ///
65    /// This is useful for desktop shells, test harnesses, and other callers
66    /// that execute tools outside the full agent loop but still need
67    /// session-aware callbacks such as MCP elicitation.
68    pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
69        self.session_id = id.into();
70        self
71    }
72}
73
74#[async_trait]
75impl ReadonlyContext for SimpleToolContext {
76    fn invocation_id(&self) -> &str {
77        &self.invocation_id
78    }
79
80    fn agent_name(&self) -> &str {
81        &self.caller_name
82    }
83
84    fn user_id(&self) -> &str {
85        "anonymous"
86    }
87
88    fn app_name(&self) -> &str {
89        &self.caller_name
90    }
91
92    fn session_id(&self) -> &str {
93        &self.session_id
94    }
95
96    fn branch(&self) -> &str {
97        ""
98    }
99
100    fn user_content(&self) -> &Content {
101        &self.user_content
102    }
103}
104
105#[async_trait]
106impl CallbackContext for SimpleToolContext {
107    fn artifacts(&self) -> Option<Arc<dyn Artifacts>> {
108        None
109    }
110}
111
112#[async_trait]
113impl ToolContext for SimpleToolContext {
114    fn function_call_id(&self) -> &str {
115        &self.function_call_id
116    }
117
118    fn actions(&self) -> EventActions {
119        self.actions.lock().unwrap_or_else(|e| e.into_inner()).clone()
120    }
121
122    fn set_actions(&self, actions: EventActions) {
123        *self.actions.lock().unwrap_or_else(|e| e.into_inner()) = actions;
124    }
125
126    async fn search_memory(&self, _query: &str) -> Result<Vec<MemoryEntry>> {
127        Ok(vec![])
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn session_id_is_empty_by_default() {
137        let context = SimpleToolContext::new("test");
138        assert_eq!(ReadonlyContext::session_id(&context), "");
139    }
140
141    #[test]
142    fn session_id_can_be_attached_for_out_of_loop_tool_calls() {
143        let context = SimpleToolContext::new("desktop").with_session_id("session-123");
144        assert_eq!(ReadonlyContext::session_id(&context), "session-123");
145    }
146}