Skip to main content

agentcat_sdk/
context.rs

1//! [`CallContext`]: what a tool can read about its own call while `AgentCat` tracks it.
2
3use std::time::SystemTime;
4
5use rmcp::model::Implementation;
6
7use crate::client::AgentCat;
8use crate::custom::{CustomEvent, publish_server};
9use crate::error::Error;
10use crate::event::ClientIdentity;
11
12/// The tracked call a tool is running in. The SDK puts one into the request context of every tool
13/// call it passes to the handler, so a tool can take it as an `Extension<CallContext>` (rmcp's
14/// `rmcp::handler::server::common::Extension`) next to its parameters. A call the SDK does not
15/// trace (tracing off, no session ID could be issued, or `AGENTCAT_ENABLED` turned the SDK off)
16/// gets one too, with no session and no agent.
17///
18/// ```no_run
19/// use agentcat_sdk::CallContext;
20/// use rmcp::handler::server::common::Extension;
21///
22/// // A parameter of a `#[tool]` method, next to its `Parameters<..>`.
23/// fn session_of(Extension(call): Extension<CallContext>) -> String {
24///     call.session_id().unwrap_or("no session").to_owned()
25/// }
26/// # let _ = session_of;
27/// ```
28#[derive(Clone, Debug)]
29pub struct CallContext {
30    /// The session of the call; `""` means none.
31    session_id: String,
32    /// The agent the call names.
33    agent_id: Option<String>,
34    agentcat: AgentCat,
35    /// The client of the call, for the events the tool publishes.
36    client: Option<ClientIdentity>,
37    /// The server info of the handler at the start of the call.
38    server: Option<Implementation>,
39}
40
41impl CallContext {
42    /// The context of a call with the session `session_id` (`""` for none) and the agent
43    /// `agent_id`, tracked by `agentcat`.
44    pub(crate) fn new(session_id: String, agent_id: Option<String>, agentcat: AgentCat) -> Self {
45        Self {
46            session_id,
47            agent_id,
48            agentcat,
49            client: None,
50            server: None,
51        }
52    }
53
54    /// This context with the client and the server info of the call. A call the SDK does not
55    /// trace has none.
56    pub(crate) fn with_identity(
57        mut self,
58        client: Option<ClientIdentity>,
59        server: Option<Implementation>,
60    ) -> Self {
61        self.client = client;
62        self.server = server;
63        self
64    }
65
66    /// The session ID the call belongs to: the one the agent sent, or the one the SDK issued for
67    /// this call. `None` when the call has no session: the agent sent a value this server did not
68    /// issue, the tool declares its own `session_id` parameter, or the SDK does not trace the
69    /// call. Also always `None` with a
70    /// [`resolve_session_id`](crate::Options::resolve_session_id) hook set: the hook decides the
71    /// session after the call starts, so the context has none.
72    pub fn session_id(&self) -> Option<&str> {
73        (!self.session_id.is_empty()).then_some(self.session_id.as_str())
74    }
75
76    /// The `agent_id` the agent sent, trimmed; `None` when agent tracking is off, the agent sent
77    /// none, or the SDK does not trace the call.
78    pub fn agent_id(&self) -> Option<&str> {
79        self.agent_id.as_deref()
80    }
81
82    /// The handle that tracks the call.
83    pub fn agentcat(&self) -> &AgentCat {
84        &self.agentcat
85    }
86}
87
88impl CallContext {
89    /// Publishes a custom event with this call's client and server identity, as a tracked server
90    /// publishes it. It only queues the event, so it returns at once.
91    ///
92    /// The event goes to the project of the handle ([`AgentCat::new`](crate::AgentCat::new)), not
93    /// to `project_id`, which only has to be non-empty. Nothing is queued while tracing is off, or
94    /// by a handle that `AGENTCAT_ENABLED` turned off. The `redact_event` hook of the options runs
95    /// on the event, on the runtime of the caller; the string hook does not. The event has no
96    /// session unless the [`CustomEvent`] names one: it takes none from the call. Pass
97    /// [`session_id()`](Self::session_id) in the [`CustomEvent`] to attribute the event to the
98    /// call's session. A `duration` of zero or none is the time since
99    /// the event was built.
100    ///
101    /// ```no_run
102    /// # fn run(call: agentcat_sdk::CallContext) -> Result<(), agentcat_sdk::Error> {
103    /// use agentcat_sdk::CustomEvent;
104    ///
105    /// let mut event = CustomEvent::new().resource_name("cache-warmed");
106    /// if let Some(session) = call.session_id() {
107    ///     event = event.session_id(session);
108    /// }
109    /// call.publish_custom_event("proj_123", event)?;
110    /// # Ok(())
111    /// # }
112    /// ```
113    ///
114    /// # Errors
115    ///
116    /// [`Error::ProjectIdRequired`] when `project_id` is empty, except on a handle that
117    /// `AGENTCAT_ENABLED` turned off, which returns `Ok(())` and does nothing.
118    pub fn publish_custom_event(&self, project_id: &str, event: CustomEvent) -> Result<(), Error> {
119        let created = SystemTime::now();
120        publish_server(
121            &self.agentcat,
122            project_id,
123            event,
124            self.client.as_ref(),
125            self.server.as_ref(),
126            created,
127            SystemTime::now(),
128        )
129    }
130}