mod await_shell; mod exec; mod gate; mod interaction; mod local; mod mcp; mod mcp_state; mod semble; use serde_json::Value; use tokio::sync::mpsc; use crate::{ cursor::proto::agent::v1 as pb, model::{ToolCall, ToolImageReference, ToolResult}, store::BlobId, Error, Result, }; use super::runtime::now_ms; pub(crate) use await_shell::{await_error, await_result, await_sleep}; pub(crate) use exec::{edit_failure, from_exec}; pub(crate) use interaction::{complete_web_fetch, complete_web_search, from_interaction}; pub(crate) use local::{local, subagents_disabled, todo_items}; pub(crate) use mcp::failure as mcp_failure; pub(crate) use semble::complete as semble; #[derive(Clone, Debug)] pub struct ToolCompletion { result: ToolResult, tool_call: pb::ToolCall, read_image: Option, } #[derive(Clone, Debug)] pub(crate) struct ReadImage { pub(crate) data: Vec, pub(crate) mime_type: String, pub(crate) path: String, } impl ToolCompletion { pub fn result(&self) -> &ToolResult { &self.result } pub fn tool_call(&self) -> &pb::ToolCall { &self.tool_call } pub(super) fn with_read_image(mut self, image: Option) -> Self { self.read_image = image; self } pub(crate) fn take_read_image(&mut self) -> Option { self.read_image.take() } pub(crate) fn persist_read_image(&mut self, blob_id: &BlobId, image: &ReadImage) -> Result<()> { self.result.content = format!("Read image file: {}", image.path); self.result.image = Some(ToolImageReference { blob_id: blob_id.to_base64(), mime_type: image.mime_type.clone(), path: image.path.clone(), }); let Some(pb::tool_call::Tool::ReadToolCall(call)) = self.tool_call.tool.as_mut() else { return Err(Error::Protocol( "Read image completion has no Read tool state".into(), )); }; let Some(pb::read_tool_result::Result::Success(success)) = call .result .as_mut() .and_then(|result| result.result.as_mut()) else { return Err(Error::Protocol( "Read image completion has no success state".into(), )); }; success.output = Some(pb::read_tool_success::Output::DataBlobId( blob_id.as_bytes().to_vec(), )); Ok(()) } pub(crate) fn new( call: &ToolCall, started_at_ms: u64, mut result: ToolResult, tool: pb::tool_call::Tool, ) -> Self { gate::model_content(&tool, &mut result.content); Self { result, tool_call: pb::ToolCall { tool_call_id: Some(call.call_id.clone()), started_at_ms: Some(started_at_ms), completed_at_ms: Some(now_ms()), tool: Some(tool), hook_additional_contexts: Vec::new(), }, read_image: None, } } pub(super) fn from_rendered( call: &ToolCall, started_at_ms: u64, output: String, is_error: bool, rendered: pb::ToolCall, ) -> Result { let tool = rendered.tool.ok_or_else(|| { Error::Protocol(format!("tool {} has no Cursor representation", call.name)) })?; Ok(Self::new( call, started_at_ms, ToolResult { call_id: call.call_id.clone(), content: output, is_error, image: None, }, tool, )) } } #[derive(Clone)] pub struct ToolResultSender(mpsc::UnboundedSender>); pub struct ToolResultReceiver(mpsc::UnboundedReceiver>); pub fn tool_result_channel() -> (ToolResultSender, ToolResultReceiver) { let (sender, receiver) = mpsc::unbounded_channel(); (ToolResultSender(sender), ToolResultReceiver(receiver)) } impl ToolResultSender { pub fn send(&self, result: ToolCompletion) { let _ = self.0.send(Ok(result)); } pub fn send_error(&self, error: Error) { let _ = self.0.send(Err(error)); } } impl ToolResultReceiver { pub async fn recv(&mut self) -> Option> { self.0.recv().await } } pub(super) fn prost_json(value: &prost_types::Value) -> Value { use prost_types::value::Kind; match value.kind.as_ref() { None | Some(Kind::NullValue(_)) => Value::Null, Some(Kind::NumberValue(value)) => serde_json::Number::from_f64(*value) .map(Value::Number) .unwrap_or(Value::Null), Some(Kind::StringValue(value)) => Value::String(value.clone()), Some(Kind::BoolValue(value)) => Value::Bool(*value), Some(Kind::StructValue(value)) => Value::Object( value .fields .iter() .map(|(key, value)| (key.clone(), prost_json(value))) .collect(), ), Some(Kind::ListValue(value)) => Value::Array(value.values.iter().map(prost_json).collect()), } }