Skip to main content

adk_core/
context.rs

1use crate::identity::{AdkIdentity, AppName, ExecutionIdentity, InvocationId, SessionId, UserId};
2use crate::{AdkError, Agent, Result, types::Content};
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::{BTreeSet, HashMap};
7use std::sync::Arc;
8
9/// Policy for handling excess tool calls when the concurrency limit is reached.
10///
11/// Determines whether tool calls that exceed the configured concurrency limit
12/// should wait in a queue or fail immediately.
13///
14/// # Example
15///
16/// ```rust
17/// use adk_core::BackpressurePolicy;
18///
19/// // Default is Queue
20/// let policy = BackpressurePolicy::default();
21/// assert!(matches!(policy, BackpressurePolicy::Queue));
22/// ```
23#[derive(Debug, Clone, Default, PartialEq, Eq)]
24pub enum BackpressurePolicy {
25    /// Queue excess calls until a permit becomes available.
26    ///
27    /// This is the default policy. Tool calls will await until a semaphore
28    /// permit is released by a completing tool execution.
29    #[default]
30    Queue,
31
32    /// Fail immediately with a concurrency limit error when no permit is available.
33    ///
34    /// Use this when latency is more important than throughput — callers receive
35    /// an immediate error rather than waiting indefinitely.
36    Fail,
37}
38
39/// Configuration for tool execution concurrency.
40///
41/// Controls how many tool calls can execute simultaneously, with support for
42/// global limits, per-tool overrides, and configurable backpressure behavior.
43///
44/// # Example
45///
46/// ```rust
47/// use adk_core::{BackpressurePolicy, ToolConcurrencyConfig};
48/// use std::collections::HashMap;
49///
50/// let config = ToolConcurrencyConfig {
51///     max_concurrency: Some(10),
52///     per_tool: HashMap::from([
53///         ("web_scraper".to_string(), 2),
54///         ("calculator".to_string(), 8),
55///     ]),
56///     backpressure: BackpressurePolicy::Fail,
57/// };
58///
59/// assert_eq!(config.max_concurrency, Some(10));
60/// assert_eq!(config.per_tool.get("web_scraper"), Some(&2));
61/// ```
62#[derive(Debug, Clone, Default)]
63pub struct ToolConcurrencyConfig {
64    /// Global maximum concurrent tool calls. `None` means unlimited.
65    pub max_concurrency: Option<usize>,
66
67    /// Per-tool concurrency overrides. When a tool name is present in this map,
68    /// its individual limit takes precedence over the global `max_concurrency`.
69    pub per_tool: HashMap<String, usize>,
70
71    /// What to do when the concurrency limit is reached.
72    pub backpressure: BackpressurePolicy,
73}
74
75/// Read-only access to invocation metadata.
76///
77/// Provides identity information (user, app, session, invocation) and the
78/// current user content. Implemented by all context types.
79#[async_trait]
80pub trait ReadonlyContext: Send + Sync {
81    /// Returns the current invocation identifier.
82    fn invocation_id(&self) -> &str;
83    /// Returns the name of the currently executing agent.
84    fn agent_name(&self) -> &str;
85    /// Returns the user identifier for this session.
86    fn user_id(&self) -> &str;
87    /// Returns the application name for this session.
88    fn app_name(&self) -> &str;
89    /// Returns the session identifier.
90    fn session_id(&self) -> &str;
91    /// Returns the current conversation branch.
92    fn branch(&self) -> &str;
93    /// Returns the user's input content for this invocation.
94    fn user_content(&self) -> &Content;
95
96    /// Returns the application name as a typed [`AppName`].
97    ///
98    /// Parses the value returned by [`app_name()`](Self::app_name). Returns an
99    /// error if the raw string fails validation (empty, null bytes, or exceeds
100    /// the maximum length).
101    ///
102    /// # Errors
103    ///
104    /// Returns an error when the
105    /// underlying string is not a valid identifier.
106    fn try_app_name(&self) -> Result<AppName> {
107        Ok(AppName::try_from(self.app_name())?)
108    }
109
110    /// Returns the user identifier as a typed [`UserId`].
111    ///
112    /// Parses the value returned by [`user_id()`](Self::user_id). Returns an
113    /// error if the raw string fails validation.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error when the
118    /// underlying string is not a valid identifier.
119    fn try_user_id(&self) -> Result<UserId> {
120        Ok(UserId::try_from(self.user_id())?)
121    }
122
123    /// Returns the session identifier as a typed [`SessionId`].
124    ///
125    /// Parses the value returned by [`session_id()`](Self::session_id).
126    /// Returns an error if the raw string fails validation.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error when the
131    /// underlying string is not a valid identifier.
132    fn try_session_id(&self) -> Result<SessionId> {
133        Ok(SessionId::try_from(self.session_id())?)
134    }
135
136    /// Returns the invocation identifier as a typed [`InvocationId`].
137    ///
138    /// Parses the value returned by [`invocation_id()`](Self::invocation_id).
139    /// Returns an error if the raw string fails validation.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error when the
144    /// underlying string is not a valid identifier.
145    fn try_invocation_id(&self) -> Result<InvocationId> {
146        Ok(InvocationId::try_from(self.invocation_id())?)
147    }
148
149    /// Returns the stable session-scoped [`AdkIdentity`] triple.
150    ///
151    /// Combines [`try_app_name()`](Self::try_app_name),
152    /// [`try_user_id()`](Self::try_user_id), and
153    /// [`try_session_id()`](Self::try_session_id) into a single composite
154    /// identity value.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if any of the three constituent identifiers fail
159    /// validation.
160    fn try_identity(&self) -> Result<AdkIdentity> {
161        Ok(AdkIdentity {
162            app_name: self.try_app_name()?,
163            user_id: self.try_user_id()?,
164            session_id: self.try_session_id()?,
165        })
166    }
167
168    /// Returns the full per-invocation [`ExecutionIdentity`].
169    ///
170    /// Combines [`try_identity()`](Self::try_identity) with the invocation,
171    /// branch, and agent name from this context.
172    ///
173    /// # Errors
174    ///
175    /// Returns an error if any of the four typed identifiers fail validation.
176    fn try_execution_identity(&self) -> Result<ExecutionIdentity> {
177        Ok(ExecutionIdentity {
178            adk: self.try_identity()?,
179            invocation_id: self.try_invocation_id()?,
180            branch: self.branch().to_string(),
181            agent_name: self.agent_name().to_string(),
182        })
183    }
184}
185
186// State management traits
187
188/// Maximum allowed length for state keys (256 bytes).
189pub const MAX_STATE_KEY_LEN: usize = 256;
190
191/// Validates a state key. Returns `Ok(())` if the key is safe, or an error message.
192///
193/// Rules:
194/// - Must not be empty
195/// - Must not exceed [`MAX_STATE_KEY_LEN`] bytes
196/// - Must not contain path separators (`/`, `\`) or `..`
197/// - Must not contain null bytes
198pub fn validate_state_key(key: &str) -> std::result::Result<(), &'static str> {
199    if key.is_empty() {
200        return Err("state key must not be empty");
201    }
202    if key.len() > MAX_STATE_KEY_LEN {
203        return Err("state key exceeds maximum length of 256 bytes");
204    }
205    if key.contains('/') || key.contains('\\') || key.contains("..") {
206        return Err("state key must not contain path separators or '..'");
207    }
208    if key.contains('\0') {
209        return Err("state key must not contain null bytes");
210    }
211    Ok(())
212}
213
214/// Mutable session state with key-value storage.
215///
216/// Implementations persist state across turns within a session.
217pub trait State: Send + Sync {
218    /// Returns the value for the given key, or `None` if not present.
219    fn get(&self, key: &str) -> Option<Value>;
220    /// Set a state value. Implementations should call [`validate_state_key`] and
221    /// reject invalid keys (e.g., by logging a warning or panicking).
222    fn set(&mut self, key: String, value: Value);
223    /// Returns all key-value pairs in the state.
224    fn all(&self) -> HashMap<String, Value>;
225}
226
227/// Read-only view of session state.
228pub trait ReadonlyState: Send + Sync {
229    /// Returns the value for the given key, or `None` if not present.
230    fn get(&self, key: &str) -> Option<Value>;
231    /// Returns all key-value pairs in the state.
232    fn all(&self) -> HashMap<String, Value>;
233}
234
235// Session trait
236/// Represents an active conversation session with identity and state.
237pub trait Session: Send + Sync {
238    /// Returns the session identifier.
239    fn id(&self) -> &str;
240    /// Returns the application name this session belongs to.
241    fn app_name(&self) -> &str;
242    /// Returns the user identifier for this session.
243    fn user_id(&self) -> &str;
244    /// Returns the mutable state associated with this session.
245    fn state(&self) -> &dyn State;
246    /// Returns the conversation history from this session as Content items
247    fn conversation_history(&self) -> Vec<Content>;
248    /// Returns conversation history filtered for a specific agent.
249    ///
250    /// When provided, events authored by other agents (not "user", not the
251    /// named agent, and not function/tool responses) are excluded. This
252    /// prevents a transferred sub-agent from seeing the parent's tool calls
253    /// mapped as "model" role, which would cause the LLM to think work is
254    /// already done.
255    ///
256    /// Default implementation delegates to [`conversation_history`](Self::conversation_history).
257    fn conversation_history_for_agent(&self, _agent_name: &str) -> Vec<Content> {
258        self.conversation_history()
259    }
260    /// Append content to conversation history (for sequential agent support)
261    fn append_to_history(&self, _content: Content) {
262        // Default no-op - implementations can override to track history
263    }
264
265    /// Returns the application name as a typed [`AppName`].
266    ///
267    /// Parses the value returned by [`app_name()`](Self::app_name). Returns an
268    /// error if the raw string fails validation (empty, null bytes, or exceeds
269    /// the maximum length).
270    ///
271    /// # Errors
272    ///
273    /// Returns an error when the
274    /// underlying string is not a valid identifier.
275    fn try_app_name(&self) -> Result<AppName> {
276        Ok(AppName::try_from(self.app_name())?)
277    }
278
279    /// Returns the user identifier as a typed [`UserId`].
280    ///
281    /// Parses the value returned by [`user_id()`](Self::user_id). Returns an
282    /// error if the raw string fails validation.
283    ///
284    /// # Errors
285    ///
286    /// Returns an error when the
287    /// underlying string is not a valid identifier.
288    fn try_user_id(&self) -> Result<UserId> {
289        Ok(UserId::try_from(self.user_id())?)
290    }
291
292    /// Returns the session identifier as a typed [`SessionId`].
293    ///
294    /// Parses the value returned by [`id()`](Self::id). Returns an error if
295    /// the raw string fails validation.
296    ///
297    /// # Errors
298    ///
299    /// Returns an error when the
300    /// underlying string is not a valid identifier.
301    fn try_session_id(&self) -> Result<SessionId> {
302        Ok(SessionId::try_from(self.id())?)
303    }
304
305    /// Returns the stable session-scoped [`AdkIdentity`] triple.
306    ///
307    /// Combines [`try_app_name()`](Self::try_app_name),
308    /// [`try_user_id()`](Self::try_user_id), and
309    /// [`try_session_id()`](Self::try_session_id) into a single composite
310    /// identity value.
311    ///
312    /// # Errors
313    ///
314    /// Returns an error if any of the three constituent identifiers fail
315    /// validation.
316    fn try_identity(&self) -> Result<AdkIdentity> {
317        Ok(AdkIdentity {
318            app_name: self.try_app_name()?,
319            user_id: self.try_user_id()?,
320            session_id: self.try_session_id()?,
321        })
322    }
323}
324
325/// Structured metadata about a completed tool execution.
326///
327/// Available via [`CallbackContext::tool_outcome()`] in after-tool callbacks,
328/// plugins, and telemetry hooks. Provides structured access to execution
329/// results without requiring JSON error parsing.
330///
331/// # Fields
332///
333/// - `tool_name` — Name of the tool that was executed.
334/// - `tool_args` — Arguments passed to the tool as a JSON value.
335/// - `success` — Whether the tool execution succeeded. Derived from the
336///   Rust `Result` / timeout path, never from JSON content inspection.
337/// - `duration` — Wall-clock duration of the tool execution.
338/// - `error_message` — Error message if the tool failed; `None` on success.
339/// - `attempt` — Retry attempt number (0 = first attempt, 1 = first retry, etc.).
340///   Always 0 when retries are not configured.
341#[derive(Debug, Clone)]
342pub struct ToolOutcome {
343    /// Name of the tool that was executed.
344    pub tool_name: String,
345    /// Arguments passed to the tool (JSON value).
346    pub tool_args: serde_json::Value,
347    /// Whether the tool execution succeeded.
348    pub success: bool,
349    /// Wall-clock duration of the tool execution.
350    pub duration: std::time::Duration,
351    /// Error message if the tool failed. `None` on success.
352    pub error_message: Option<String>,
353    /// Retry attempt number (0 = first attempt, 1 = first retry, etc.).
354    /// Always 0 when retries are not configured.
355    pub attempt: u32,
356}
357
358/// Context available to agent lifecycle callbacks.
359///
360/// Extends [`ReadonlyContext`] with access to artifacts and tool execution metadata.
361#[async_trait]
362pub trait CallbackContext: ReadonlyContext {
363    /// Returns the artifact store, if one is configured.
364    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
365
366    /// Returns structured metadata about the most recent tool execution.
367    /// Available in after-tool callbacks and plugin hooks.
368    /// Returns `None` when not in a tool execution context.
369    fn tool_outcome(&self) -> Option<ToolOutcome> {
370        None // default for backward compatibility
371    }
372
373    /// Returns the name of the tool about to be executed.
374    /// Available in before-tool and after-tool callback contexts.
375    fn tool_name(&self) -> Option<&str> {
376        None
377    }
378
379    /// Returns the input arguments for the tool about to be executed.
380    /// Available in before-tool and after-tool callback contexts.
381    fn tool_input(&self) -> Option<&serde_json::Value> {
382        None
383    }
384
385    /// Returns the shared state for parallel agent coordination.
386    /// Returns `None` when not running inside a `ParallelAgent` with shared state enabled.
387    fn shared_state(&self) -> Option<Arc<crate::SharedState>> {
388        None
389    }
390}
391
392/// Wraps a [`CallbackContext`] to inject tool name and input for before-tool
393/// and after-tool callbacks.
394///
395/// Used by the agent runtime to provide tool context to `BeforeToolCallback`
396/// and `AfterToolCallback` invocations.
397///
398/// # Example
399///
400/// ```rust,ignore
401/// let tool_ctx = Arc::new(ToolCallbackContext::new(
402///     ctx.clone(),
403///     "search".to_string(),
404///     serde_json::json!({"query": "hello"}),
405/// ));
406/// callback(tool_ctx as Arc<dyn CallbackContext>).await;
407/// ```
408pub struct ToolCallbackContext {
409    /// The inner callback context to delegate to.
410    pub inner: Arc<dyn CallbackContext>,
411    /// The name of the tool being executed.
412    pub tool_name: String,
413    /// The input arguments for the tool being executed.
414    pub tool_input: serde_json::Value,
415}
416
417impl ToolCallbackContext {
418    /// Creates a new `ToolCallbackContext` wrapping the given inner context.
419    pub fn new(
420        inner: Arc<dyn CallbackContext>,
421        tool_name: String,
422        tool_input: serde_json::Value,
423    ) -> Self {
424        Self { inner, tool_name, tool_input }
425    }
426}
427
428#[async_trait]
429impl ReadonlyContext for ToolCallbackContext {
430    fn invocation_id(&self) -> &str {
431        self.inner.invocation_id()
432    }
433
434    fn agent_name(&self) -> &str {
435        self.inner.agent_name()
436    }
437
438    fn user_id(&self) -> &str {
439        self.inner.user_id()
440    }
441
442    fn app_name(&self) -> &str {
443        self.inner.app_name()
444    }
445
446    fn session_id(&self) -> &str {
447        self.inner.session_id()
448    }
449
450    fn branch(&self) -> &str {
451        self.inner.branch()
452    }
453
454    fn user_content(&self) -> &Content {
455        self.inner.user_content()
456    }
457}
458
459#[async_trait]
460impl CallbackContext for ToolCallbackContext {
461    fn artifacts(&self) -> Option<Arc<dyn Artifacts>> {
462        self.inner.artifacts()
463    }
464
465    fn tool_outcome(&self) -> Option<ToolOutcome> {
466        self.inner.tool_outcome()
467    }
468
469    fn tool_name(&self) -> Option<&str> {
470        Some(&self.tool_name)
471    }
472
473    fn tool_input(&self) -> Option<&serde_json::Value> {
474        Some(&self.tool_input)
475    }
476
477    fn shared_state(&self) -> Option<Arc<crate::SharedState>> {
478        self.inner.shared_state()
479    }
480}
481
482/// Full invocation context available to agents during execution.
483///
484/// Extends [`CallbackContext`] with access to the agent itself, memory,
485/// session, and run configuration.
486#[async_trait]
487pub trait InvocationContext: CallbackContext {
488    /// Returns the agent being executed.
489    fn agent(&self) -> Arc<dyn Agent>;
490    /// Returns the memory service, if one is configured.
491    fn memory(&self) -> Option<Arc<dyn Memory>>;
492    /// Returns the current session.
493    fn session(&self) -> &dyn Session;
494    /// Returns the run configuration for this invocation.
495    fn run_config(&self) -> &RunConfig;
496    /// Signals that this invocation should end after the current turn.
497    fn end_invocation(&self);
498    /// Returns whether the invocation has been ended.
499    fn ended(&self) -> bool;
500
501    /// Returns the scopes granted to the current user for this invocation.
502    ///
503    /// When a [`RequestContext`](crate::RequestContext) is present (set by the
504    /// server's auth middleware bridge), this returns the scopes from that
505    /// context. The default returns an empty vec (no scopes granted).
506    fn user_scopes(&self) -> Vec<String> {
507        vec![]
508    }
509
510    /// Returns the request metadata from the auth middleware bridge, if present.
511    ///
512    /// This provides access to custom key-value pairs extracted from the HTTP
513    /// request by the [`RequestContextExtractor`](crate::RequestContext).
514    fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
515        HashMap::new()
516    }
517
518    /// Retrieve a secret by name from the configured secret provider.
519    ///
520    /// Returns `Ok(Some(value))` when a provider is configured and the secret
521    /// exists, `Ok(None)` when no provider is configured, or an error on
522    /// provider failure. The default returns `Ok(None)`.
523    async fn get_secret(&self, _name: &str) -> Result<Option<String>> {
524        Ok(None)
525    }
526}
527
528// Placeholder service traits
529/// Binary artifact storage for agents.
530#[async_trait]
531pub trait Artifacts: Send + Sync {
532    /// Saves a binary artifact and returns its version number.
533    async fn save(&self, name: &str, data: &crate::Part) -> Result<i64>;
534    /// Loads a binary artifact by name.
535    async fn load(&self, name: &str) -> Result<crate::Part>;
536    /// Lists all artifact names.
537    async fn list(&self) -> Result<Vec<String>>;
538}
539
540/// Semantic memory search for agents.
541#[async_trait]
542pub trait Memory: Send + Sync {
543    /// Searches memory for entries matching the query.
544    async fn search(&self, query: &str) -> Result<Vec<MemoryEntry>>;
545
546    /// Verify backend connectivity.
547    ///
548    /// The default implementation succeeds, which is suitable for in-memory
549    /// implementations and adapters without an external dependency.
550    async fn health_check(&self) -> Result<()> {
551        Ok(())
552    }
553
554    /// Add a single memory entry.
555    ///
556    /// The default implementation returns an "not implemented" error, which is
557    /// suitable for read-only memory backends.
558    async fn add(&self, entry: MemoryEntry) -> Result<()> {
559        let _ = entry;
560        Err(AdkError::memory("add not implemented"))
561    }
562
563    /// Delete entries matching a query. Returns count of deleted entries.
564    ///
565    /// The default implementation returns an "not implemented" error, which is
566    /// suitable for read-only memory backends.
567    async fn delete(&self, query: &str) -> Result<u64> {
568        let _ = query;
569        Err(AdkError::memory("delete not implemented"))
570    }
571
572    /// Search for memories within a specific project.
573    /// Returns global entries + entries for the given project.
574    /// Default delegates to `search` (global-only results).
575    async fn search_in_project(&self, query: &str, project_id: &str) -> Result<Vec<MemoryEntry>> {
576        let _ = project_id;
577        self.search(query).await
578    }
579
580    /// Add a memory entry scoped to a specific project.
581    /// Default delegates to `add` (global entry).
582    async fn add_to_project(&self, entry: MemoryEntry, project_id: &str) -> Result<()> {
583        let _ = project_id;
584        self.add(entry).await
585    }
586}
587
588/// Trait for retrieving secrets at runtime.
589///
590/// This is the core-level abstraction used by [`ToolContext::get_secret`] and
591/// [`InvocationContext::get_secret`]. Concrete implementations (e.g., AWS
592/// Secrets Manager, Azure Key Vault, GCP Secret Manager) live in `adk-auth`
593/// behind feature flags and implement this trait via the `SecretProvider`
594/// adapter.
595///
596/// # Example
597///
598/// ```rust,ignore
599/// use adk_core::SecretService;
600///
601/// struct EnvSecretService;
602///
603/// #[async_trait::async_trait]
604/// impl SecretService for EnvSecretService {
605///     async fn get_secret(&self, name: &str) -> adk_core::Result<String> {
606///         std::env::var(name).map_err(|_| adk_core::AdkError::not_found(
607///             format!("secret '{name}' not found in environment"),
608///         ))
609///     }
610/// }
611/// ```
612#[async_trait]
613pub trait SecretService: Send + Sync {
614    /// Retrieve a secret value by name.
615    ///
616    /// Returns the secret string on success, or an [`AdkError`] on failure.
617    async fn get_secret(&self, name: &str) -> Result<String>;
618}
619
620/// A single entry returned from memory search.
621#[derive(Debug, Clone)]
622pub struct MemoryEntry {
623    /// The content of this memory entry.
624    pub content: Content,
625    /// The author who created this memory entry.
626    pub author: String,
627}
628
629/// Streaming mode for agent responses.
630/// Matches ADK Python/Go specification.
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
632pub enum StreamingMode {
633    /// No streaming; responses delivered as complete units.
634    /// Agent collects all chunks internally and yields a single final event.
635    None,
636    /// Server-Sent Events streaming; one-way streaming from server to client.
637    /// Agent yields each chunk as it arrives with stable event ID.
638    #[default]
639    SSE,
640    /// Bidirectional streaming; simultaneous communication in both directions.
641    /// Used for realtime audio/video agents.
642    Bidi,
643}
644
645/// Controls what parts of prior conversation history is received by llmagent
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
647pub enum IncludeContents {
648    /// The llmagent operates solely on its current turn (latest user input + any following agent events)
649    None,
650    /// Default - The llmagent receives the relevant conversation history
651    #[default]
652    Default,
653}
654
655/// Decision applied when a tool execution requires human confirmation.
656#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
657#[serde(rename_all = "snake_case")]
658pub enum ToolConfirmationDecision {
659    /// Approve the tool execution.
660    Approve,
661    /// Deny the tool execution.
662    Deny,
663}
664
665/// Policy defining which tools require human confirmation before execution.
666#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
667#[serde(rename_all = "snake_case")]
668pub enum ToolConfirmationPolicy {
669    /// No tool confirmation is required.
670    #[default]
671    Never,
672    /// Every tool call requires confirmation.
673    Always,
674    /// Only the listed tool names require confirmation.
675    PerTool(BTreeSet<String>),
676}
677
678impl ToolConfirmationPolicy {
679    /// Returns true when the given tool name must be confirmed before execution.
680    pub fn requires_confirmation(&self, tool_name: &str) -> bool {
681        match self {
682            Self::Never => false,
683            Self::Always => true,
684            Self::PerTool(tools) => tools.contains(tool_name),
685        }
686    }
687
688    /// Add one tool name to the confirmation policy (converts `Never` to `PerTool`).
689    pub fn with_tool(mut self, tool_name: impl Into<String>) -> Self {
690        let tool_name = tool_name.into();
691        match &mut self {
692            Self::Never => {
693                let mut tools = BTreeSet::new();
694                tools.insert(tool_name);
695                Self::PerTool(tools)
696            }
697            Self::Always => Self::Always,
698            Self::PerTool(tools) => {
699                tools.insert(tool_name);
700                self
701            }
702        }
703    }
704}
705
706/// Payload describing a tool call awaiting human confirmation.
707#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
708#[serde(rename_all = "camelCase")]
709pub struct ToolConfirmationRequest {
710    /// Name of the tool awaiting confirmation.
711    pub tool_name: String,
712    /// The function call ID from the LLM, if available.
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub function_call_id: Option<String>,
715    /// Arguments the tool would be called with.
716    pub args: Value,
717}
718
719/// Configuration for a single agent run.
720///
721/// Controls streaming behavior, tool confirmation, caching, transfer targets,
722/// and concurrency settings. Use [`RunConfig::builder()`] to construct from
723/// external crates (struct is `#[non_exhaustive]`).
724#[non_exhaustive]
725#[derive(Debug, Clone)]
726pub struct RunConfig {
727    /// The streaming mode for agent responses.
728    pub streaming_mode: StreamingMode,
729    /// Optional per-tool confirmation decisions for the current run.
730    /// Keys are tool names.
731    pub tool_confirmation_decisions: HashMap<String, ToolConfirmationDecision>,
732    /// Optional cached content name for automatic prompt caching.
733    /// When set by the runner's cache lifecycle manager, agents should attach
734    /// this name to their `GenerateContentConfig` so the LLM provider can
735    /// reuse cached system instructions and tool definitions.
736    pub cached_content: Option<String>,
737    /// Valid agent names this agent can transfer to (parent, peers, children).
738    /// Set by the runner when invoking agents in a multi-agent tree.
739    /// When non-empty, the `transfer_to_agent` tool is injected and validation
740    /// uses this list instead of only checking `sub_agents`.
741    pub transfer_targets: Vec<String>,
742    /// The name of the parent agent, if this agent was invoked via transfer.
743    /// Used by the agent to apply `disallow_transfer_to_parent` filtering.
744    pub parent_agent: Option<String>,
745    /// Enable automatic prompt caching for all providers that support it.
746    ///
747    /// When `true` (the default), the runner enables provider-level caching:
748    /// - Anthropic: sets `prompt_caching = true` on the config
749    /// - Bedrock: sets `prompt_caching = Some(BedrockCacheConfig::default())`
750    /// - OpenAI / DeepSeek: no action needed (caching is automatic)
751    /// - Gemini: handled separately via `ContextCacheConfig`
752    pub auto_cache: bool,
753    /// Maximum number of recent persisted events to load at the start of a run.
754    ///
755    /// `None` preserves the previous behavior and loads the full session
756    /// history. Set this for chat surfaces that already summarize older turns
757    /// and need predictable startup latency.
758    pub history_max_events: Option<usize>,
759    /// Tool concurrency configuration controlling parallel tool dispatch limits,
760    /// per-tool overrides, and backpressure behavior.
761    ///
762    /// The default (`ToolConcurrencyConfig::default()`) imposes no limits,
763    /// preserving backward compatibility with the previous `max_tool_concurrency: None`.
764    pub tool_concurrency: ToolConcurrencyConfig,
765    /// Whether tracing spans may include full request, response, and tool
766    /// payloads when the `record-payloads` crate feature is enabled.
767    pub record_payloads: bool,
768    /// Maximum serialized bytes recorded for tracing payload fields when full
769    /// payload recording is disabled.
770    pub trace_payload_max_bytes: usize,
771}
772
773impl Default for RunConfig {
774    fn default() -> Self {
775        Self {
776            streaming_mode: StreamingMode::SSE,
777            tool_confirmation_decisions: HashMap::new(),
778            cached_content: None,
779            transfer_targets: Vec::new(),
780            parent_agent: None,
781            auto_cache: true,
782            history_max_events: None,
783            tool_concurrency: ToolConcurrencyConfig::default(),
784            record_payloads: false,
785            trace_payload_max_bytes: 2048,
786        }
787    }
788}
789
790impl RunConfig {
791    /// Creates a new [`RunConfigBuilder`] initialized with default values.
792    ///
793    /// Use the builder to construct a `RunConfig` when struct literal syntax
794    /// is unavailable (e.g., from external crates due to `#[non_exhaustive]`).
795    ///
796    /// # Example
797    ///
798    /// ```rust
799    /// use adk_core::{RunConfig, StreamingMode};
800    ///
801    /// let config = RunConfig::builder()
802    ///     .streaming_mode(StreamingMode::None)
803    ///     .auto_cache(false)
804    ///     .build();
805    ///
806    /// assert_eq!(config.streaming_mode, StreamingMode::None);
807    /// assert!(!config.auto_cache);
808    /// ```
809    pub fn builder() -> RunConfigBuilder {
810        RunConfigBuilder::default()
811    }
812}
813
814/// Builder for [`RunConfig`].
815///
816/// Provides a fluent API for constructing `RunConfig` instances. All fields
817/// start with their default values and can be overridden individually.
818///
819/// # Example
820///
821/// ```rust
822/// use adk_core::{RunConfig, RunConfigBuilder, StreamingMode, ToolConcurrencyConfig};
823///
824/// let config = RunConfigBuilder::default()
825///     .streaming_mode(StreamingMode::Bidi)
826///     .history_max_events(Some(50))
827///     .build();
828/// ```
829#[derive(Debug, Clone, Default)]
830pub struct RunConfigBuilder {
831    config: RunConfig,
832}
833
834impl RunConfigBuilder {
835    /// Sets the streaming mode for the run.
836    pub fn streaming_mode(mut self, mode: StreamingMode) -> Self {
837        self.config.streaming_mode = mode;
838        self
839    }
840
841    /// Sets per-tool confirmation decisions for the current run.
842    pub fn tool_confirmation_decisions(
843        mut self,
844        decisions: HashMap<String, ToolConfirmationDecision>,
845    ) -> Self {
846        self.config.tool_confirmation_decisions = decisions;
847        self
848    }
849
850    /// Sets the cached content name for automatic prompt caching.
851    pub fn cached_content(mut self, name: impl Into<String>) -> Self {
852        self.config.cached_content = Some(name.into());
853        self
854    }
855
856    /// Sets the valid agent names this agent can transfer to.
857    pub fn transfer_targets(mut self, targets: Vec<String>) -> Self {
858        self.config.transfer_targets = targets;
859        self
860    }
861
862    /// Sets the parent agent name.
863    pub fn parent_agent(mut self, name: impl Into<String>) -> Self {
864        self.config.parent_agent = Some(name.into());
865        self
866    }
867
868    /// Enables or disables automatic prompt caching for supported providers.
869    pub fn auto_cache(mut self, enabled: bool) -> Self {
870        self.config.auto_cache = enabled;
871        self
872    }
873
874    /// Sets the maximum number of recent persisted events to load at run start.
875    pub fn history_max_events(mut self, max: Option<usize>) -> Self {
876        self.config.history_max_events = max;
877        self
878    }
879
880    /// Sets the tool concurrency configuration.
881    pub fn tool_concurrency(mut self, config: ToolConcurrencyConfig) -> Self {
882        self.config.tool_concurrency = config;
883        self
884    }
885
886    /// Enables or disables full payload recording in tracing spans.
887    pub fn record_payloads(mut self, enabled: bool) -> Self {
888        self.config.record_payloads = enabled;
889        self
890    }
891
892    /// Sets the maximum serialized bytes for tracing payload fields.
893    pub fn trace_payload_max_bytes(mut self, max: usize) -> Self {
894        self.config.trace_payload_max_bytes = max;
895        self
896    }
897
898    /// Consumes the builder and returns the configured [`RunConfig`].
899    pub fn build(self) -> RunConfig {
900        self.config
901    }
902}
903
904#[cfg(test)]
905mod tests {
906    use super::*;
907
908    #[test]
909    fn test_run_config_default() {
910        let config = RunConfig::default();
911        assert_eq!(config.streaming_mode, StreamingMode::SSE);
912        assert_eq!(config.history_max_events, None);
913        assert_eq!(config.tool_concurrency.max_concurrency, None);
914        assert!(config.tool_concurrency.per_tool.is_empty());
915        assert_eq!(config.tool_concurrency.backpressure, BackpressurePolicy::Queue);
916        assert!(!config.record_payloads);
917        assert_eq!(config.trace_payload_max_bytes, 2048);
918        assert!(config.tool_confirmation_decisions.is_empty());
919    }
920
921    #[test]
922    fn test_streaming_mode() {
923        assert_eq!(StreamingMode::SSE, StreamingMode::SSE);
924        assert_ne!(StreamingMode::SSE, StreamingMode::None);
925        assert_ne!(StreamingMode::None, StreamingMode::Bidi);
926    }
927
928    #[test]
929    fn test_tool_confirmation_policy() {
930        let policy = ToolConfirmationPolicy::default();
931        assert!(!policy.requires_confirmation("search"));
932
933        let policy = policy.with_tool("search");
934        assert!(policy.requires_confirmation("search"));
935        assert!(!policy.requires_confirmation("write_file"));
936
937        assert!(ToolConfirmationPolicy::Always.requires_confirmation("any_tool"));
938    }
939
940    #[test]
941    fn test_validate_state_key_valid() {
942        assert!(validate_state_key("user_name").is_ok());
943        assert!(validate_state_key("app:config").is_ok());
944        assert!(validate_state_key("temp:data").is_ok());
945        assert!(validate_state_key("a").is_ok());
946    }
947
948    #[test]
949    fn test_validate_state_key_empty() {
950        assert_eq!(validate_state_key(""), Err("state key must not be empty"));
951    }
952
953    #[test]
954    fn test_validate_state_key_too_long() {
955        let long_key = "a".repeat(MAX_STATE_KEY_LEN + 1);
956        assert!(validate_state_key(&long_key).is_err());
957    }
958
959    #[test]
960    fn test_validate_state_key_path_traversal() {
961        assert!(validate_state_key("../etc/passwd").is_err());
962        assert!(validate_state_key("foo/bar").is_err());
963        assert!(validate_state_key("foo\\bar").is_err());
964        assert!(validate_state_key("..").is_err());
965    }
966
967    #[test]
968    fn test_validate_state_key_null_byte() {
969        assert!(validate_state_key("foo\0bar").is_err());
970    }
971
972    #[test]
973    fn test_run_config_builder_defaults() {
974        let config = RunConfig::builder().build();
975        let default = RunConfig::default();
976        assert_eq!(config.streaming_mode, default.streaming_mode);
977        assert_eq!(config.auto_cache, default.auto_cache);
978        assert_eq!(config.history_max_events, default.history_max_events);
979        assert_eq!(config.record_payloads, default.record_payloads);
980        assert_eq!(config.trace_payload_max_bytes, default.trace_payload_max_bytes);
981        assert!(config.tool_confirmation_decisions.is_empty());
982        assert!(config.transfer_targets.is_empty());
983        assert!(config.cached_content.is_none());
984        assert!(config.parent_agent.is_none());
985    }
986
987    #[test]
988    fn test_run_config_builder_all_fields() {
989        let mut decisions = HashMap::new();
990        decisions.insert("delete".to_string(), ToolConfirmationDecision::Approve);
991
992        let config = RunConfig::builder()
993            .streaming_mode(StreamingMode::None)
994            .tool_confirmation_decisions(decisions.clone())
995            .cached_content("my-cache")
996            .transfer_targets(vec!["agent_a".to_string(), "agent_b".to_string()])
997            .parent_agent("parent")
998            .auto_cache(false)
999            .history_max_events(Some(50))
1000            .tool_concurrency(ToolConcurrencyConfig {
1001                max_concurrency: Some(4),
1002                per_tool: HashMap::new(),
1003                backpressure: BackpressurePolicy::Fail,
1004            })
1005            .record_payloads(true)
1006            .trace_payload_max_bytes(4096)
1007            .build();
1008
1009        assert_eq!(config.streaming_mode, StreamingMode::None);
1010        assert_eq!(config.tool_confirmation_decisions, decisions);
1011        assert_eq!(config.cached_content.as_deref(), Some("my-cache"));
1012        assert_eq!(config.transfer_targets, vec!["agent_a", "agent_b"]);
1013        assert_eq!(config.parent_agent.as_deref(), Some("parent"));
1014        assert!(!config.auto_cache);
1015        assert_eq!(config.history_max_events, Some(50));
1016        assert_eq!(config.tool_concurrency.max_concurrency, Some(4));
1017        assert_eq!(config.tool_concurrency.backpressure, BackpressurePolicy::Fail);
1018        assert!(config.record_payloads);
1019        assert_eq!(config.trace_payload_max_bytes, 4096);
1020    }
1021}