| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| use crate::{CellContext, CellDiagnostic, CellDiagnosticKind, ToolCall}; | |
| use maka_agent_facts::tools::{ToolError, ToolExecutor}; | |
| use serde_json::Value; | |
| use std::{ | |
| collections::HashSet, | |
| sync::{Arc, Mutex}, | |
| }; | |
| use tokio::{runtime::Handle, sync::oneshot}; | |
| use tokio_util::{sync::CancellationToken, task::TaskTracker}; | |
| #[derive(Default)] | |
| struct Admission { | |
| calls: Vec<ToolCall>, | |
| } | |
| pub struct ToolScope { | |
| context: CellContext, | |
| executor: Arc<dyn ToolExecutor>, | |
| names: HashSet<String>, | |
| cancellation: CancellationToken, | |
| tasks: TaskTracker, | |
| host: Handle, | |
| admission: Mutex<Admission>, | |
| } | |
| impl ToolScope { | |
| pub fn new( | |
| context: CellContext, | |
| executor: Arc<dyn ToolExecutor>, | |
| cancellation: CancellationToken, | |
| host: Handle, | |
| ) -> Arc<Self> { | |
| Arc::new(Self { | |
| names: executor.names().into_iter().collect(), | |
| context, | |
| executor, | |
| cancellation, | |
| tasks: TaskTracker::new(), | |
| host, | |
| admission: Mutex::new(Admission::default()), | |
| }) | |
| } | |
| pub fn cancel(&self) { | |
| self.cancellation.cancel(); | |
| } | |
| pub async fn settle(&self) { | |
| self.tasks.close(); | |
| self.tasks.wait().await; | |
| } | |
| pub fn calls(&self) -> Vec<ToolCall> { | |
| self.admission.lock().unwrap().calls.clone() | |
| } | |
| pub fn start( | |
| self: &Arc<Self>, | |
| name: String, | |
| input: Value, | |
| ) -> Result<oneshot::Receiver<Result<Value, CellDiagnostic>>, CellDiagnostic> { | |
| if self.cancellation.is_cancelled() { | |
| return Err(CellDiagnostic::new( | |
| CellDiagnosticKind::ExecutionError, | |
| "cell cancelled", | |
| )); | |
| } | |
| if !self.names.contains(&name) { | |
| return Err(CellDiagnostic::new( | |
| CellDiagnosticKind::UnknownTool, | |
| format!("unknown tool: {name}"), | |
| )); | |
| } | |
| { | |
| let mut admission = self.admission.lock().unwrap(); | |
| if self.cancellation.is_cancelled() { | |
| return Err(CellDiagnostic::new( | |
| CellDiagnosticKind::ExecutionError, | |
| "cell cancelled", | |
| )); | |
| } | |
| let index = admission.calls.len() + 1; | |
| let mut reserved = admission.calls.clone(); | |
| reserved.push(ToolCall { | |
| index, | |
| name: name.clone(), | |
| }); | |
| admission.calls = reserved; | |
| } | |
| let (sender, receiver) = oneshot::channel(); | |
| let scope = self.clone(); | |
| self.tasks.spawn_on( | |
| async move { | |
| use futures_util::FutureExt; | |
| let result = std::panic::AssertUnwindSafe(async { | |
| if scope.cancellation.is_cancelled() { | |
| return Err(ToolError::Failed( | |
| "cell cancelled before tool dispatch".into(), | |
| )); | |
| } | |
| scope | |
| .executor | |
| .invoke(name, input, scope.cancellation.clone()) | |
| .await | |
| }) | |
| .catch_unwind() | |
| .await; | |
| let result = result.unwrap_or_else(|_| { | |
| Err(ToolError::CleanupUnconfirmed( | |
| "tool executor panicked".into(), | |
| )) | |
| }); | |
| if let Err(error @ (ToolError::Persistence(_) | ToolError::CleanupUnconfirmed(_))) = | |
| &result | |
| { | |
| scope.cancellation.cancel(); | |
| scope.context.fail(error.clone()); | |
| } | |
| // The effect is settled even if user code no longer observes its result. | |
| let result = result.map_err(|error| { | |
| CellDiagnostic::new(CellDiagnosticKind::ToolFailure, error.to_string()) | |
| }); | |
| let _ = sender.send(result); | |
| }, | |
| &self.host, | |
| ); | |
| Ok(receiver) | |
| } | |
| } |