1use std::any::Any;
10use std::future::{Future, IntoFuture};
11use std::pin::Pin;
12use std::sync::{Arc, Weak};
13
14use anyhow::anyhow;
15use everruns::ToolCallContext;
16use serde_json::Value;
17
18use crate::app::Mode;
19use crate::host::{Host, NewSession};
20
21#[derive(Clone)]
27pub struct Cx {
28 host: Weak<Host>,
29 agent: Option<&'static str>,
30 session: Option<String>,
31 call: Option<ToolCallContext>,
32}
33
34impl std::fmt::Debug for Cx {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("Cx")
37 .field("session", &self.session)
38 .field("agent", &self.agent)
39 .field("call", &self.call)
40 .finish()
41 }
42}
43
44impl Cx {
45 pub(crate) fn app(host: &Arc<Host>) -> Self {
47 Self {
48 host: Arc::downgrade(host),
49 agent: None,
50 session: None,
51 call: None,
52 }
53 }
54
55 pub(crate) fn tool(host: Weak<Host>, agent: &'static str, call: ToolCallContext) -> Self {
57 Self {
58 host,
59 agent: Some(agent),
60 session: Some(call.session_id().to_string()),
61 call: Some(call),
62 }
63 }
64
65 fn host(&self) -> crate::Result<Arc<Host>> {
66 self.host
67 .upgrade()
68 .ok_or_else(|| anyhow!("the serve host has shut down"))
69 }
70
71 pub fn session_id(&self) -> Option<&str> {
73 self.session.as_deref()
74 }
75
76 pub fn tool_call_id(&self) -> Option<&str> {
78 self.call.as_ref().map(ToolCallContext::tool_call_id)
79 }
80
81 pub fn turn_id(&self) -> Option<String> {
83 self.call.as_ref().and_then(ToolCallContext::turn_id)
84 }
85
86 pub fn agent(&self) -> Option<&'static str> {
88 self.agent
89 }
90
91 pub fn mode(&self) -> Option<Mode> {
93 self.host.upgrade().map(|host| host.mode)
94 }
95
96 pub fn connection<T: Any + Send + Sync>(&self) -> crate::Result<Arc<T>> {
99 let host = self.host()?;
100 host.app
101 .inner
102 .connections
103 .iter()
104 .find_map(|entry| entry.value.value.clone().downcast::<T>().ok())
105 .ok_or_else(|| anyhow!("no #[connection] returns `{}`", std::any::type_name::<T>()))
106 }
107
108 pub fn secret(&self, name: &'static str) -> crate::Result<String> {
110 crate::Secret::named(name).value()
111 }
112
113 pub async fn progress(&self, message: impl Into<String>) {
116 if let Some(call) = &self.call {
117 call.progress(message).await;
118 }
119 }
120
121 pub fn start_session(&self, input: impl Into<String>) -> StartSession {
124 StartSession {
125 cx: self.clone(),
126 input: input.into(),
127 agent: None,
128 deliver_to: None,
129 metadata: None,
130 }
131 }
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
136pub struct DeliveryTarget {
137 pub channel: String,
138 pub target: String,
139}
140
141impl DeliveryTarget {
142 pub fn new(channel: impl Into<String>, target: impl Into<String>) -> Self {
143 Self {
144 channel: channel.into(),
145 target: target.into(),
146 }
147 }
148
149 pub(crate) fn encode(&self) -> String {
151 format!("{}:{}", self.channel, self.target)
152 }
153
154 pub(crate) fn decode(value: &str) -> Option<Self> {
155 let (channel, target) = value.split_once(':')?;
156 Some(Self::new(channel, target))
157 }
158}
159
160#[must_use = "a StartSession does nothing until awaited"]
162pub struct StartSession {
163 cx: Cx,
164 input: String,
165 agent: Option<String>,
166 deliver_to: Option<DeliveryTarget>,
167 metadata: Option<Value>,
168}
169
170impl StartSession {
171 pub fn agent(mut self, name: impl Into<String>) -> Self {
173 self.agent = Some(name.into());
174 self
175 }
176
177 pub fn deliver_to(mut self, target: DeliveryTarget) -> Self {
179 self.deliver_to = Some(target);
180 self
181 }
182
183 pub fn metadata(mut self, metadata: Value) -> Self {
185 self.metadata = Some(metadata);
186 self
187 }
188}
189
190impl IntoFuture for StartSession {
191 type Output = crate::Result;
192 type IntoFuture = Pin<Box<dyn Future<Output = crate::Result> + Send>>;
193
194 fn into_future(self) -> Self::IntoFuture {
195 Box::pin(async move {
196 let host = self.cx.host()?;
197 let session = host
198 .create_session(NewSession {
199 agent: self.agent,
200 metadata: self.metadata,
201 deliver_to: self.deliver_to.map(|target| target.encode()),
202 ..NewSession::default()
203 })
204 .await?;
205 let turn = host.send(&session, self.input).await?;
206 let outcome = turn.wait().await?;
207 if !outcome.success {
208 anyhow::bail!(
209 "session {session} turn failed: {}",
210 outcome.error.unwrap_or_default()
211 );
212 }
213 Ok(())
214 })
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn delivery_target_keeps_colons_in_the_target() {
224 let target = DeliveryTarget::new("slack", "C1:1700000000.0001");
225 let decoded = DeliveryTarget::decode(&target.encode()).unwrap();
226 assert_eq!(decoded, target);
227 }
228}