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}