mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-02 02:13:49 +00:00
Merge pull request #860 from fabro-sh/pebble-port-routes
Pin pebble main 6996942 and implement its PortRoutes trait
This commit is contained in:
commit
6cd4bb417b
4 changed files with 67 additions and 34 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -5867,7 +5867,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "pebble-agent"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/lithoscomputer/pebble?rev=430740f1114f859d6d173683f25b5007cf2023ca#430740f1114f859d6d173683f25b5007cf2023ca"
|
||||
source = "git+https://github.com/lithoscomputer/pebble?rev=69969420c9017ca15ac6c175c820a0cb8090866a#69969420c9017ca15ac6c175c820a0cb8090866a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-util",
|
||||
|
|
@ -5884,7 +5884,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "pebble-cli-core"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/lithoscomputer/pebble?rev=430740f1114f859d6d173683f25b5007cf2023ca#430740f1114f859d6d173683f25b5007cf2023ca"
|
||||
source = "git+https://github.com/lithoscomputer/pebble?rev=69969420c9017ca15ac6c175c820a0cb8090866a#69969420c9017ca15ac6c175c820a0cb8090866a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -5913,7 +5913,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "pebble-coding-agent"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/lithoscomputer/pebble?rev=430740f1114f859d6d173683f25b5007cf2023ca#430740f1114f859d6d173683f25b5007cf2023ca"
|
||||
source = "git+https://github.com/lithoscomputer/pebble?rev=69969420c9017ca15ac6c175c820a0cb8090866a#69969420c9017ca15ac6c175c820a0cb8090866a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-util",
|
||||
|
|
@ -5922,7 +5922,6 @@ dependencies = [
|
|||
"reqwest 0.13.4",
|
||||
"rmcp",
|
||||
"rustix",
|
||||
"sandbox-driver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
|
|
|
|||
15
Cargo.toml
15
Cargo.toml
|
|
@ -118,14 +118,13 @@ sandbox-driver-daytona-config = { git = "https://github.com/lithoscomputer/sandb
|
|||
sandbox-driver-testing = { git = "https://github.com/lithoscomputer/sandbox-driver", rev = "a92c0db6b6a122ca9b6df75de6615544f53c0d47" }
|
||||
# pebble: the coding agent loop fabro runs its agent stages, Ask Fabro
|
||||
# sessions, hook evaluators, and `fabro exec` on. Pinned by rev to pebble
|
||||
# `main`. The `PreviewUrls` trait objects fabro hands pebble's MCP servers
|
||||
# only cross when fabro and pebble's `mcp` feature name the same
|
||||
# sandbox-driver revision, so move the two pins together. Pebble pins the
|
||||
# same lithos-llm rev as fabro, and its lockfile policy is that every shared
|
||||
# crate resolves to the version lithos-llm locks.
|
||||
pebble-agent = { git = "https://github.com/lithoscomputer/pebble", rev = "430740f1114f859d6d173683f25b5007cf2023ca" }
|
||||
pebble-coding-agent = { git = "https://github.com/lithoscomputer/pebble", rev = "430740f1114f859d6d173683f25b5007cf2023ca", features = ["mcp", "search-providers"] }
|
||||
pebble-cli-core = { git = "https://github.com/lithoscomputer/pebble", rev = "430740f1114f859d6d173683f25b5007cf2023ca" }
|
||||
# `main`. Pebble owns the `PortRoutes` trait fabro implements over its run
|
||||
# sandbox, so the pebble and sandbox-driver pins move independently. Pebble
|
||||
# pins the same lithos-llm rev as fabro, and its lockfile policy is that
|
||||
# every shared crate resolves to the version lithos-llm locks.
|
||||
pebble-agent = { git = "https://github.com/lithoscomputer/pebble", rev = "69969420c9017ca15ac6c175c820a0cb8090866a" }
|
||||
pebble-coding-agent = { git = "https://github.com/lithoscomputer/pebble", rev = "69969420c9017ca15ac6c175c820a0cb8090866a", features = ["mcp", "search-providers"] }
|
||||
pebble-cli-core = { git = "https://github.com/lithoscomputer/pebble", rev = "69969420c9017ca15ac6c175c820a0cb8090866a" }
|
||||
sentry = { version = "0.35", default-features = false, features = ["backtrace", "contexts", "ureq", "rustls"] }
|
||||
fork = "0.2"
|
||||
exec = "0.3"
|
||||
|
|
|
|||
|
|
@ -780,6 +780,8 @@ mod tests {
|
|||
summary_token_estimate: 500,
|
||||
tracked_file_count: 3,
|
||||
reason: CompactionReason::Threshold,
|
||||
usage: TokenUsage::default(),
|
||||
cost_usd_micros: None,
|
||||
}),
|
||||
);
|
||||
assert!(ui.stage.active_stages["s1"].compaction_bar.is_none());
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ use fabro_github::GitHubCredentials;
|
|||
use fabro_github::token_source::TokenSnapshot;
|
||||
use fabro_types::SandboxProviderKind;
|
||||
use fabro_util::workspace_glob::WorkspaceGlob;
|
||||
use pebble_coding_agent::mcp::{PortRoute, PortRouteError, PortRoutes};
|
||||
use sandbox_driver::{
|
||||
Capability, DirEntry, EventContext, ExecControls, ExecResult, ExecSpec, ExecStreamingResult,
|
||||
FileKind, GitRetryPolicy, GrepMatch, GrepOptions, PreviewUrl, PreviewUrls, PtyOptions,
|
||||
PtySession, PtySize, Sandbox as DriverHandle, SandboxProvider as DriverProvider,
|
||||
SandboxSpec as DriverSpec, SandboxState, Search as _, StdioProcess, WaitOptions, WalkOptions,
|
||||
DirEntry, EventContext, ExecControls, ExecResult, ExecSpec, ExecStreamingResult, FileKind,
|
||||
GitRetryPolicy, GrepMatch, GrepOptions, PreviewUrls, PtyOptions, PtySession, PtySize,
|
||||
Sandbox as DriverHandle, SandboxProvider as DriverProvider, SandboxSpec as DriverSpec,
|
||||
SandboxState, Search as _, StdioProcess, WaitOptions, WalkOptions,
|
||||
};
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
|
@ -998,11 +999,12 @@ impl RunSandbox {
|
|||
}
|
||||
|
||||
/// The route from fabro to a port inside the sandbox, as pebble's MCP
|
||||
/// support takes it: the driver's preview URLs, when the provider has
|
||||
/// them. `None` for a provider without forwarding, which is where pebble
|
||||
/// reaches the port on the loopback address instead.
|
||||
/// support takes it: pebble's [`PortRoutes`] over the driver's preview
|
||||
/// URLs, when the provider has them. `None` for a provider without
|
||||
/// forwarding, which is where pebble reaches the port on the loopback
|
||||
/// address instead.
|
||||
#[must_use]
|
||||
pub fn port_routes(self: &Arc<Self>) -> Option<Arc<dyn PreviewUrls>> {
|
||||
pub fn port_routes(self: &Arc<Self>) -> Option<Arc<dyn PortRoutes>> {
|
||||
self.handle().ok()?.preview_urls()?;
|
||||
Some(Arc::new(SandboxPortRoutes(Arc::clone(self))))
|
||||
}
|
||||
|
|
@ -1025,34 +1027,48 @@ impl RunSandbox {
|
|||
}
|
||||
}
|
||||
|
||||
/// [`PreviewUrls`] over a run sandbox's driver handle, for pebble.
|
||||
/// Pebble's [`PortRoutes`] over a run sandbox's driver handle: the driver's
|
||||
/// preview-URL facet answers with the URL and headers that reach a port.
|
||||
struct SandboxPortRoutes(Arc<RunSandbox>);
|
||||
|
||||
impl SandboxPortRoutes {
|
||||
/// The driver's facet, present whenever [`RunSandbox::port_routes`] handed
|
||||
/// this out: the handle is set once and never cleared.
|
||||
fn facet(&self) -> Option<&dyn PreviewUrls> {
|
||||
/// this out: the handle is set once and never cleared. A missing facet is
|
||||
/// the environment routing to none of its ports.
|
||||
fn facet(&self) -> Result<&dyn PreviewUrls, PortRouteError> {
|
||||
self.0
|
||||
.handle()
|
||||
.ok()
|
||||
.and_then(|handle| handle.preview_urls())
|
||||
.ok_or(PortRouteError::Unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl PreviewUrls for SandboxPortRoutes {
|
||||
async fn preview_url(&self, port: u16) -> sandbox_driver::Result<PreviewUrl> {
|
||||
match self.facet() {
|
||||
Some(facet) => facet.preview_url(port).await,
|
||||
None => Err(sandbox_driver::Error::unsupported(Capability::PreviewUrls)),
|
||||
}
|
||||
impl PortRoutes for SandboxPortRoutes {
|
||||
async fn route(&self, port: u16) -> Result<PortRoute, PortRouteError> {
|
||||
let preview = self.facet()?.preview_url(port).await.map_err(|error| {
|
||||
PortRouteError::failed_with_source(
|
||||
format!("Failed to open a route to sandbox port {port}"),
|
||||
error,
|
||||
)
|
||||
})?;
|
||||
Ok(PortRoute {
|
||||
url: preview.url,
|
||||
headers: preview.headers,
|
||||
})
|
||||
}
|
||||
|
||||
async fn release_preview_url(&self, port: u16) -> sandbox_driver::Result<()> {
|
||||
match self.facet() {
|
||||
Some(facet) => facet.release_preview_url(port).await,
|
||||
None => Err(sandbox_driver::Error::unsupported(Capability::PreviewUrls)),
|
||||
}
|
||||
async fn release(&self, port: u16) -> Result<(), PortRouteError> {
|
||||
self.facet()?
|
||||
.release_preview_url(port)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
PortRouteError::failed_with_source(
|
||||
format!("Failed to release the route to sandbox port {port}"),
|
||||
error,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1411,4 +1427,21 @@ mod tests {
|
|||
assert_eq!(url, "http://127.0.0.1:8080");
|
||||
assert!(headers.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn port_routes_answer_pebble_with_the_access_facets_preview_url() {
|
||||
let Fixture {
|
||||
dir,
|
||||
_provider: provider,
|
||||
sandbox,
|
||||
} = fixture().await;
|
||||
let sandbox = Arc::new(sandbox);
|
||||
let routes = sandbox
|
||||
.port_routes()
|
||||
.expect("the host provider routes to its ports");
|
||||
let route = routes.route(8080).await.unwrap();
|
||||
assert_eq!(route, PortRoute::new("http://127.0.0.1:8080"));
|
||||
routes.release(8080).await.unwrap();
|
||||
drop((dir, provider));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue