monoloop_loop/transaction/
loop_adapters.rs1use super::dispatcher::{DispatchOutcome, DispatchRequest, TransactionToolDispatcher};
4use crate::registry::{
5 ResolveToolRequest, ToolDescriptorRef, ToolRegistry, ToolRegistryError, ToolResolution,
6};
7use crate::tools::{
8 StartToolExecution, ToolExecutionHandle, ToolRuntime, ToolRuntimeError, ToolRuntimeTerminal,
9};
10use monoloop_contracts::{
11 ExchangeId, OutboundToolOutcome, ToolActionId, ToolName, ToolUnavailableReason,
12};
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16use tokio::sync::oneshot;
17
18type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
19
20pub struct ResolvedToolRegistry {
22 tools: super::resolved_tools::ResolvedToolSet,
23}
24
25impl ResolvedToolRegistry {
26 pub fn new(tools: super::resolved_tools::ResolvedToolSet) -> Self {
28 Self { tools }
29 }
30}
31
32impl ToolRegistry for ResolvedToolRegistry {
33 fn resolve<'a>(
34 &'a self,
35 request: ResolveToolRequest,
36 ) -> BoxFuture<'a, Result<ToolResolution, ToolRegistryError>> {
37 Box::pin(async move {
38 let name = match ToolName::try_new(&request.tool_name) {
39 Ok(n) => n,
40 Err(_) => {
41 return Ok(ToolResolution::Unavailable(ToolUnavailableReason::NotFound));
42 }
43 };
44 if self.tools.contains_name(&name) {
45 Ok(ToolResolution::Available(ToolDescriptorRef {
46 name: request.tool_name,
47 }))
48 } else if self.tools.is_empty() {
49 Ok(ToolResolution::Unavailable(
50 ToolUnavailableReason::NoRegisteredTool,
51 ))
52 } else {
53 Ok(ToolResolution::Unavailable(ToolUnavailableReason::NotFound))
54 }
55 })
56 }
57}
58
59pub struct HostToolRuntime {
61 dispatcher: Arc<TransactionToolDispatcher>,
62 exchange_id: ExchangeId,
63}
64
65impl HostToolRuntime {
66 pub fn new(dispatcher: Arc<TransactionToolDispatcher>, exchange_id: ExchangeId) -> Self {
68 Self {
69 dispatcher,
70 exchange_id,
71 }
72 }
73}
74
75impl ToolRuntime for HostToolRuntime {
76 fn start(&self, request: StartToolExecution) -> Result<ToolExecutionHandle, ToolRuntimeError> {
77 let name = ToolName::try_new(&request.tool_name)
78 .map_err(|_| ToolRuntimeError("invalid tool name".into()))?;
79 let action_id = ToolActionId::new(request.tool_action_id.clone());
80 let dispatcher = Arc::clone(&self.dispatcher);
81 let exchange_id = self.exchange_id;
82 let payload = request.request_payload;
83 let provider_id = request.execution_id.as_str().to_string();
84 let ordinal = request.request_generation as u32;
85 let (tx, rx) = oneshot::channel();
86 tokio::spawn(async move {
87 let outcome = dispatcher
88 .dispatch(DispatchRequest {
89 exchange_id,
90 tool_action_id: action_id,
91 tool_name: name,
92 provider_tool_call_id: provider_id,
93 request_ordinal: ordinal,
94 arguments_json: payload,
95 })
96 .await;
97 let _ = tx.send(map_outcome(outcome));
98 });
99
100 Ok(ToolExecutionHandle {
101 execution_id: request.execution_id,
102 completion: Some(rx),
103 })
104 }
105}
106
107fn map_outcome(outcome: DispatchOutcome) -> ToolRuntimeTerminal {
108 match outcome {
109 DispatchOutcome::Canonical { result, .. } => {
110 let payload = serde_json::to_string(&result.outcome)
111 .unwrap_or_else(|_| "{\"error\":\"encode\"}".into());
112 ToolRuntimeTerminal {
114 outcome: OutboundToolOutcome::Success,
115 payload,
116 }
117 }
118 DispatchOutcome::Rejected { code, message, .. } => ToolRuntimeTerminal {
119 outcome: OutboundToolOutcome::DispatchRejected,
120 payload: format!("{code}:{message}"),
121 },
122 DispatchOutcome::RuntimeFailed { code, .. } => ToolRuntimeTerminal {
123 outcome: OutboundToolOutcome::ExecutionFailed,
124 payload: code,
125 },
126 }
127}
128
129pub async fn dispatch_ready_tool(
131 dispatcher: &Arc<TransactionToolDispatcher>,
132 exchange_id: ExchangeId,
133 tool_action_id: ToolActionId,
134 tool_name: &str,
135 provider_tool_call_id: &str,
136 request_ordinal: u32,
137 arguments_json: &str,
138) -> DispatchOutcome {
139 dispatch_ready_tool_cancellable(
140 dispatcher,
141 exchange_id,
142 tool_action_id,
143 tool_name,
144 provider_tool_call_id,
145 request_ordinal,
146 arguments_json,
147 None,
148 )
149 .await
150}
151
152#[allow(clippy::too_many_arguments)]
154pub async fn dispatch_ready_tool_cancellable(
155 dispatcher: &Arc<TransactionToolDispatcher>,
156 exchange_id: ExchangeId,
157 tool_action_id: ToolActionId,
158 tool_name: &str,
159 provider_tool_call_id: &str,
160 request_ordinal: u32,
161 arguments_json: &str,
162 cancel: Option<std::sync::Arc<tokio::sync::Notify>>,
163) -> DispatchOutcome {
164 let name = match ToolName::try_new(tool_name) {
165 Ok(n) => n,
166 Err(_) => {
167 return DispatchOutcome::Rejected {
168 tool_action_id,
169 code: "invalid_tool_name",
170 message: "tool name failed identity validation".into(),
171 lifecycle: vec![],
172 };
173 }
174 };
175 dispatcher
176 .dispatch_with_cancel(
177 DispatchRequest {
178 exchange_id,
179 tool_action_id,
180 tool_name: name,
181 provider_tool_call_id: provider_tool_call_id.into(),
182 request_ordinal,
183 arguments_json: arguments_json.into(),
184 },
185 cancel,
186 )
187 .await
188}