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