1use std::collections::BTreeMap;
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 described: tokio::sync::Mutex<BTreeMap<String, ConnectorDescription>>,
45 sequence: AtomicU64,
46}
47
48impl DispatchSession {
49 pub async fn new(
51 runtime: Arc<CodeModeRuntime>,
52 execution_id: impl Into<String>,
53 connectors: Vec<Arc<dyn Connector>>,
54 ) -> Result<Self, String> {
55 let execution_id = execution_id.into();
56 let mut descriptions = Vec::new();
57 for connector in &connectors {
58 descriptions.push(connector.describe().await?);
59 }
60 Self::new_with_descriptions(runtime, execution_id, connectors, descriptions).await
61 }
62
63 pub async fn new_with_descriptions(
65 runtime: Arc<CodeModeRuntime>,
66 execution_id: impl Into<String>,
67 connectors: Vec<Arc<dyn Connector>>,
68 snapshot: Vec<ConnectorDescription>,
69 ) -> Result<Self, String> {
70 let execution_id = execution_id.into();
71 Self::new_with_descriptions_and_context(
72 runtime,
73 ToolContext {
74 execution_id,
75 control: Default::default(),
76 request: None,
77 },
78 connectors,
79 snapshot,
80 )
81 .await
82 }
83
84 pub async fn new_with_descriptions_and_context(
86 runtime: Arc<CodeModeRuntime>,
87 context: ToolContext,
88 connectors: Vec<Arc<dyn Connector>>,
89 snapshot: Vec<ConnectorDescription>,
90 ) -> Result<Self, String> {
91 let mut resolved = BTreeMap::new();
95 for connector in connectors {
96 let name = connector.name().to_string();
97 if resolved.insert(name.clone(), connector).is_some() {
98 return Err(format!("Duplicate connector name \"{name}\""));
99 }
100 }
101 let descriptions = snapshot
102 .into_iter()
103 .map(|description| (description.name.clone(), description))
104 .collect();
105 Ok(Self {
106 runtime,
107 execution_id: context.execution_id.clone(),
108 context,
109 connectors: resolved,
110 descriptions,
111 described: tokio::sync::Mutex::new(BTreeMap::new()),
112 sequence: AtomicU64::new(0),
113 })
114 }
115
116 pub fn descriptions(&self) -> Vec<ConnectorDescription> {
118 self.descriptions.values().cloned().collect()
119 }
120
121 async fn describe_once(&self, name: &str) -> Result<ConnectorDescription, String> {
128 {
133 let cache = self.described.lock().await;
134 if let Some(description) = cache.get(name) {
135 return Ok(description.clone());
136 }
137 }
138 let connector = self
139 .connectors
140 .get(name)
141 .ok_or_else(|| capability_unavailable(name, ""))?;
142 let description = connector.describe().await?;
143 self.described
144 .lock()
145 .await
146 .insert(name.to_string(), description.clone());
147 Ok(description)
148 }
149
150 async fn described_all(&self) -> Result<Vec<ConnectorDescription>, String> {
156 let names: Vec<String> = self.connectors.keys().cloned().collect();
157 let mut descriptions = Vec::with_capacity(names.len());
158 for name in names {
159 descriptions.push(self.describe_once(&name).await?);
160 }
161 Ok(descriptions)
162 }
163
164 pub async fn call(
166 &self,
167 connector: &str,
168 method: &str,
169 arguments: Value,
170 now: u64,
171 ) -> DispatchResponse {
172 let seq = self.sequence.fetch_add(1, Ordering::Relaxed);
173 self.call_at(seq, connector, method, arguments, now).await
174 }
175
176 pub async fn call_at(
178 &self,
179 seq: u64,
180 connector: &str,
181 method: &str,
182 arguments: Value,
183 now: u64,
184 ) -> DispatchResponse {
185 match self
186 .call_inner(seq, connector, method, arguments, now)
187 .await
188 {
189 Ok(response) => response,
190 Err(error) => DispatchResponse {
191 result: None,
192 control: Some("error".to_string()),
193 message: Some(error),
194 },
195 }
196 }
197
198 pub async fn begin_step(&self, name: &str, now: u64) -> Result<StepResponse, RuntimeError> {
200 let seq = self.sequence.fetch_add(1, Ordering::Relaxed);
201 self.begin_step_at(seq, name, now).await
202 }
203
204 pub async fn begin_step_at(
206 &self,
207 seq: u64,
208 name: &str,
209 now: u64,
210 ) -> Result<StepResponse, RuntimeError> {
211 Ok(
212 match self
213 .runtime
214 .decide(
215 &self.execution_id,
216 seq,
217 "__step",
218 name,
219 Value::Null,
220 false,
221 false,
222 now,
223 )
224 .await?
225 {
226 ToolDecision::Replay(result) => StepResponse {
227 kind: "replay".to_string(),
228 seq,
229 result: Some(result),
230 },
231 ToolDecision::Execute(seq) => StepResponse {
232 kind: "execute".to_string(),
233 seq,
234 result: None,
235 },
236 ToolDecision::Pause(seq) => StepResponse {
237 kind: "pause".to_string(),
238 seq,
239 result: None,
240 },
241 },
242 )
243 }
244
245 pub async fn record_step(&self, seq: u64, result: Value, now: u64) -> Result<(), RuntimeError> {
247 self.runtime
248 .record_result(&self.execution_id, seq, result, now)
249 .await
250 }
251
252 pub async fn pass_ended(&self, status: &str) {
254 for connector in self.connectors.values() {
255 connector.pass_ended(&self.execution_id, status).await;
256 }
257 }
258
259 pub async fn execution_ended(&self, status: &str) {
261 for connector in self.connectors.values() {
262 connector.execution_ended(&self.execution_id, status).await;
263 }
264 }
265
266 async fn call_inner(
267 &self,
268 seq: u64,
269 connector_name: &str,
270 method: &str,
271 arguments: Value,
272 now: u64,
273 ) -> Result<DispatchResponse, String> {
274 if connector_name == "codemode" {
275 return self.call_builtin(seq, method, arguments, now).await;
276 }
277 let connector = self
278 .connectors
279 .get(connector_name)
280 .ok_or_else(|| capability_unavailable(connector_name, method))?;
281 let description = self.describe_once(connector_name).await?;
282 let tool = description
283 .tools
284 .iter()
285 .find(|tool| tool.name == method)
286 .ok_or_else(|| format!("Tool \"{method}\" not found on {connector_name}"))?;
287 match self
288 .runtime
289 .decide(
290 &self.execution_id,
291 seq,
292 connector_name,
293 method,
294 arguments.clone(),
295 tool.policy.requires_approval,
296 tool.policy.replay == ReplayPolicy::Reexecute,
297 now,
298 )
299 .await
300 .map_err(|error| error.to_string())?
301 {
302 ToolDecision::Replay(result) => Ok(DispatchResponse {
303 result: Some(result),
304 control: None,
305 message: None,
306 }),
307 ToolDecision::Pause(_) => Ok(DispatchResponse {
308 result: None,
309 control: Some("pause".to_string()),
310 message: None,
311 }),
312 ToolDecision::Execute(seq) => {
313 let result = connector.execute(method, arguments, &self.context).await?;
314 self.runtime
315 .record_result(&self.execution_id, seq, result.clone(), now)
316 .await
317 .map_err(|error| error.to_string())?;
318 Ok(DispatchResponse {
319 result: Some(result),
320 control: None,
321 message: None,
322 })
323 }
324 }
325 }
326
327 async fn call_builtin(
328 &self,
329 seq: u64,
330 method: &str,
331 arguments: Value,
332 now: u64,
333 ) -> Result<DispatchResponse, String> {
334 match self
335 .runtime
336 .decide(
337 &self.execution_id,
338 seq,
339 "codemode",
340 method,
341 arguments.clone(),
342 false,
343 true,
344 now,
345 )
346 .await
347 .map_err(|error| error.to_string())?
348 {
349 ToolDecision::Pause(_) => Ok(DispatchResponse {
350 result: None,
351 control: Some("pause".to_string()),
352 message: None,
353 }),
354 ToolDecision::Replay(result) => Ok(DispatchResponse {
355 result: Some(result),
356 control: None,
357 message: None,
358 }),
359 ToolDecision::Execute(seq) => {
360 let connectors = self.described_all().await?;
361 let snippets = self
362 .runtime
363 .snippets()
364 .await
365 .map_err(|error| error.to_string())?;
366 let value = match method {
367 "search" => serde_json::to_value(search(
368 arguments
369 .get("query")
370 .and_then(Value::as_str)
371 .unwrap_or_default(),
372 &connectors,
373 &snippets,
374 )),
375 "describe" => serde_json::to_value(describe(
376 arguments
377 .get("target")
378 .and_then(Value::as_str)
379 .unwrap_or_default(),
380 &connectors,
381 &snippets,
382 )),
383 _ => return Err(format!("Tool \"{method}\" not found on codemode")),
384 }
385 .map_err(|error| error.to_string())?;
386 self.runtime
387 .record_result(&self.execution_id, seq, value.clone(), now)
388 .await
389 .map_err(|error| error.to_string())?;
390 Ok(DispatchResponse {
391 result: Some(value),
392 control: None,
393 message: None,
394 })
395 }
396 }
397 }
398}
399
400fn capability_unavailable(connector: &str, method: &str) -> String {
401 serde_json::json!({
402 "code": "CAPABILITY_UNAVAILABLE",
403 "message": format!(
404 "Capability {connector}.{method} existed when the execution began but is unavailable"
405 ),
406 "connector": connector,
407 "method": method,
408 })
409 .to_string()
410}