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