1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! Docker-based [`ToolSetProvider`] for L2 container sandbox.
//!
//! [`DockerToolSetProvider`] builds a tool registry where `run_shell` is
//! replaced by [`DockerShellTool`], transparently routing shell commands
//! into an isolated Docker container.
//!
//! Gated behind the `cloud-runtime` feature flag.
use std::path::PathBuf;
use std::sync::Arc;
use crate::tool_set_provider::{SandboxMode, ToolSetProvider};
use crate::tools::ToolRegistry;
use super::docker_sandbox::DockerShellTool;
/// Configuration for the Docker tool provider.
pub struct DockerToolSetProvider {
/// Docker image to use for the sandbox container.
pub image: String,
/// Workspace path that will be bind-mounted into the container.
pub workspace: PathBuf,
pub shell_timeout_secs: u64,
pub skills: Vec<crate::skills::Skill>,
}
impl DockerToolSetProvider {
pub fn new(
image: impl Into<String>,
workspace: PathBuf,
shell_timeout_secs: u64,
skills: Vec<crate::skills::Skill>,
) -> Self {
Self {
image: image.into(),
workspace,
shell_timeout_secs,
skills,
}
}
/// Use the default `ubuntu:22.04` image.
pub fn ubuntu(
workspace: PathBuf,
shell_timeout_secs: u64,
skills: Vec<crate::skills::Skill>,
) -> Self {
Self::new("ubuntu:22.04", workspace, shell_timeout_secs, skills)
}
}
impl ToolSetProvider for DockerToolSetProvider {
fn build_registry(&self) -> ToolRegistry {
// Build the standard registry (which includes a local run_shell).
let mut registry = crate::tools::build_standard_tools(
&self.workspace,
&self.skills,
self.shell_timeout_secs,
);
// Create a blocking runtime-local future to spin up the container.
// Note: `build_registry` is sync; we use `tokio::task::block_in_place`
// when called from async context, or fall back to a new runtime.
let image = self.image.clone();
let workspace = self.workspace.clone();
let timeout = self.shell_timeout_secs;
let docker_shell = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async move {
DockerShellTool::new(&image, workspace.as_path(), timeout).await
})
});
match docker_shell {
Ok(tool) => {
// register_mut replaces the existing "run_shell" entry.
registry.register_mut(Arc::new(tool));
}
Err(e) => {
tracing::warn!(
"DockerToolSetProvider: failed to start container, falling back to local shell: {e}"
);
// Fallback: keep the existing local run_shell
}
}
registry
}
fn sandbox_mode(&self) -> SandboxMode {
SandboxMode::Container
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn docker_provider_sandbox_mode_is_container() {
let dir = TempDir::new().unwrap();
let p = DockerToolSetProvider::ubuntu(dir.path().to_path_buf(), 30, vec![]);
assert_eq!(p.sandbox_mode(), SandboxMode::Container);
}
}