Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ aws-smithy-runtime = { version = "1.10", features = ["connector-hyper-0-14-x", "
base64 = "0.22.1"
bytes = "1.11.1"
chrono = { version = "0.4.44", features = ["serde"] }
clap = { version = "4.6.0", features = ["derive"] }
clap = { version = "4.6.0", features = ["derive", "env"] }
clap_complete = "4.6.0"
colored = "3.1.1"
console = "0.16.3"
Expand Down
12 changes: 11 additions & 1 deletion crates/forge_api/src/forge_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use forge_app::{
ProviderAuthService, ProviderService, Services, User, UserUsage, Walker, WorkspaceService,
};
use forge_domain::{Agent, ConsoleWriter, *};
use forge_infra::ForgeInfra;
use forge_infra::{ForgeInfra, TensorlakeConfig};
use forge_repo::ForgeRepo;
use forge_services::ForgeServices;
use forge_stream::MpscStream;
Expand Down Expand Up @@ -40,13 +40,23 @@ impl<A, F> ForgeAPI<A, F> {
}

impl ForgeAPI<ForgeServices<ForgeRepo<ForgeInfra>>, ForgeRepo<ForgeInfra>> {
/// Initialises the API with the local command executor (default mode).
pub fn init(cwd: PathBuf) -> Self {
let infra = Arc::new(ForgeInfra::new(cwd));
let repo = Arc::new(ForgeRepo::new(infra.clone()));
let app = Arc::new(ForgeServices::new(repo.clone()));
ForgeAPI::new(app, repo)
}

/// Initialises the API routing all shell commands through an isolated
/// Tensorlake Firecracker microVM sandbox.
pub fn init_with_tensorlake(cwd: PathBuf, config: TensorlakeConfig) -> Self {
let infra = Arc::new(ForgeInfra::new_with_tensorlake(cwd, config));
let repo = Arc::new(ForgeRepo::new(infra.clone()));
let app = Arc::new(ForgeServices::new(repo.clone()));
ForgeAPI::new(app, repo)
}

pub async fn get_skills_internal(&self) -> Result<Vec<Skill>> {
use forge_domain::SkillRepository;
self.infra.load_skills().await
Expand Down
1 change: 1 addition & 0 deletions crates/forge_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub use forge_api::*;
pub use forge_app::dto::*;
pub use forge_app::{Plan, UsageInfo, UserUsage};
pub use forge_domain::{Agent, *};
pub use forge_infra::TensorlakeConfig;
1 change: 1 addition & 0 deletions crates/forge_infra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ serial_test = "3.4"
fake = { version = "5.1.0", features = ["derive"] }
pretty_assertions.workspace = true
forge_domain = { path = "../forge_domain" }
mockito = { workspace = true }
84 changes: 76 additions & 8 deletions crates/forge_infra/src/forge_infra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,20 @@ use crate::http::ForgeHttpInfra;
use crate::inquire::ForgeInquire;
use crate::mcp_client::ForgeMcpClient;
use crate::mcp_server::ForgeMcpServer;
use crate::tensorlake::{TensorlakeCommandExecutor, TensorlakeConfig};
use crate::walker::ForgeWalkerService;

/// Abstraction over the available command execution backends.
///
/// Commands are either executed locally on the host machine via
/// `ForgeCommandExecutorService`, or inside an isolated Tensorlake
/// Firecracker microVM via `TensorlakeCommandExecutor`.
#[derive(Clone)]
enum CommandExecutor {
Local(Arc<ForgeCommandExecutorService>),
Tensorlake(Arc<TensorlakeCommandExecutor>),
}

#[derive(Clone)]
pub struct ForgeInfra {
// TODO: Drop the "Service" suffix. Use names like ForgeFileReader, ForgeFileWriter,
Expand All @@ -44,7 +56,7 @@ pub struct ForgeInfra {
file_meta_service: Arc<ForgeFileMetaService>,
create_dirs_service: Arc<ForgeCreateDirsService>,
directory_reader_service: Arc<ForgeDirectoryReaderService>,
command_executor_service: Arc<ForgeCommandExecutorService>,
command_executor: CommandExecutor,
inquire_service: Arc<ForgeInquire>,
mcp_server: ForgeMcpServer,
walker_service: Arc<ForgeWalkerService>,
Expand All @@ -55,6 +67,8 @@ pub struct ForgeInfra {
}

impl ForgeInfra {
/// Creates a `ForgeInfra` instance that executes shell commands locally on
/// the host machine.
pub fn new(cwd: PathBuf) -> Self {
let config_infra = Arc::new(ForgeEnvironmentInfra::new(cwd));
let env = config_infra.get_environment();
Expand All @@ -76,9 +90,49 @@ impl ForgeInfra {
file_meta_service,
create_dirs_service: Arc::new(ForgeCreateDirsService),
directory_reader_service,
command_executor_service: Arc::new(ForgeCommandExecutorService::new(
command_executor: CommandExecutor::Local(Arc::new(ForgeCommandExecutorService::new(
env.clone(),
output_printer.clone(),
))),
inquire_service: Arc::new(ForgeInquire::new()),
mcp_server: ForgeMcpServer,
walker_service: Arc::new(ForgeWalkerService::new()),
strategy_factory: Arc::new(ForgeAuthStrategyFactory::new()),
http_service,
grpc_client,
output_printer,
}
}

/// Creates a `ForgeInfra` instance that executes shell commands inside an
/// isolated Tensorlake Firecracker microVM sandbox.
///
/// A single sandbox is provisioned lazily on the first command execution
/// and reused for the entire session. The sandbox is terminated when
/// the returned `ForgeInfra` is dropped.
pub fn new_with_tensorlake(cwd: PathBuf, config: TensorlakeConfig) -> Self {
let config_infra = Arc::new(ForgeEnvironmentInfra::new(cwd));
let env = config_infra.get_environment();

let file_write_service = Arc::new(ForgeFileWriteService::new());
let http_service = Arc::new(ForgeHttpInfra::new(env.clone(), file_write_service.clone()));
let file_read_service = Arc::new(ForgeFileReadService::new());
let file_meta_service = Arc::new(ForgeFileMetaService);
let directory_reader_service =
Arc::new(ForgeDirectoryReaderService::new(env.parallel_file_reads));
let grpc_client = Arc::new(ForgeGrpcClient::new(env.service_url.clone()));
let output_printer = Arc::new(StdConsoleWriter::default());

Self {
file_read_service,
file_write_service,
file_remove_service: Arc::new(ForgeFileRemoveService::new()),
config_infra,
file_meta_service,
create_dirs_service: Arc::new(ForgeCreateDirsService),
directory_reader_service,
command_executor: CommandExecutor::Tensorlake(Arc::new(
TensorlakeCommandExecutor::new(config),
)),
inquire_service: Arc::new(ForgeInquire::new()),
mcp_server: ForgeMcpServer,
Expand Down Expand Up @@ -196,9 +250,16 @@ impl CommandInfra for ForgeInfra {
silent: bool,
env_vars: Option<Vec<String>>,
) -> anyhow::Result<CommandOutput> {
self.command_executor_service
.execute_command(command, working_dir, silent, env_vars)
.await
match &self.command_executor {
CommandExecutor::Local(svc) => {
svc.execute_command(command, working_dir, silent, env_vars)
.await
}
CommandExecutor::Tensorlake(svc) => {
svc.execute_command(command, working_dir, silent, env_vars)
.await
}
}
}

async fn execute_command_raw(
Expand All @@ -207,9 +268,16 @@ impl CommandInfra for ForgeInfra {
working_dir: PathBuf,
env_vars: Option<Vec<String>>,
) -> anyhow::Result<ExitStatus> {
self.command_executor_service
.execute_command_raw(command, working_dir, env_vars)
.await
match &self.command_executor {
CommandExecutor::Local(svc) => {
svc.execute_command_raw(command, working_dir, env_vars)
.await
}
CommandExecutor::Tensorlake(svc) => {
svc.execute_command_raw(command, working_dir, env_vars)
.await
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/forge_infra/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod console;
mod env;
pub mod executor;
pub mod tensorlake;

mod auth;
mod error;
Expand All @@ -24,3 +25,4 @@ pub use env::ForgeEnvironmentInfra;
pub use executor::ForgeCommandExecutorService;
pub use forge_infra::*;
pub use kv_storage::CacacheStorage;
pub use tensorlake::{TensorlakeCommandExecutor, TensorlakeConfig};
Loading
Loading