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::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}