agent_sdk_core/testing/tool.rs
1//! Deterministic test-kit helpers for SDK consumers. Use these fakes and harnesses to
2//! exercise public contracts without live providers, real stores, product UI, network
3//! telemetry, or wall-clock-dependent infrastructure. They mutate only their
4//! in-memory state unless noted. This file contains the tool portion of that
5//! contract.
6//!
7use std::sync::{Arc, Mutex};
8
9use crate::{
10 capability::ExecutorRef,
11 domain::AgentError,
12 tool_ports::{ToolExecutionOutput, ToolExecutionRequest, ToolExecutor},
13};
14
15#[derive(Clone)]
16/// In-memory scripted tool executor fixture for SDK conformance tests.
17/// Use it to script deterministic behavior in memory; any transcript or endpoint mutation is documented on the method that performs it.
18pub struct ScriptedToolExecutor {
19 executor_ref: ExecutorRef,
20 output: ToolExecutionOutput,
21 calls: Arc<Mutex<Vec<ToolExecutionRequest>>>,
22}
23
24impl ScriptedToolExecutor {
25 /// Creates a new testing::tool value with explicit caller-provided
26 /// inputs. This constructor is data-only and performs no I/O or
27 /// external side effects.
28 ///
29 /// # Panics
30 ///
31 /// Panics if constructor invariants fail, such as invalid identifier
32 /// text or constructor-specific bounds. Use a fallible constructor such as
33 /// `try_new` when one is available for untrusted input.
34 pub fn new(executor_ref: ExecutorRef, output: ToolExecutionOutput) -> Self {
35 Self {
36 executor_ref,
37 output,
38 calls: Arc::new(Mutex::new(Vec::new())),
39 }
40 }
41
42 /// Operates on in-memory or journal-derived testing::tool state for
43 /// diagnostics and repair evidence. It does not create a second run loop
44 /// or product workflow owner.
45 pub fn calls(&self) -> Vec<ToolExecutionRequest> {
46 self.calls.lock().expect("tool executor calls lock").clone()
47 }
48
49 /// Returns the call count currently held by this value.
50 /// This reads deterministic in-memory test state and performs no external I/O.
51 pub fn call_count(&self) -> usize {
52 self.calls.lock().expect("tool executor calls lock").len()
53 }
54}
55
56impl ToolExecutor for ScriptedToolExecutor {
57 fn executor_ref(&self) -> &ExecutorRef {
58 &self.executor_ref
59 }
60
61 fn execute(&self, request: &ToolExecutionRequest) -> Result<ToolExecutionOutput, AgentError> {
62 self.calls
63 .lock()
64 .expect("tool executor calls lock")
65 .push(request.clone());
66 Ok(self.output.clone())
67 }
68}