Skip to main content

adk_core/
context.rs

1use crate::identity::{AdkIdentity, AppName, ExecutionIdentity, InvocationId, SessionId, UserId};
2use crate::{AdkError, Agent, Result, Toolset, 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 session state when the runtime exposes it to dynamic toolsets.
97    ///
98    /// The default keeps lightweight contexts and third-party implementations
99    /// source-compatible. Consumers must gracefully handle contexts without
100    /// session state. Dynamic [`Toolset`] implementations use
101    /// this to determine their context-specific tool surface without requiring
102    /// mutable access to the session.
103    fn state(&self) -> Option<&dyn State> {
104        None
105    }
106
107    /// Returns the application name as a typed [`AppName`].
108    ///
109    /// Parses the value returned by [`app_name()`](Self::app_name). Returns an
110    /// error if the raw string fails validation (empty, null bytes, or exceeds
111    /// the maximum length).
112    ///
113    /// # Errors
114    ///
115    /// Returns an error when the
116    /// underlying string is not a valid identifier.
117    fn try_app_name(&self) -> Result<AppName> {
118        Ok(AppName::try_from(self.app_name())?)
119    }
120
121    /// Returns the user identifier as a typed [`UserId`].
122    ///
123    /// Parses the value returned by [`user_id()`](Self::user_id). Returns an
124    /// error if the raw string fails validation.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error when the
129    /// underlying string is not a valid identifier.
130    fn try_user_id(&self) -> Result<UserId> {
131        Ok(UserId::try_from(self.user_id())?)
132    }
133
134    /// Returns the session identifier as a typed [`SessionId`].
135    ///
136    /// Parses the value returned by [`session_id()`](Self::session_id).
137    /// Returns an error if the raw string fails validation.
138    ///
139    /// # Errors
140    ///
141    /// Returns an error when the
142    /// underlying string is not a valid identifier.
143    fn try_session_id(&self) -> Result<SessionId> {
144        Ok(SessionId::try_from(self.session_id())?)
145    }
146
147    /// Returns the invocation identifier as a typed [`InvocationId`].
148    ///
149    /// Parses the value returned by [`invocation_id()`](Self::invocation_id).
150    /// Returns an error if the raw string fails validation.
151    ///
152    /// # Errors
153    ///
154    /// Returns an error when the
155    /// underlying string is not a valid identifier.
156    fn try_invocation_id(&self) -> Result<InvocationId> {
157        Ok(InvocationId::try_from(self.invocation_id())?)
158    }
159
160    /// Returns the stable session-scoped [`AdkIdentity`] triple.
161    ///
162    /// Combines [`try_app_name()`](Self::try_app_name),
163    /// [`try_user_id()`](Self::try_user_id), and
164    /// [`try_session_id()`](Self::try_session_id) into a single composite
165    /// identity value.
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if any of the three constituent identifiers fail
170    /// validation.
171    fn try_identity(&self) -> Result<AdkIdentity> {
172        Ok(AdkIdentity {
173            app_name: self.try_app_name()?,
174            user_id: self.try_user_id()?,
175            session_id: self.try_session_id()?,
176        })
177    }
178
179    /// Returns the full per-invocation [`ExecutionIdentity`].
180    ///
181    /// Combines [`try_identity()`](Self::try_identity) with the invocation,
182    /// branch, and agent name from this context.
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if any of the four typed identifiers fail validation.
187    fn try_execution_identity(&self) -> Result<ExecutionIdentity> {
188        Ok(ExecutionIdentity {
189            adk: self.try_identity()?,
190            invocation_id: self.try_invocation_id()?,
191            branch: self.branch().to_string(),
192            agent_name: self.agent_name().to_string(),
193        })
194    }
195}
196
197// State management traits
198
199/// Maximum allowed length for state keys (256 bytes).
200pub const MAX_STATE_KEY_LEN: usize = 256;
201
202/// Validates a state key. Returns `Ok(())` if the key is safe, or an error message.
203///
204/// Rules:
205/// - Must not be empty
206/// - Must not exceed [`MAX_STATE_KEY_LEN`] bytes
207/// - Must not contain path separators (`/`, `\`) or `..`
208/// - Must not contain null bytes
209pub fn validate_state_key(key: &str) -> std::result::Result<(), &'static str> {
210    if key.is_empty() {
211        return Err("state key must not be empty");
212    }
213    if key.len() > MAX_STATE_KEY_LEN {
214        return Err("state key exceeds maximum length of 256 bytes");
215    }
216    if key.contains('/') || key.contains('\\') || key.contains("..") {
217        return Err("state key must not contain path separators or '..'");
218    }
219    if key.contains('\0') {
220        return Err("state key must not contain null bytes");
221    }
222    Ok(())
223}
224
225/// Mutable session state with key-value storage.
226///
227/// Implementations persist state across turns within a session.
228pub trait State: Send + Sync {
229    /// Returns the value for the given key, or `None` if not present.
230    fn get(&self, key: &str) -> Option<Value>;
231    /// Set a state value. Implementations should call [`validate_state_key`] and
232    /// reject invalid keys (e.g., by logging a warning or panicking).
233    fn set(&mut self, key: String, value: Value);
234    /// Returns all key-value pairs in the state.
235    fn all(&self) -> HashMap<String, Value>;
236}
237
238/// Read-only view of session state.
239pub trait ReadonlyState: Send + Sync {
240    /// Returns the value for the given key, or `None` if not present.
241    fn get(&self, key: &str) -> Option<Value>;
242    /// Returns all key-value pairs in the state.
243    fn all(&self) -> HashMap<String, Value>;
244}
245
246// Session trait
247/// Represents an active conversation session with identity and state.
248pub trait Session: Send + Sync {
249    /// Returns the session identifier.
250    fn id(&self) -> &str;
251    /// Returns the application name this session belongs to.
252    fn app_name(&self) -> &str;
253    /// Returns the user identifier for this session.
254    fn user_id(&self) -> &str;
255    /// Returns the mutable state associated with this session.
256    fn state(&self) -> &dyn State;
257    /// Returns the conversation history from this session as Content items
258    fn conversation_history(&self) -> Vec<Content>;
259    /// Returns conversation history filtered for a specific agent.
260    ///
261    /// When provided, events authored by other agents (not "user", not the
262    /// named agent, and not function/tool responses) are excluded. This
263    /// prevents a transferred sub-agent from seeing the parent's tool calls
264    /// mapped as "model" role, which would cause the LLM to think work is
265    /// already done.
266    ///
267    /// Default implementation delegates to [`conversation_history`](Self::conversation_history).
268    fn conversation_history_for_agent(&self, _agent_name: &str) -> Vec<Content> {
269        self.conversation_history()
270    }
271    /// Returns conversation history scoped to an agent and a conversation branch.
272    ///
273    /// `branch` is the invocation branch of the agent asking for history. An
274    /// event is visible when its branch equals that branch or is an *ancestor*
275    /// of it, so a sub-agent sees the conversation that led to it but not what
276    /// its siblings produced. `ParallelAgent` relies on this to keep concurrent
277    /// branches from contaminating each other's context, mirroring ADK Python's
278    /// `_is_event_belongs_to_branch` and ADK Go's `eventBelongsToBranch`.
279    ///
280    /// An empty `branch` on either side means "unscoped" and matches everything,
281    /// so implementations that never set [`crate::Event::branch`] are unaffected.
282    ///
283    /// Default implementation ignores `branch` and preserves the agent-name
284    /// filtering behaviour, so existing [`Session`] implementations keep working.
285    fn conversation_history_scoped(&self, agent_name: Option<&str>, _branch: &str) -> Vec<Content> {
286        match agent_name {
287            Some(name) => self.conversation_history_for_agent(name),
288            None => self.conversation_history(),
289        }
290    }
291    /// Append content to conversation history (for sequential agent support)
292    fn append_to_history(&self, _content: Content) {
293        // Default no-op - implementations can override to track history
294    }
295
296    /// Returns the application name as a typed [`AppName`].
297    ///
298    /// Parses the value returned by [`app_name()`](Self::app_name). Returns an
299    /// error if the raw string fails validation (empty, null bytes, or exceeds
300    /// the maximum length).
301    ///
302    /// # Errors
303    ///
304    /// Returns an error when the
305    /// underlying string is not a valid identifier.
306    fn try_app_name(&self) -> Result<AppName> {
307        Ok(AppName::try_from(self.app_name())?)
308    }
309
310    /// Returns the user identifier as a typed [`UserId`].
311    ///
312    /// Parses the value returned by [`user_id()`](Self::user_id). Returns an
313    /// error if the raw string fails validation.
314    ///
315    /// # Errors
316    ///
317    /// Returns an error when the
318    /// underlying string is not a valid identifier.
319    fn try_user_id(&self) -> Result<UserId> {
320        Ok(UserId::try_from(self.user_id())?)
321    }
322
323    /// Returns the session identifier as a typed [`SessionId`].
324    ///
325    /// Parses the value returned by [`id()`](Self::id). Returns an error if
326    /// the raw string fails validation.
327    ///
328    /// # Errors
329    ///
330    /// Returns an error when the
331    /// underlying string is not a valid identifier.
332    fn try_session_id(&self) -> Result<SessionId> {
333        Ok(SessionId::try_from(self.id())?)
334    }
335
336    /// Returns the stable session-scoped [`AdkIdentity`] triple.
337    ///
338    /// Combines [`try_app_name()`](Self::try_app_name),
339    /// [`try_user_id()`](Self::try_user_id), and
340    /// [`try_session_id()`](Self::try_session_id) into a single composite
341    /// identity value.
342    ///
343    /// # Errors
344    ///
345    /// Returns an error if any of the three constituent identifiers fail
346    /// validation.
347    fn try_identity(&self) -> Result<AdkIdentity> {
348        Ok(AdkIdentity {
349            app_name: self.try_app_name()?,
350            user_id: self.try_user_id()?,
351            session_id: self.try_session_id()?,
352        })
353    }
354}
355
356/// Structured metadata about a completed tool execution.
357///
358/// Available via [`CallbackContext::tool_outcome()`] in after-tool callbacks,
359/// plugins, and telemetry hooks. Provides structured access to execution
360/// results without requiring JSON error parsing.
361///
362/// # Fields
363///
364/// - `tool_name` — Name of the tool that was executed.
365/// - `tool_args` — Arguments passed to the tool as a JSON value.
366/// - `success` — Whether the tool execution succeeded. Derived from the
367///   Rust `Result` / timeout path, never from JSON content inspection.
368/// - `duration` — Wall-clock duration of the tool execution.
369/// - `error_message` — Error message if the tool failed; `None` on success.
370/// - `attempt` — Retry attempt number (0 = first attempt, 1 = first retry, etc.).
371///   Always 0 when retries are not configured.
372#[derive(Debug, Clone)]
373pub struct ToolOutcome {
374    /// Name of the tool that was executed.
375    pub tool_name: String,
376    /// Arguments passed to the tool (JSON value).
377    pub tool_args: serde_json::Value,
378    /// Whether the tool execution succeeded.
379    pub success: bool,
380    /// Wall-clock duration of the tool execution.
381    pub duration: std::time::Duration,
382    /// Error message if the tool failed. `None` on success.
383    pub error_message: Option<String>,
384    /// Retry attempt number (0 = first attempt, 1 = first retry, etc.).
385    /// Always 0 when retries are not configured.
386    pub attempt: u32,
387}
388
389/// Context available to agent lifecycle callbacks.
390///
391/// Extends [`ReadonlyContext`] with access to artifacts and tool execution metadata.
392#[async_trait]
393pub trait CallbackContext: ReadonlyContext {
394    /// Returns the artifact store, if one is configured.
395    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
396
397    /// Returns structured metadata about the most recent tool execution.
398    /// Available in after-tool callbacks and plugin hooks.
399    /// Returns `None` when not in a tool execution context.
400    fn tool_outcome(&self) -> Option<ToolOutcome> {
401        None // default for backward compatibility
402    }
403
404    /// Returns the name of the tool about to be executed.
405    /// Available in before-tool and after-tool callback contexts.
406    fn tool_name(&self) -> Option<&str> {
407        None
408    }
409
410    /// Returns the input arguments for the tool about to be executed.
411    /// Available in before-tool and after-tool callback contexts.
412    fn tool_input(&self) -> Option<&serde_json::Value> {
413        None
414    }
415
416    /// Returns the shared state for parallel agent coordination.
417    /// Returns `None` when not running inside a `ParallelAgent` with shared state enabled.
418    fn shared_state(&self) -> Option<Arc<crate::SharedState>> {
419        None
420    }
421}
422
423/// Wraps a [`CallbackContext`] to inject tool name and input for before-tool
424/// and after-tool callbacks.
425///
426/// Used by the agent runtime to provide tool context to `BeforeToolCallback`
427/// and `AfterToolCallback` invocations.
428///
429/// # Example
430///
431/// ```rust,ignore
432/// let tool_ctx = Arc::new(ToolCallbackContext::new(
433///     ctx.clone(),
434///     "search".to_string(),
435///     serde_json::json!({"query": "hello"}),
436/// ));
437/// callback(tool_ctx as Arc<dyn CallbackContext>).await;
438/// ```
439pub struct ToolCallbackContext {
440    /// The inner callback context to delegate to.
441    pub inner: Arc<dyn CallbackContext>,
442    /// The name of the tool being executed.
443    pub tool_name: String,
444    /// The input arguments for the tool being executed.
445    pub tool_input: serde_json::Value,
446}
447
448impl ToolCallbackContext {
449    /// Creates a new `ToolCallbackContext` wrapping the given inner context.
450    pub fn new(
451        inner: Arc<dyn CallbackContext>,
452        tool_name: String,
453        tool_input: serde_json::Value,
454    ) -> Self {
455        Self { inner, tool_name, tool_input }
456    }
457}
458
459#[async_trait]
460impl ReadonlyContext for ToolCallbackContext {
461    fn invocation_id(&self) -> &str {
462        self.inner.invocation_id()
463    }
464
465    fn agent_name(&self) -> &str {
466        self.inner.agent_name()
467    }
468
469    fn user_id(&self) -> &str {
470        self.inner.user_id()
471    }
472
473    fn app_name(&self) -> &str {
474        self.inner.app_name()
475    }
476
477    fn session_id(&self) -> &str {
478        self.inner.session_id()
479    }
480
481    fn branch(&self) -> &str {
482        self.inner.branch()
483    }
484
485    fn user_content(&self) -> &Content {
486        self.inner.user_content()
487    }
488}
489
490#[async_trait]
491impl CallbackContext for ToolCallbackContext {
492    fn artifacts(&self) -> Option<Arc<dyn Artifacts>> {
493        self.inner.artifacts()
494    }
495
496    fn tool_outcome(&self) -> Option<ToolOutcome> {
497        self.inner.tool_outcome()
498    }
499
500    fn tool_name(&self) -> Option<&str> {
501        Some(&self.tool_name)
502    }
503
504    fn tool_input(&self) -> Option<&serde_json::Value> {
505        Some(&self.tool_input)
506    }
507
508    fn shared_state(&self) -> Option<Arc<crate::SharedState>> {
509        self.inner.shared_state()
510    }
511}
512
513/// Full invocation context available to agents during execution.
514///
515/// Extends [`CallbackContext`] with access to the agent itself, memory,
516/// session, and run configuration.
517#[async_trait]
518pub trait InvocationContext: CallbackContext {
519    /// Returns the agent being executed.
520    fn agent(&self) -> Arc<dyn Agent>;
521    /// Returns the memory service, if one is configured.
522    fn memory(&self) -> Option<Arc<dyn Memory>>;
523    /// Returns the current session.
524    fn session(&self) -> &dyn Session;
525    /// Returns the run configuration for this invocation.
526    fn run_config(&self) -> &RunConfig;
527    /// Signals that this invocation should end after the current turn.
528    fn end_invocation(&self);
529    /// Returns whether the invocation has been ended.
530    fn ended(&self) -> bool;
531
532    /// Returns `true` if this invocation has been cancelled.
533    ///
534    /// Agents and tools can poll this during long-running work (LLM streaming,
535    /// HTTP I/O, tool execution) to detect an external cancellation request —
536    /// for example, a user pressing "Stop" or a call to
537    /// [`Runner::interrupt`](https://docs.rs/adk-runner). Checking it at chunk
538    /// or tool boundaries lets an agent exit promptly and perform any graceful
539    /// cleanup instead of running to natural completion.
540    ///
541    /// The default returns `false`. The runtime sets the underlying token when
542    /// `Runner::interrupt()` is called or `RunConfig::cancellation_token` fires.
543    fn is_cancelled(&self) -> bool {
544        false
545    }
546
547    /// Returns the scopes granted to the current user for this invocation.
548    ///
549    /// When a [`RequestContext`](crate::RequestContext) is present (set by the
550    /// server's auth middleware bridge), this returns the scopes from that
551    /// context. The default returns an empty vec (no scopes granted).
552    fn user_scopes(&self) -> Vec<String> {
553        vec![]
554    }
555
556    /// Returns the request metadata from the auth middleware bridge, if present.
557    ///
558    /// This provides access to custom key-value pairs extracted from the HTTP
559    /// request by the [`RequestContextExtractor`](crate::RequestContext).
560    fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
561        HashMap::new()
562    }
563
564    /// Whether the run's transfer target list replaces static sub-agent targets.
565    fn authoritative_transfer_targets(&self) -> bool {
566        false
567    }
568
569    /// Current nested agent-as-tool delegation depth.
570    fn delegation_depth(&self) -> u32 {
571        0
572    }
573
574    /// Maximum nested agent-as-tool delegation depth.
575    fn max_delegation_depth(&self) -> Option<u32> {
576        None
577    }
578
579    /// Returns the root invocation that owns this orchestration tree.
580    ///
581    /// Nested agent runs use this stable identifier to aggregate budgets,
582    /// traces, and execution receipts without conflating unrelated runs.
583    fn orchestration_root_invocation_id(&self) -> &str {
584        self.invocation_id()
585    }
586
587    /// Returns the causal relationship execution that started this run.
588    fn orchestration_edge_id(&self) -> Option<&str> {
589        None
590    }
591
592    /// Returns whether a runtime-injected tool requires confirmation.
593    ///
594    /// This additive hook lets composition layers protect relationship tools
595    /// without mutating the concrete agent that receives them.
596    fn requires_tool_confirmation(&self, _tool_name: &str) -> bool {
597        false
598    }
599
600    /// Retrieve a secret by name from the configured secret provider.
601    ///
602    /// Returns `Ok(Some(value))` when a provider is configured and the secret
603    /// exists, `Ok(None)` when no provider is configured, or an error on
604    /// provider failure. The default returns `Ok(None)`.
605    async fn get_secret(&self, _name: &str) -> Result<Option<String>> {
606        Ok(None)
607    }
608
609    /// Resolves a secret for a described access.
610    ///
611    /// A wrapper context must forward this, and a tool context builds the request from
612    /// the identity the framework gave it. The default drops the description and calls
613    /// [`InvocationContext::get_secret`], which keeps a context that predates the
614    /// request object working.
615    async fn get_secret_for(&self, request: &SecretRequest) -> Result<Option<String>> {
616        self.get_secret(&request.name).await
617    }
618}
619
620// Placeholder service traits
621/// Binary artifact storage for agents.
622#[async_trait]
623pub trait Artifacts: Send + Sync {
624    /// Saves a binary artifact and returns its version number.
625    async fn save(&self, name: &str, data: &crate::Part) -> Result<i64>;
626    /// Loads a binary artifact by name.
627    async fn load(&self, name: &str) -> Result<crate::Part>;
628    /// Lists all artifact names.
629    async fn list(&self) -> Result<Vec<String>>;
630}
631
632/// Semantic memory search for agents.
633#[async_trait]
634pub trait Memory: Send + Sync {
635    /// Searches memory for entries matching the query.
636    async fn search(&self, query: &str) -> Result<Vec<MemoryEntry>>;
637
638    /// Verify backend connectivity.
639    ///
640    /// The default implementation succeeds, which is suitable for in-memory
641    /// implementations and adapters without an external dependency.
642    async fn health_check(&self) -> Result<()> {
643        Ok(())
644    }
645
646    /// Add a single memory entry.
647    ///
648    /// The default implementation returns an "not implemented" error, which is
649    /// suitable for read-only memory backends.
650    async fn add(&self, entry: MemoryEntry) -> Result<()> {
651        let _ = entry;
652        Err(AdkError::memory("add not implemented"))
653    }
654
655    /// Delete entries matching a query. Returns count of deleted entries.
656    ///
657    /// The default implementation returns an "not implemented" error, which is
658    /// suitable for read-only memory backends.
659    async fn delete(&self, query: &str) -> Result<u64> {
660        let _ = query;
661        Err(AdkError::memory("delete not implemented"))
662    }
663
664    /// Whether this memory keeps project-scoped entries isolated.
665    ///
666    /// Returns `false` by default, so a caller can tell real isolation apart from a
667    /// memory that has no project support instead of inferring it from data.
668    fn supports_project_scoping(&self) -> bool {
669        false
670    }
671
672    /// Searches memories within a specific project.
673    ///
674    /// # Errors
675    ///
676    /// The default implementation returns an error. Delegating to the global search
677    /// would return entries the project boundary is meant to exclude, and nothing in
678    /// the result would say the boundary was ignored.
679    async fn search_in_project(&self, query: &str, project_id: &str) -> Result<Vec<MemoryEntry>> {
680        let _ = (query, project_id);
681        Err(AdkError::memory(
682            "this memory does not implement project scoping, so `search_in_project` cannot \
683             honour the project boundary; check `supports_project_scoping` first, or call \
684             `search` if global scope is intended",
685        ))
686    }
687
688    /// Adds a memory entry scoped to a specific project.
689    ///
690    /// # Errors
691    ///
692    /// Returns an error by default. Writing the entry globally would make data
693    /// intended for one project visible everywhere under the same app and user.
694    async fn add_to_project(&self, entry: MemoryEntry, project_id: &str) -> Result<()> {
695        let _ = (entry, project_id);
696        Err(AdkError::memory(
697            "this memory does not implement project scoping, so `add_to_project` cannot honour \
698             the project boundary; check `supports_project_scoping` first, or call `add` if \
699             global scope is intended",
700        ))
701    }
702}
703
704/// Trait for retrieving secrets at runtime.
705///
706/// This is the core-level abstraction used by `ToolContext::get_secret` and
707/// `InvocationContext::get_secret`. Concrete implementations (e.g., AWS
708/// Secrets Manager, Azure Key Vault, GCP Secret Manager) live in `adk-auth`
709/// behind feature flags and implement this trait via the `SecretProvider`
710/// adapter.
711///
712/// # Example
713///
714/// ```rust,ignore
715/// use adk_core::SecretService;
716///
717/// struct EnvSecretService;
718///
719/// #[async_trait::async_trait]
720/// impl SecretService for EnvSecretService {
721///     async fn get_secret(&self, name: &str) -> adk_core::Result<String> {
722///         std::env::var(name).map_err(|_| adk_core::AdkError::not_found(
723///             format!("secret '{name}' not found in environment"),
724///         ))
725///     }
726/// }
727/// ```
728#[async_trait]
729pub trait SecretService: Send + Sync {
730    /// Retrieve a secret value by name.
731    ///
732    /// Returns the secret string on success, or an [`AdkError`] on failure.
733    async fn get_secret(&self, name: &str) -> Result<String>;
734
735    /// Retrieve a secret for a described access.
736    ///
737    /// This is the form an authorizing service implements: the request carries who is
738    /// asking and why, so a decision can be made before the value is fetched. The
739    /// default implementation ignores the context and calls
740    /// [`SecretService::get_secret`], which is correct for a service that has no
741    /// policy of its own.
742    ///
743    /// Every field on [`SecretRequest`] is set by the framework at the call site, not
744    /// supplied by the tool, so a tool cannot present another tool's identity.
745    async fn get_secret_for(&self, request: &SecretRequest) -> Result<String> {
746        self.get_secret(&request.name).await
747    }
748}
749
750/// A described secret access.
751///
752/// Carries the requested name plus the identity the framework observed at the call
753/// site, so a [`SecretService`] can authorize and audit rather than being handed a
754/// bare name with no context.
755///
756/// # Example
757///
758/// ```rust
759/// use adk_core::SecretRequest;
760///
761/// let request = SecretRequest::new("payments-api-key")
762///     .with_tool_name("charge_card")
763///     .with_purpose("authorize a customer payment");
764///
765/// assert_eq!(request.tool_name.as_deref(), Some("charge_card"));
766/// ```
767#[derive(Debug, Clone, Default, PartialEq, Eq)]
768pub struct SecretRequest {
769    /// Name of the requested secret.
770    pub name: String,
771    /// The tool making the request, when the access came from a tool.
772    ///
773    /// Set by the framework from the tool it dispatched, never from a value the tool
774    /// provided.
775    pub tool_name: Option<String>,
776    /// Application the run belongs to.
777    pub app_name: Option<String>,
778    /// Authenticated user the run belongs to.
779    pub user_id: Option<String>,
780    /// Session the run belongs to.
781    pub session_id: Option<String>,
782    /// Invocation the access happened in, for correlating audit records.
783    pub invocation_id: Option<String>,
784    /// Why the secret is needed, when the caller states it.
785    pub purpose: Option<String>,
786}
787
788impl SecretRequest {
789    /// Creates a request for `name` with no identity attached.
790    pub fn new(name: impl Into<String>) -> Self {
791        Self { name: name.into(), ..Default::default() }
792    }
793
794    /// Attaches the requesting tool's name.
795    #[must_use]
796    pub fn with_tool_name(mut self, tool_name: impl Into<String>) -> Self {
797        self.tool_name = Some(tool_name.into());
798        self
799    }
800
801    /// Attaches the run's identity.
802    #[must_use]
803    pub fn with_identity(
804        mut self,
805        app_name: impl Into<String>,
806        user_id: impl Into<String>,
807        session_id: impl Into<String>,
808    ) -> Self {
809        self.app_name = Some(app_name.into());
810        self.user_id = Some(user_id.into());
811        self.session_id = Some(session_id.into());
812        self
813    }
814
815    /// Attaches the invocation the access happened in.
816    #[must_use]
817    pub fn with_invocation_id(mut self, invocation_id: impl Into<String>) -> Self {
818        self.invocation_id = Some(invocation_id.into());
819        self
820    }
821
822    /// Attaches a stated purpose.
823    #[must_use]
824    pub fn with_purpose(mut self, purpose: impl Into<String>) -> Self {
825        self.purpose = Some(purpose.into());
826        self
827    }
828}
829
830/// A single entry returned from memory search.
831#[derive(Debug, Clone)]
832pub struct MemoryEntry {
833    /// The content of this memory entry.
834    pub content: Content,
835    /// The author who created this memory entry.
836    pub author: String,
837}
838
839/// Streaming mode for agent responses.
840/// Matches ADK Python/Go specification.
841#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
842pub enum StreamingMode {
843    /// No streaming; responses delivered as complete units.
844    /// Agent collects all chunks internally and yields a single final event.
845    None,
846    /// Server-Sent Events streaming; one-way streaming from server to client.
847    /// Agent yields each chunk as it arrives with stable event ID.
848    #[default]
849    SSE,
850    /// Bidirectional streaming; simultaneous communication in both directions.
851    /// Used for realtime audio/video agents.
852    Bidi,
853}
854
855/// Controls what parts of prior conversation history is received by llmagent
856#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
857pub enum IncludeContents {
858    /// The llmagent operates solely on its current turn (latest user input + any following agent events)
859    None,
860    /// Default - The llmagent receives the relevant conversation history
861    #[default]
862    Default,
863}
864
865/// Decision applied when a tool execution requires human confirmation.
866#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
867#[serde(rename_all = "snake_case")]
868pub enum ToolConfirmationDecision {
869    /// Approve the tool execution.
870    Approve,
871    /// Deny the tool execution.
872    Deny,
873}
874
875/// Produces a canonical fingerprint of a tool call.
876///
877/// The fingerprint is the tool name followed by its arguments in canonical JSON
878/// form, with object keys sorted at every level so that two structurally equal
879/// argument sets always produce the same string. It is deliberately readable
880/// rather than hashed, so a mismatch can be diagnosed by inspection.
881///
882/// Use it with
883/// [`RunConfig::tool_confirmation_fingerprints`](RunConfig::tool_confirmation_fingerprints)
884/// to bind an approval to the exact arguments it was granted for.
885///
886/// # Example
887///
888/// ```rust
889/// use adk_core::tool_call_fingerprint;
890/// use serde_json::json;
891///
892/// // Key order does not change the fingerprint.
893/// let a = tool_call_fingerprint("delete_file", &json!({ "path": "/tmp/a", "force": true }));
894/// let b = tool_call_fingerprint("delete_file", &json!({ "force": true, "path": "/tmp/a" }));
895/// assert_eq!(a, b);
896///
897/// // A different path does not.
898/// let c = tool_call_fingerprint("delete_file", &json!({ "path": "/etc/passwd", "force": true }));
899/// assert_ne!(a, c);
900/// ```
901pub fn tool_call_fingerprint(tool_name: &str, args: &Value) -> String {
902    let mut out = String::with_capacity(tool_name.len() + 32);
903    out.push_str(tool_name);
904    out.push('\u{1f}');
905    write_canonical(args, &mut out);
906    out
907}
908
909/// Writes `value` as canonical JSON, with object keys sorted at every level.
910fn write_canonical(value: &Value, out: &mut String) {
911    match value {
912        Value::Object(map) => {
913            let mut keys: Vec<&String> = map.keys().collect();
914            keys.sort();
915            out.push('{');
916            for (i, key) in keys.iter().enumerate() {
917                if i > 0 {
918                    out.push(',');
919                }
920                out.push_str(&Value::String((*key).clone()).to_string());
921                out.push(':');
922                write_canonical(&map[*key], out);
923            }
924            out.push('}');
925        }
926        Value::Array(items) => {
927            out.push('[');
928            for (i, item) in items.iter().enumerate() {
929                if i > 0 {
930                    out.push(',');
931                }
932                write_canonical(item, out);
933            }
934            out.push(']');
935        }
936        other => out.push_str(&other.to_string()),
937    }
938}
939
940/// Policy defining which tools require human confirmation before execution.
941#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
942#[serde(rename_all = "snake_case")]
943pub enum ToolConfirmationPolicy {
944    /// No tool confirmation is required.
945    #[default]
946    Never,
947    /// Every tool call requires confirmation.
948    Always,
949    /// Only the listed tool names require confirmation.
950    PerTool(BTreeSet<String>),
951}
952
953impl ToolConfirmationPolicy {
954    /// Returns true when the given tool name must be confirmed before execution.
955    pub fn requires_confirmation(&self, tool_name: &str) -> bool {
956        match self {
957            Self::Never => false,
958            Self::Always => true,
959            Self::PerTool(tools) => tools.contains(tool_name),
960        }
961    }
962
963    /// Add one tool name to the confirmation policy (converts `Never` to `PerTool`).
964    pub fn with_tool(mut self, tool_name: impl Into<String>) -> Self {
965        let tool_name = tool_name.into();
966        match &mut self {
967            Self::Never => {
968                let mut tools = BTreeSet::new();
969                tools.insert(tool_name);
970                Self::PerTool(tools)
971            }
972            Self::Always => Self::Always,
973            Self::PerTool(tools) => {
974                tools.insert(tool_name);
975                self
976            }
977        }
978    }
979}
980
981/// Payload describing a tool call awaiting human confirmation.
982#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
983#[serde(rename_all = "camelCase")]
984pub struct ToolConfirmationRequest {
985    /// Name of the tool awaiting confirmation.
986    pub tool_name: String,
987    /// The function call ID from the LLM, if available.
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub function_call_id: Option<String>,
990    /// Arguments the tool would be called with.
991    pub args: Value,
992}
993
994/// Asynchronous decision source for tool calls that require confirmation.
995///
996/// Front ends and protocol adapters can implement this trait to pause an
997/// invocation while a person or an external policy service reviews the exact
998/// tool call. When no handler is configured, agents preserve the existing
999/// behavior and emit an interrupted confirmation event for a later run.
1000#[async_trait]
1001pub trait ToolConfirmationHandler: std::fmt::Debug + Send + Sync {
1002    /// Approve or deny one pending tool call.
1003    async fn decide(&self, request: &ToolConfirmationRequest) -> Result<ToolConfirmationDecision>;
1004}
1005
1006/// A toolset attached to one runner invocation rather than compiled into the
1007/// agent definition.
1008///
1009/// Protocol adapters use this wrapper for session-scoped capabilities such as
1010/// MCP servers supplied by an ACP client. The wrapper keeps [`RunConfig`]
1011/// debuggable without requiring every toolset implementation to implement
1012/// [`std::fmt::Debug`].
1013#[derive(Clone)]
1014pub struct RuntimeToolset(Arc<dyn Toolset>);
1015
1016impl RuntimeToolset {
1017    /// Wrap a toolset for use during one runner invocation.
1018    pub fn new(toolset: Arc<dyn Toolset>) -> Self {
1019        Self(toolset)
1020    }
1021
1022    /// Borrow the wrapped toolset.
1023    pub fn toolset(&self) -> &Arc<dyn Toolset> {
1024        &self.0
1025    }
1026}
1027
1028impl std::fmt::Debug for RuntimeToolset {
1029    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1030        formatter.debug_tuple("RuntimeToolset").field(&self.0.name()).finish()
1031    }
1032}
1033
1034/// Configuration for a single agent run.
1035///
1036/// Controls streaming behavior, tool confirmation, caching, transfer targets,
1037/// and concurrency settings. Use [`RunConfig::builder()`] to construct from
1038#[derive(Debug, Clone)]
1039pub struct RunConfig {
1040    /// The streaming mode for agent responses.
1041    pub streaming_mode: StreamingMode,
1042    /// Static confirmation decisions for the current run, keyed by **function
1043    /// call ID**.
1044    ///
1045    /// The ID is the one reported on
1046    /// [`ToolConfirmationRequest::function_call_id`], so a decision authorizes the
1047    /// exact call it was requested for. A decision under a tool *name* is not
1048    /// consulted, because one name can cover materially different calls — a
1049    /// `delete_file` approval for a scratch path must not authorize a call that
1050    /// targets a different path.
1051    ///
1052    /// Use [`tool_confirmation_fingerprints`](Self::tool_confirmation_fingerprints)
1053    /// to additionally bind a decision to the arguments it was granted for. For
1054    /// name-wide or policy-driven decisions, supply a
1055    /// [`tool_confirmation_handler`](Self::tool_confirmation_handler) instead.
1056    pub tool_confirmation_decisions: HashMap<String, ToolConfirmationDecision>,
1057    /// Optional argument binding for entries in
1058    /// [`tool_confirmation_decisions`](Self::tool_confirmation_decisions), keyed by
1059    /// the same function call ID.
1060    ///
1061    /// The value is the fingerprint produced by [`tool_call_fingerprint`] for the
1062    /// call the decision was granted for. When an entry is present and the actual
1063    /// call does not match it, the decision is ignored and the call is treated as
1064    /// unconfirmed — the safe direction. Use this when a decision travels through
1065    /// an untrusted round trip, such as a browser, where the arguments could be
1066    /// changed while the call ID is replayed.
1067    pub tool_confirmation_fingerprints: HashMap<String, String>,
1068    /// Optional live decision source for confirmations that have no static
1069    /// entry in [`tool_confirmation_decisions`](Self::tool_confirmation_decisions).
1070    pub tool_confirmation_handler: Option<Arc<dyn ToolConfirmationHandler>>,
1071    /// Toolsets made available only for this invocation.
1072    pub runtime_toolsets: Vec<RuntimeToolset>,
1073    /// Optional cached content name for automatic prompt caching.
1074    /// When set by the runner's cache lifecycle manager, agents should attach
1075    /// this name to their `GenerateContentConfig` so the LLM provider can
1076    /// reuse cached system instructions and tool definitions.
1077    pub cached_content: Option<String>,
1078    /// Valid agent names this agent can transfer to (parent, peers, children).
1079    /// Set by the runner when invoking agents in a multi-agent tree.
1080    /// When non-empty, the `transfer_to_agent` tool is injected and validation
1081    /// uses this list instead of only checking `sub_agents`.
1082    pub transfer_targets: Vec<String>,
1083    /// The name of the parent agent, if this agent was invoked via transfer.
1084    /// Used by the agent to apply `disallow_transfer_to_parent` filtering.
1085    pub parent_agent: Option<String>,
1086    /// Enable automatic prompt caching for all providers that support it.
1087    ///
1088    /// When `true` (the default), the runner enables provider-level caching:
1089    /// - Anthropic: sets `prompt_caching = true` on the config
1090    /// - Bedrock: sets `prompt_caching = Some(BedrockCacheConfig::default())`
1091    /// - OpenAI / DeepSeek: no action needed (caching is automatic)
1092    /// - Gemini: handled separately via `ContextCacheConfig`
1093    pub auto_cache: bool,
1094    /// Maximum number of recent persisted events to load at the start of a run.
1095    ///
1096    /// `None` preserves the previous behavior and loads the full session
1097    /// history. Set this for chat surfaces that already summarize older turns
1098    /// and need predictable startup latency.
1099    pub history_max_events: Option<usize>,
1100    /// Tool concurrency configuration controlling parallel tool dispatch limits,
1101    /// per-tool overrides, and backpressure behavior.
1102    ///
1103    /// The default (`ToolConcurrencyConfig::default()`) imposes no limits,
1104    /// preserving backward compatibility with the previous `max_tool_concurrency: None`.
1105    pub tool_concurrency: ToolConcurrencyConfig,
1106    /// Whether tracing spans may include full request, response, and tool
1107    /// payloads when the `record-payloads` crate feature is enabled.
1108    pub record_payloads: bool,
1109    /// Maximum serialized bytes recorded for tracing payload fields when full
1110    /// payload recording is disabled.
1111    pub trace_payload_max_bytes: usize,
1112    /// Maximum number of agent-to-agent transfers allowed in a single run.
1113    ///
1114    /// Prevents infinite transfer loops when agents transfer back and forth.
1115    /// Defaults to 10 when `None`.
1116    pub max_transfer_depth: Option<u32>,
1117}
1118
1119impl Default for RunConfig {
1120    fn default() -> Self {
1121        Self {
1122            streaming_mode: StreamingMode::SSE,
1123            tool_confirmation_decisions: HashMap::new(),
1124            tool_confirmation_fingerprints: HashMap::new(),
1125            tool_confirmation_handler: None,
1126            runtime_toolsets: Vec::new(),
1127            cached_content: None,
1128            transfer_targets: Vec::new(),
1129            parent_agent: None,
1130            auto_cache: true,
1131            history_max_events: None,
1132            tool_concurrency: ToolConcurrencyConfig::default(),
1133            record_payloads: false,
1134            trace_payload_max_bytes: 2048,
1135            max_transfer_depth: None,
1136        }
1137    }
1138}
1139
1140impl RunConfig {
1141    /// Creates a new [`RunConfigBuilder`] initialized with default values.
1142    ///
1143    /// Use the builder to construct a `RunConfig` when struct literal syntax
1144    ///
1145    /// # Example
1146    ///
1147    /// ```rust
1148    /// use adk_core::{RunConfig, StreamingMode};
1149    ///
1150    /// let config = RunConfig::builder()
1151    ///     .streaming_mode(StreamingMode::None)
1152    ///     .auto_cache(false)
1153    ///     .build();
1154    ///
1155    /// assert_eq!(config.streaming_mode, StreamingMode::None);
1156    /// assert!(!config.auto_cache);
1157    /// ```
1158    pub fn builder() -> RunConfigBuilder {
1159        RunConfigBuilder::default()
1160    }
1161}
1162
1163/// Builder for [`RunConfig`].
1164///
1165/// Provides a fluent API for constructing `RunConfig` instances. All fields
1166/// start with their default values and can be overridden individually.
1167///
1168/// # Example
1169///
1170/// ```rust
1171/// use adk_core::{RunConfig, RunConfigBuilder, StreamingMode, ToolConcurrencyConfig};
1172///
1173/// let config = RunConfigBuilder::default()
1174///     .streaming_mode(StreamingMode::Bidi)
1175///     .history_max_events(Some(50))
1176///     .build();
1177/// ```
1178#[derive(Debug, Clone, Default)]
1179pub struct RunConfigBuilder {
1180    config: RunConfig,
1181}
1182
1183impl RunConfigBuilder {
1184    /// Sets the streaming mode for the run.
1185    pub fn streaming_mode(mut self, mode: StreamingMode) -> Self {
1186        self.config.streaming_mode = mode;
1187        self
1188    }
1189
1190    /// Sets static confirmation decisions for the current run, keyed by function
1191    /// call ID.
1192    ///
1193    /// The ID is the one carried on `ToolConfirmationRequest::function_call_id`.
1194    pub fn tool_confirmation_decisions(
1195        mut self,
1196        decisions: HashMap<String, ToolConfirmationDecision>,
1197    ) -> Self {
1198        self.config.tool_confirmation_decisions = decisions;
1199        self
1200    }
1201
1202    /// Binds confirmation decisions to the arguments they were granted for.
1203    ///
1204    /// Keys are function call IDs and values are fingerprints from
1205    /// [`tool_call_fingerprint`]. A decision whose fingerprint does not match the
1206    /// actual call is ignored and the call is treated as unconfirmed.
1207    pub fn tool_confirmation_fingerprints(mut self, fingerprints: HashMap<String, String>) -> Self {
1208        self.config.tool_confirmation_fingerprints = fingerprints;
1209        self
1210    }
1211
1212    /// Sets an asynchronous tool confirmation handler for the current run.
1213    pub fn tool_confirmation_handler(mut self, handler: Arc<dyn ToolConfirmationHandler>) -> Self {
1214        self.config.tool_confirmation_handler = Some(handler);
1215        self
1216    }
1217
1218    /// Adds a toolset that is resolved only for this runner invocation.
1219    pub fn runtime_toolset(mut self, toolset: Arc<dyn Toolset>) -> Self {
1220        self.config.runtime_toolsets.push(RuntimeToolset::new(toolset));
1221        self
1222    }
1223
1224    /// Adds several toolsets that are resolved only for this runner invocation.
1225    pub fn runtime_toolsets(
1226        mut self,
1227        toolsets: impl IntoIterator<Item = Arc<dyn Toolset>>,
1228    ) -> Self {
1229        self.config.runtime_toolsets.extend(toolsets.into_iter().map(RuntimeToolset::new));
1230        self
1231    }
1232
1233    /// Sets the cached content name for automatic prompt caching.
1234    pub fn cached_content(mut self, name: impl Into<String>) -> Self {
1235        self.config.cached_content = Some(name.into());
1236        self
1237    }
1238
1239    /// Sets the valid agent names this agent can transfer to.
1240    pub fn transfer_targets(mut self, targets: Vec<String>) -> Self {
1241        self.config.transfer_targets = targets;
1242        self
1243    }
1244
1245    /// Sets the parent agent name.
1246    pub fn parent_agent(mut self, name: impl Into<String>) -> Self {
1247        self.config.parent_agent = Some(name.into());
1248        self
1249    }
1250
1251    /// Enables or disables automatic prompt caching for supported providers.
1252    pub fn auto_cache(mut self, enabled: bool) -> Self {
1253        self.config.auto_cache = enabled;
1254        self
1255    }
1256
1257    /// Sets the maximum number of recent persisted events to load at run start.
1258    pub fn history_max_events(mut self, max: Option<usize>) -> Self {
1259        self.config.history_max_events = max;
1260        self
1261    }
1262
1263    /// Sets the tool concurrency configuration.
1264    pub fn tool_concurrency(mut self, config: ToolConcurrencyConfig) -> Self {
1265        self.config.tool_concurrency = config;
1266        self
1267    }
1268
1269    /// Enables or disables full payload recording in tracing spans.
1270    pub fn record_payloads(mut self, enabled: bool) -> Self {
1271        self.config.record_payloads = enabled;
1272        self
1273    }
1274
1275    /// Sets the maximum serialized bytes for tracing payload fields.
1276    pub fn trace_payload_max_bytes(mut self, max: usize) -> Self {
1277        self.config.trace_payload_max_bytes = max;
1278        self
1279    }
1280
1281    /// Sets the maximum number of agent-to-agent transfers allowed in a single run.
1282    ///
1283    /// Prevents infinite transfer loops. Defaults to 10 when `None`.
1284    pub fn max_transfer_depth(mut self, depth: u32) -> Self {
1285        self.config.max_transfer_depth = Some(depth);
1286        self
1287    }
1288
1289    /// Consumes the builder and returns the configured [`RunConfig`].
1290    pub fn build(self) -> RunConfig {
1291        self.config
1292    }
1293}
1294
1295#[cfg(test)]
1296mod tests {
1297    use super::*;
1298
1299    #[test]
1300    fn test_run_config_default() {
1301        let config = RunConfig::default();
1302        assert_eq!(config.streaming_mode, StreamingMode::SSE);
1303        assert_eq!(config.history_max_events, None);
1304        assert_eq!(config.tool_concurrency.max_concurrency, None);
1305        assert!(config.tool_concurrency.per_tool.is_empty());
1306        assert_eq!(config.tool_concurrency.backpressure, BackpressurePolicy::Queue);
1307        assert!(!config.record_payloads);
1308        assert_eq!(config.trace_payload_max_bytes, 2048);
1309        assert!(config.tool_confirmation_decisions.is_empty());
1310        assert_eq!(config.max_transfer_depth, None);
1311    }
1312
1313    #[test]
1314    fn test_streaming_mode() {
1315        assert_eq!(StreamingMode::SSE, StreamingMode::SSE);
1316        assert_ne!(StreamingMode::SSE, StreamingMode::None);
1317        assert_ne!(StreamingMode::None, StreamingMode::Bidi);
1318    }
1319
1320    #[test]
1321    fn test_tool_confirmation_policy() {
1322        let policy = ToolConfirmationPolicy::default();
1323        assert!(!policy.requires_confirmation("search"));
1324
1325        let policy = policy.with_tool("search");
1326        assert!(policy.requires_confirmation("search"));
1327        assert!(!policy.requires_confirmation("write_file"));
1328
1329        assert!(ToolConfirmationPolicy::Always.requires_confirmation("any_tool"));
1330    }
1331
1332    #[test]
1333    fn test_validate_state_key_valid() {
1334        assert!(validate_state_key("user_name").is_ok());
1335        assert!(validate_state_key("app:config").is_ok());
1336        assert!(validate_state_key("temp:data").is_ok());
1337        assert!(validate_state_key("a").is_ok());
1338    }
1339
1340    #[test]
1341    fn test_validate_state_key_empty() {
1342        assert_eq!(validate_state_key(""), Err("state key must not be empty"));
1343    }
1344
1345    #[test]
1346    fn test_validate_state_key_too_long() {
1347        let long_key = "a".repeat(MAX_STATE_KEY_LEN + 1);
1348        assert!(validate_state_key(&long_key).is_err());
1349    }
1350
1351    #[test]
1352    fn test_validate_state_key_path_traversal() {
1353        assert!(validate_state_key("../etc/passwd").is_err());
1354        assert!(validate_state_key("foo/bar").is_err());
1355        assert!(validate_state_key("foo\\bar").is_err());
1356        assert!(validate_state_key("..").is_err());
1357    }
1358
1359    #[test]
1360    fn test_validate_state_key_null_byte() {
1361        assert!(validate_state_key("foo\0bar").is_err());
1362    }
1363
1364    #[test]
1365    fn test_run_config_builder_defaults() {
1366        let config = RunConfig::builder().build();
1367        let default = RunConfig::default();
1368        assert_eq!(config.streaming_mode, default.streaming_mode);
1369        assert_eq!(config.auto_cache, default.auto_cache);
1370        assert_eq!(config.history_max_events, default.history_max_events);
1371        assert_eq!(config.record_payloads, default.record_payloads);
1372        assert_eq!(config.trace_payload_max_bytes, default.trace_payload_max_bytes);
1373        assert!(config.tool_confirmation_decisions.is_empty());
1374        assert!(config.transfer_targets.is_empty());
1375        assert!(config.cached_content.is_none());
1376        assert!(config.parent_agent.is_none());
1377    }
1378
1379    #[test]
1380    fn test_run_config_builder_all_fields() {
1381        let mut decisions = HashMap::new();
1382        decisions.insert("delete".to_string(), ToolConfirmationDecision::Approve);
1383
1384        let config = RunConfig::builder()
1385            .streaming_mode(StreamingMode::None)
1386            .tool_confirmation_decisions(decisions.clone())
1387            .cached_content("my-cache")
1388            .transfer_targets(vec!["agent_a".to_string(), "agent_b".to_string()])
1389            .parent_agent("parent")
1390            .auto_cache(false)
1391            .history_max_events(Some(50))
1392            .tool_concurrency(ToolConcurrencyConfig {
1393                max_concurrency: Some(4),
1394                per_tool: HashMap::new(),
1395                backpressure: BackpressurePolicy::Fail,
1396            })
1397            .record_payloads(true)
1398            .trace_payload_max_bytes(4096)
1399            .build();
1400
1401        assert_eq!(config.streaming_mode, StreamingMode::None);
1402        assert_eq!(config.tool_confirmation_decisions, decisions);
1403        assert_eq!(config.cached_content.as_deref(), Some("my-cache"));
1404        assert_eq!(config.transfer_targets, vec!["agent_a", "agent_b"]);
1405        assert_eq!(config.parent_agent.as_deref(), Some("parent"));
1406        assert!(!config.auto_cache);
1407        assert_eq!(config.history_max_events, Some(50));
1408        assert_eq!(config.tool_concurrency.max_concurrency, Some(4));
1409        assert_eq!(config.tool_concurrency.backpressure, BackpressurePolicy::Fail);
1410        assert!(config.record_payloads);
1411        assert_eq!(config.trace_payload_max_bytes, 4096);
1412    }
1413}