mount acp in the goosed server to migrate using acp protocols iteratively (#9097)

This commit is contained in:
Lifei Zhou
2026-05-18 09:43:30 +10:00
committed by GitHub
parent d04c882388
commit 1fafd74413
17 changed files with 1430 additions and 61 deletions
+33 -5
View File
@@ -6,6 +6,12 @@ use axum::{
};
use subtle::ConstantTimeEq;
fn token_matches(candidate: Option<&str>, expected: &str) -> bool {
candidate
.map(|key| bool::from(key.as_bytes().ct_eq(expected.as_bytes())))
.unwrap_or(false)
}
pub async fn check_token(
State(state): State<String>,
request: Request,
@@ -24,10 +30,32 @@ pub async fn check_token(
.get("X-Secret-Key")
.and_then(|value| value.to_str().ok());
match secret_key {
Some(key) if bool::from(key.as_bytes().ct_eq(state.as_bytes())) => {
Ok(next.run(request).await)
}
_ => Err(StatusCode::UNAUTHORIZED),
if token_matches(secret_key, &state) {
Ok(next.run(request).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
pub async fn check_acp_token(
State(state): State<String>,
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
let header_token = request
.headers()
.get("X-Secret-Key")
.and_then(|value| value.to_str().ok());
let query_token = request.uri().query().and_then(|query| {
url::form_urlencoded::parse(query.as_bytes())
.find(|(key, _)| key == "token")
.map(|(_, value)| value.into_owned())
});
if token_matches(header_token, &state) || token_matches(query_token.as_deref(), &state) {
Ok(next.run(request).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
+28 -7
View File
@@ -3,9 +3,14 @@ use crate::state;
use anyhow::Result;
use axum::middleware;
use axum_server::Handle;
use goose_server::auth::check_token;
use goose::acp::server_factory::{AcpServer, AcpServerFactoryConfig};
use goose::acp::transport::create_acp_router;
use goose::agents::GoosePlatform;
use goose::config::paths::Paths;
use goose_server::auth::{check_acp_token, check_token};
#[cfg(any(feature = "rustls-tls", feature = "native-tls"))]
use goose_server::tls::setup_tls;
use std::sync::Arc;
use tower_http::cors::{Any, CorsLayer};
use tracing::info;
@@ -64,12 +69,28 @@ pub async fn run() -> Result<()> {
.allow_methods(Any)
.allow_headers(Any);
let app = crate::routes::configure(app_state.clone(), secret_key.clone())
.layer(middleware::from_fn_with_state(
secret_key.clone(),
check_token,
))
.layer(cors);
// TODO(acp-migration): When ui/desktop launches `goose serve` directly,
// move any goosed-only ACP setup into the goose serve path before deleting
// this bridge. In particular, verify everything ACP currently gets from
// goosed startup/AppState initialization, including builtin extension
// registration and the desktop platform identity.
let acp_server = Arc::new(AcpServer::new(AcpServerFactoryConfig {
builtins: vec!["developer".to_string()],
data_dir: Paths::data_dir(),
config_dir: Paths::config_dir(),
goose_platform: GoosePlatform::GooseDesktop,
additional_source_roots: Vec::new(),
}));
let rest_router = crate::routes::configure(app_state.clone(), secret_key.clone()).layer(
middleware::from_fn_with_state(secret_key.clone(), check_token),
);
let acp_router = create_acp_router(acp_server).layer(middleware::from_fn_with_state(
secret_key.clone(),
check_acp_token,
));
let app = rest_router.merge(acp_router).layer(cors);
let addr = settings.socket_addr();
+6 -1
View File
@@ -33,7 +33,12 @@ impl GooseAcpAgent {
pub(super) async fn on_get_extensions(
&self,
) -> Result<GetExtensionsResponse, agent_client_protocol::Error> {
let extensions = crate::config::extensions::get_all_extensions();
let extensions = crate::config::extensions::get_all_extensions()
.into_iter()
.filter(|ext| {
!crate::agents::extension_manager::is_hidden_extension(&ext.config.name())
})
.collect::<Vec<_>>();
let warnings = crate::config::extensions::get_warnings();
let extensions_json = extensions
.into_iter()
+19 -9
View File
@@ -94,10 +94,8 @@ async fn health() -> &'static str {
"ok"
}
pub fn create_router(server: Arc<AcpServer>, secret_key: String) -> Router {
let registry = Arc::new(connection::ConnectionRegistry::new(server));
let cors = CorsLayer::new()
fn acp_cors_layer() -> CorsLayer {
CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST, Method::DELETE, Method::OPTIONS])
.allow_headers([
@@ -113,14 +111,26 @@ pub fn create_router(server: Arc<AcpServer>, secret_key: String) -> Router {
.expose_headers([
HeaderName::from_static("acp-connection-id"),
HeaderName::from_static("acp-session-id"),
]);
])
}
fn create_acp_routes(server: Arc<AcpServer>) -> Router {
let registry = Arc::new(connection::ConnectionRegistry::new(server));
Router::new()
.route("/health", get(health))
.route("/status", get(health))
.route("/acp", post(http::handle_post).with_state(registry.clone()))
.route("/acp", get(handle_get).with_state(registry.clone()))
.route("/acp", delete(http::handle_delete).with_state(registry))
.merge(super::mcp_app_proxy::routes(secret_key))
.layer(cors)
}
pub fn create_acp_router(server: Arc<AcpServer>) -> Router {
create_acp_routes(server).layer(acp_cors_layer())
}
pub fn create_router(server: Arc<AcpServer>, secret_key: String) -> Router {
create_acp_routes(server)
.route("/health", get(health))
.route("/status", get(health))
.merge(super::mcp_app_proxy::routes(secret_key))
.layer(acp_cors_layer())
}