1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU64, Ordering};
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::{
9 CodeModeRuntime, Connector, ConnectorDescription, ReplayPolicy, RuntimeError, ToolContext,
10 ToolDecision, describe, search,
11};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct DispatchResponse {
16 pub result: Option<Value>,
18 #[serde(rename = "__codemode_control__")]
20 pub control: Option<String>,
21 pub message: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct StepResponse {
28 pub kind: String,
30 pub seq: u64,
32 pub result: Option<Value>,
34}
35
36pub struct DispatchSession {
38 runtime: Arc<CodeModeRuntime>,
39 execution_id: String,
40 context: ToolContext,
41 connectors: BTreeMap<String, Arc<dyn Connector>>,
42 descriptions: BTreeMap<String, ConnectorDescription>,
43 available: BTreeMap<String, BTreeSet<String>>,
44 sequence: AtomicU64,
45}
46
47impl DispatchSession {
48 pub async fn new(
50 runtime: Arc<CodeModeRuntime>,
51 execution_id: impl Into<String>,
52 connectors: Vec<Arc<dyn Connector>>,
53 ) -> Result<Self, String> {
54 let execution_id = execution_id.into();
55 let mut descriptions = Vec::new();
56 for connector in &connectors {
57 descriptions.push(connector.describe().await?);
58 }
59 Self::new_with_descriptions(runtime, execution_id, connectors, descriptions).await
60 }
61
62 pub async fn new_with_descriptions(
64 runtime: Arc<CodeModeRuntime>,
65 execution_id: impl Into<String>,
66 connectors: Vec<Arc<dyn Connector>>,
67 snapshot: Vec<ConnectorDescription>,
68 ) -> Result<Self, String> {
69 let execution_id = execution_id.into();
70 Self::new_with_descriptions_and_context(
71 runtime,
72 ToolContext {
73 execution_id,
74 control: Default::default(),
75 request: None,
76 },
77 connectors,
78 snapshot,
79 )
80 .await
81 }
82
83 pub async fn new_with_descriptions_and_context(
85 runtime: Arc<CodeModeRuntime>,
86 context: ToolContext,
87 connectors: Vec<Arc<dyn Connector>>,
88 snapshot: Vec<ConnectorDescription>,
89 ) -> Result<Self, String> {
90 let mut resolved = BTreeMap::new();
91 let mut available = BTreeMap::new();
92 for connector in connectors {
93 let description = connector.describe().await?;
94 available.insert(
95 description.name.clone(),
96 description
97 .tools
98 .iter()
99 .map(|tool| tool.name.clone())
100 .collect(),
101 );
102 if resolved
103 .insert(description.name.clone(), connector)
104 .is_some()
105 {
106 return Err(format!("Duplicate connector name \"{}\"", description.name));
107 }
108 }
109 let descriptions = snapshot
110 .into_iter()
111 .map(|description| (description.name.clone(), description))
112 .collect();
113 Ok(Self {
114 runtime,
115 execution_id: context.execution_id.clone(),
116 context,
117 connectors: resolved,
118 descriptions,
119 available,
120 sequence: AtomicU64::new(0),
121 })
122 }
123
124 pub fn descriptions(&self) -> Vec<ConnectorDescription> {
126 self.descriptions.values().cloned().collect()
127 }
128
129 pub async fn call(
131 &self,
132 connector: &str,
133 method: &str,
134 arguments: Value,
135 now: u64,
136 ) -> DispatchResponse {
137 let seq = self.sequence.fetch_add(1, Ordering::Relaxed);
138 self.call_at(seq, connector, method, arguments, now).await
139 }
140
141 pub async fn call_at(
143 &self,
144 seq: u64,
145 connector: &str,
146 method: &str,
147 arguments: Value,
148 now: u64,
149 ) -> DispatchResponse {
150 match self
151 .call_inner(seq, connector, method, arguments, now)
152 .await
153 {
154 Ok(response) => response,
155 Err(error) => DispatchResponse {
156 result: None,
157 control: Some("error".to_string()),
158 message: Some(error),
159 },
160 }
161 }
162
163 pub async fn begin_step(&self, name: &str, now: u64) -> Result<StepResponse, RuntimeError> {
165 let seq = self.sequence.fetch_add(1, Ordering::Relaxed);
166 self.begin_step_at(seq, name, now).await
167 }
168
169 pub async fn begin_step_at(
171 &self,
172 seq: u64,
173 name: &str,
174 now: u64,
175 ) -> Result<StepResponse, RuntimeError> {
176 Ok(
177 match self
178 .runtime
179 .decide(
180 &self.execution_id,
181 seq,
182 "__step",
183 name,
184 Value::Null,
185 false,
186 false,
187 now,
188 )
189 .await?
190 {
191 ToolDecision::Replay(result) => StepResponse {
192 kind: "replay".to_string(),
193 seq,
194 result: Some(result),
195 },
196 ToolDecision::Execute(seq) => StepResponse {
197 kind: "execute".to_string(),
198 seq,
199 result: None,
200 },
201 ToolDecision::Pause(seq) => StepResponse {
202 kind: "pause".to_string(),
203 seq,
204 result: None,
205 },
206 },
207 )
208 }
209
210 pub async fn record_step(&self, seq: u64, result: Value, now: u64) -> Result<(), RuntimeError> {
212 self.runtime
213 .record_result(&self.execution_id, seq, result, now)
214 .await
215 }
216
217 pub async fn pass_ended(&self, status: &str) {
219 for connector in self.connectors.values() {
220 connector.pass_ended(&self.execution_id, status).await;
221 }
222 }
223
224 pub async fn execution_ended(&self, status: &str) {
226 for connector in self.connectors.values() {
227 connector.execution_ended(&self.execution_id, status).await;
228 }
229 }
230
231 async fn call_inner(
232 &self,
233 seq: u64,
234 connector_name: &str,
235 method: &str,
236 arguments: Value,
237 now: u64,
238 ) -> Result<DispatchResponse, String> {
239 if connector_name == "codemode" {
240 return self.call_builtin(seq, method, arguments, now).await;
241 }
242 let connector = self
243 .connectors
244 .get(connector_name)
245 .ok_or_else(|| capability_unavailable(connector_name, method))?;
246 let tool = self
247 .descriptions
248 .get(connector_name)
249 .and_then(|description| description.tools.iter().find(|tool| tool.name == method))
250 .ok_or_else(|| format!("Tool \"{method}\" not found on {connector_name}"))?;
251 if !self
252 .available
253 .get(connector_name)
254 .is_some_and(|tools| tools.contains(method))
255 {
256 return Err(capability_unavailable(connector_name, method));
257 }
258 match self
259 .runtime
260 .decide(
261 &self.execution_id,
262 seq,
263 connector_name,
264 method,
265 arguments.clone(),
266 tool.policy.requires_approval,
267 tool.policy.replay == ReplayPolicy::Reexecute,
268 now,
269 )
270 .await
271 .map_err(|error| error.to_string())?
272 {
273 ToolDecision::Replay(result) => Ok(DispatchResponse {
274 result: Some(result),
275 control: None,
276 message: None,
277 }),
278 ToolDecision::Pause(_) => Ok(DispatchResponse {
279 result: None,
280 control: Some("pause".to_string()),
281 message: None,
282 }),
283 ToolDecision::Execute(seq) => {
284 let result = connector.execute(method, arguments, &self.context).await?;
285 self.runtime
286 .record_result(&self.execution_id, seq, result.clone(), now)
287 .await
288 .map_err(|error| error.to_string())?;
289 Ok(DispatchResponse {
290 result: Some(result),
291 control: None,
292 message: None,
293 })
294 }
295 }
296 }
297
298 async fn call_builtin(
299 &self,
300 seq: u64,
301 method: &str,
302 arguments: Value,
303 now: u64,
304 ) -> Result<DispatchResponse, String> {
305 match self
306 .runtime
307 .decide(
308 &self.execution_id,
309 seq,
310 "codemode",
311 method,
312 arguments.clone(),
313 false,
314 true,
315 now,
316 )
317 .await
318 .map_err(|error| error.to_string())?
319 {
320 ToolDecision::Pause(_) => Ok(DispatchResponse {
321 result: None,
322 control: Some("pause".to_string()),
323 message: None,
324 }),
325 ToolDecision::Replay(result) => Ok(DispatchResponse {
326 result: Some(result),
327 control: None,
328 message: None,
329 }),
330 ToolDecision::Execute(seq) => {
331 let connectors = self.descriptions();
332 let snippets = self
333 .runtime
334 .snippets()
335 .await
336 .map_err(|error| error.to_string())?;
337 let value = match method {
338 "search" => serde_json::to_value(search(
339 arguments
340 .get("query")
341 .and_then(Value::as_str)
342 .unwrap_or_default(),
343 &connectors,
344 &snippets,
345 )),
346 "describe" => serde_json::to_value(describe(
347 arguments
348 .get("target")
349 .and_then(Value::as_str)
350 .unwrap_or_default(),
351 &connectors,
352 &snippets,
353 )),
354 _ => return Err(format!("Tool \"{method}\" not found on codemode")),
355 }
356 .map_err(|error| error.to_string())?;
357 self.runtime
358 .record_result(&self.execution_id, seq, value.clone(), now)
359 .await
360 .map_err(|error| error.to_string())?;
361 Ok(DispatchResponse {
362 result: Some(value),
363 control: None,
364 message: None,
365 })
366 }
367 }
368 }
369}
370
371fn capability_unavailable(connector: &str, method: &str) -> String {
372 serde_json::json!({
373 "code": "CAPABILITY_UNAVAILABLE",
374 "message": format!(
375 "Capability {connector}.{method} existed when the execution began but is unavailable"
376 ),
377 "connector": connector,
378 "method": method,
379 })
380 .to_string()
381}