Skip to main content

machi_tools/
context.rs

1//! Per-call execution context.
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use machi_types::{AgentId, Deadline, SessionId};
8use tokio_util::sync::CancellationToken;
9
10/// Extra key: nesting depth of the agent that owns this tool call
11/// (`0` = first host-spawned level). Used by `spawn_agent` to fail-closed on depth.
12pub const EXTRA_SPAWN_DEPTH: &str = "machi.spawn_depth";
13
14/// Context passed into every tool invocation.
15#[derive(Debug, Clone)]
16pub struct ToolCallContext {
17    /// Cancellation token for the call / turn.
18    pub cancel: CancellationToken,
19    /// Optional absolute deadline.
20    pub deadline: Option<Deadline>,
21    /// Working directory for relative paths.
22    pub cwd: Option<PathBuf>,
23    /// Session id when known.
24    pub session_id: Option<SessionId>,
25    /// Agent id when known.
26    pub agent_id: Option<AgentId>,
27    /// Host-defined extensions (stringly map for v1).
28    pub extras: Arc<HashMap<String, String>>,
29}
30
31impl Default for ToolCallContext {
32    fn default() -> Self {
33        Self {
34            cancel: CancellationToken::new(),
35            deadline: None,
36            cwd: None,
37            session_id: None,
38            agent_id: None,
39            extras: Arc::new(HashMap::new()),
40        }
41    }
42}
43
44impl ToolCallContext {
45    /// Builder: set cancel token.
46    #[must_use]
47    pub fn with_cancel(mut self, cancel: CancellationToken) -> Self {
48        self.cancel = cancel;
49        self
50    }
51
52    /// Builder: set deadline.
53    #[must_use]
54    pub fn with_deadline(mut self, deadline: Deadline) -> Self {
55        self.deadline = Some(deadline);
56        self
57    }
58
59    /// Builder: replace extras map.
60    #[must_use]
61    pub fn with_extras(mut self, extras: HashMap<String, String>) -> Self {
62        self.extras = Arc::new(extras);
63        self
64    }
65
66    /// Read nesting depth of the current agent (`None` = top-level session turn).
67    #[must_use]
68    pub fn spawn_depth(&self) -> Option<u32> {
69        self.extras
70            .get(EXTRA_SPAWN_DEPTH)
71            .and_then(|s| s.parse().ok())
72    }
73
74    /// True when cancel requested or deadline expired.
75    #[must_use]
76    pub fn is_cancelled(&self) -> bool {
77        self.cancel.is_cancelled() || self.deadline.is_some_and(|d| d.is_expired())
78    }
79}