Skip to main content

vv_agent/tools/base/
context.rs

1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use serde_json::Value;
6
7use super::paths::resolve_workspace_path_checked;
8use super::SubTaskRunner;
9use crate::workspace::{LocalWorkspaceBackend, WorkspaceBackend};
10
11#[derive(Clone)]
12pub struct ToolContext {
13    pub workspace: PathBuf,
14    pub shared_state: BTreeMap<String, Value>,
15    pub cycle_index: u32,
16    pub task_id: String,
17    pub metadata: BTreeMap<String, Value>,
18    pub workspace_backend: Arc<dyn WorkspaceBackend>,
19    pub sub_task_runner: Option<SubTaskRunner>,
20    pub sub_task_manager: Option<crate::runtime::sub_task_manager::SubTaskManager>,
21    pub execution_backend: Option<crate::runtime::backends::RuntimeExecutionBackend>,
22}
23
24impl std::fmt::Debug for ToolContext {
25    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        formatter
27            .debug_struct("ToolContext")
28            .field("workspace", &self.workspace)
29            .field("shared_state", &self.shared_state)
30            .field("cycle_index", &self.cycle_index)
31            .field("task_id", &self.task_id)
32            .field("metadata", &self.metadata)
33            .field("has_sub_task_runner", &self.sub_task_runner.is_some())
34            .field("has_sub_task_manager", &self.sub_task_manager.is_some())
35            .field("has_execution_backend", &self.execution_backend.is_some())
36            .finish_non_exhaustive()
37    }
38}
39
40impl ToolContext {
41    pub fn new(workspace: impl Into<PathBuf>) -> Self {
42        let workspace = workspace.into();
43        Self {
44            workspace: workspace.clone(),
45            shared_state: BTreeMap::new(),
46            cycle_index: 0,
47            task_id: String::new(),
48            metadata: BTreeMap::new(),
49            workspace_backend: Arc::new(crate::workspace::LocalWorkspaceBackend::new(workspace)),
50            sub_task_runner: None,
51            sub_task_manager: None,
52            execution_backend: None,
53        }
54    }
55
56    pub fn allow_outside_workspace_paths(&self) -> bool {
57        for key in [
58            "allow_outside_workspace_paths",
59            "allow_outside_workspace",
60            "workspace_allow_outside_main",
61            "workspace_allow_outside",
62        ] {
63            if let Some(parsed) = parse_bool(self.metadata.get(key)) {
64                return parsed;
65            }
66        }
67        false
68    }
69
70    pub fn resolve_workspace_path(&self, raw_path: &str) -> Result<PathBuf, String> {
71        resolve_workspace_path_checked(
72            &self.workspace,
73            raw_path,
74            self.allow_outside_workspace_paths(),
75        )
76    }
77
78    pub fn effective_workspace_backend(&self) -> Arc<dyn WorkspaceBackend> {
79        if self.allow_outside_workspace_paths() {
80            if let Some(local) = self
81                .workspace_backend
82                .as_any()
83                .downcast_ref::<LocalWorkspaceBackend>()
84            {
85                let mut local = local.clone();
86                local.allow_outside_root = true;
87                return Arc::new(local);
88            }
89        }
90        self.workspace_backend.clone()
91    }
92}
93
94fn parse_bool(value: Option<&Value>) -> Option<bool> {
95    match value {
96        Some(Value::Bool(value)) => Some(*value),
97        Some(Value::Number(number)) => number.as_i64().and_then(|value| match value {
98            0 => Some(false),
99            1 => Some(true),
100            _ => None,
101        }),
102        Some(Value::String(value)) => match value.trim().to_ascii_lowercase().as_str() {
103            "1" | "true" | "yes" | "on" => Some(true),
104            "0" | "false" | "no" | "off" => Some(false),
105            _ => None,
106        },
107        _ => None,
108    }
109}