Revert "refactor: convert desktop v1 and goose-server extensions to ACP+ (#9448)" (#9564)

This commit is contained in:
Alex Hancock
2026-06-02 09:44:18 -04:00
committed by GitHub
parent 586bb15d40
commit 942a4564e3
13 changed files with 826 additions and 124 deletions
+11
View File
@@ -392,6 +392,9 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::config_management::upsert_config,
super::routes::config_management::remove_config,
super::routes::config_management::read_config,
super::routes::config_management::add_extension,
super::routes::config_management::remove_extension,
super::routes::config_management::get_extensions,
super::routes::config_management::read_all_config,
super::routes::config_management::providers,
super::routes::config_management::get_provider_models,
@@ -425,6 +428,8 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::agent::export_app,
super::routes::agent::import_app,
super::routes::agent::update_from_session,
super::routes::agent::agent_add_extension,
super::routes::agent::agent_remove_extension,
super::routes::agent::update_agent_provider,
super::routes::agent::update_session,
super::routes::action_required::confirm_tool_action,
@@ -444,6 +449,7 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::session::import_session_nostr,
super::routes::session::update_session_user_recipe_values,
super::routes::session::fork_session,
super::routes::session::get_session_extensions,
super::routes::schedule::create_schedule,
super::routes::schedule::list_schedules,
super::routes::schedule::delete_schedule,
@@ -485,6 +491,8 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::config_management::SlashCommandsResponse,
super::routes::config_management::SlashCommand,
super::routes::config_management::CommandType,
super::routes::config_management::ExtensionResponse,
super::routes::config_management::ExtensionQuery,
super::routes::config_management::ToolPermission,
super::routes::config_management::UpsertPermissionsQuery,
super::routes::config_management::UpdateCustomProviderRequest,
@@ -517,6 +525,7 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::session::UpdateSessionUserRecipeValuesResponse,
super::routes::session::ForkRequest,
super::routes::session::ForkResponse,
super::routes::session::SessionExtensionsResponse,
Message,
MessageContent,
MessageMetadata,
@@ -635,6 +644,8 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::agent::RestartAgentRequest,
super::routes::agent::UpdateWorkingDirRequest,
super::routes::agent::UpdateFromSessionRequest,
super::routes::agent::AddExtensionRequest,
super::routes::agent::RemoveExtensionRequest,
super::routes::agent::ResumeAgentResponse,
super::routes::agent::RestartAgentResponse,
goose::agents::ExtensionLoadResult,
+89 -5
View File
@@ -99,6 +99,18 @@ pub struct ResumeAgentRequest {
load_model_and_extensions: bool,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct AddExtensionRequest {
session_id: String,
config: ExtensionConfig,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct RemoveExtensionRequest {
name: String,
session_id: String,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct SetContainerRequest {
session_id: String,
@@ -689,6 +701,72 @@ async fn update_session(
Ok(())
}
#[utoipa::path(
post,
path = "/agent/add_extension",
request_body = AddExtensionRequest,
responses(
(status = 200, description = "Extension added", body = String),
(status = 401, description = "Unauthorized - invalid secret key"),
(status = 424, description = "Agent not initialized"),
(status = 500, description = "Internal server error")
)
)]
async fn agent_add_extension(
State(state): State<Arc<AppState>>,
Json(request): Json<AddExtensionRequest>,
) -> Result<StatusCode, ErrorResponse> {
#[cfg(feature = "telemetry")]
let extension_name = request.config.name();
let agent = state.get_agent(request.session_id.clone()).await?;
agent
.add_extension(request.config, &request.session_id)
.await
.map_err(|e| {
#[cfg(feature = "telemetry")]
goose::posthog::emit_error(
"extension_add_failed",
&format!("{}: {}", extension_name, e),
);
ErrorResponse::internal(format!("Failed to add extension: {}", e))
})?;
Ok(StatusCode::OK)
}
#[utoipa::path(
post,
path = "/agent/remove_extension",
request_body = RemoveExtensionRequest,
responses(
(status = 200, description = "Extension removed", body = String),
(status = 401, description = "Unauthorized - invalid secret key"),
(status = 424, description = "Agent not initialized"),
(status = 500, description = "Internal server error")
)
)]
async fn agent_remove_extension(
State(state): State<Arc<AppState>>,
Json(request): Json<RemoveExtensionRequest>,
) -> Result<StatusCode, ErrorResponse> {
let agent = state.get_agent(request.session_id.clone()).await?;
agent
.remove_extension(&request.name, &request.session_id)
.await
.map_err(|e| {
error!("Failed to remove extension: {}", e);
ErrorResponse {
message: format!("Failed to remove extension: {}", e),
status: StatusCode::INTERNAL_SERVER_ERROR,
}
})?;
Ok(StatusCode::OK)
}
#[utoipa::path(
post,
path = "/agent/set_container",
@@ -1296,6 +1374,8 @@ pub fn routes(state: Arc<AppState>) -> Router {
.route("/agent/update_provider", post(update_agent_provider))
.route("/agent/update_session", post(update_session))
.route("/agent/update_from_session", post(update_from_session))
.route("/agent/add_extension", post(agent_add_extension))
.route("/agent/remove_extension", post(agent_remove_extension))
.route("/agent/set_container", post(set_container))
.route("/agent/stop", post(stop_agent))
.with_state(state)
@@ -1344,11 +1424,15 @@ mod tests {
.await
.unwrap();
let agent = state.get_agent(session.id.clone()).await.unwrap();
agent
.add_extension(frontend_extension(), &session.id)
.await
.unwrap();
agent_add_extension(
State(state.clone()),
Json(AddExtensionRequest {
session_id: session.id.clone(),
config: frontend_extension(),
}),
)
.await
.unwrap();
let Json(tools) = get_tools(
State(state.clone()),
@@ -9,6 +9,7 @@ use axum::{
};
use goose::config::declarative_providers::LoadedProvider;
use goose::config::paths::Paths;
use goose::config::ExtensionEntry;
use goose::config::{Config, ConfigError};
use goose::custom_requests::SourceType;
use goose::model::ModelConfig;
@@ -21,7 +22,7 @@ use goose::providers::catalog::{
use goose::providers::create_with_default_model;
use goose::providers::providers as get_providers;
use goose::{
agents::execute_commands, config::permission::PermissionLevel,
agents::execute_commands, agents::ExtensionConfig, config::permission::PermissionLevel,
slash_commands::recipe_slash_command,
};
use serde::{Deserialize, Serialize};
@@ -30,6 +31,20 @@ use serde_yaml;
use std::{collections::HashMap, sync::Arc};
use utoipa::ToSchema;
#[derive(Serialize, ToSchema)]
pub struct ExtensionResponse {
pub extensions: Vec<ExtensionEntry>,
#[serde(default)]
pub warnings: Vec<String>,
}
#[derive(Deserialize, ToSchema)]
pub struct ExtensionQuery {
pub name: String,
pub config: ExtensionConfig,
pub enabled: bool,
}
#[derive(Deserialize, ToSchema)]
pub struct UpsertConfigQuery {
pub key: String,
@@ -284,6 +299,72 @@ pub async fn read_config(
Ok(Json(response_value))
}
#[utoipa::path(
get,
path = "/config/extensions",
responses(
(status = 200, description = "All extensions retrieved successfully", body = ExtensionResponse),
(status = 500, description = "Internal server error")
)
)]
pub async fn get_extensions() -> Result<Json<ExtensionResponse>, ErrorResponse> {
let extensions = goose::config::get_all_extensions()
.into_iter()
.filter(|ext| !goose::agents::extension_manager::is_hidden_extension(&ext.config.name()))
.collect();
let warnings = goose::config::get_warnings();
Ok(Json(ExtensionResponse {
extensions,
warnings,
}))
}
#[utoipa::path(
post,
path = "/config/extensions",
request_body = ExtensionQuery,
responses(
(status = 200, description = "Extension added or updated successfully", body = String),
(status = 400, description = "Invalid request"),
(status = 422, description = "Could not serialize config.yaml"),
(status = 500, description = "Internal server error")
)
)]
pub async fn add_extension(
Json(extension_query): Json<ExtensionQuery>,
) -> Result<Json<String>, ErrorResponse> {
let extensions = goose::config::get_all_extensions();
let key = goose::config::extensions::name_to_key(&extension_query.name);
let is_update = extensions.iter().any(|e| e.config.key() == key);
goose::config::set_extension(ExtensionEntry {
enabled: extension_query.enabled,
config: extension_query.config,
});
if is_update {
Ok(Json(format!("Updated extension {}", extension_query.name)))
} else {
Ok(Json(format!("Added extension {}", extension_query.name)))
}
}
#[utoipa::path(
delete,
path = "/config/extensions/{name}",
responses(
(status = 200, description = "Extension removed successfully", body = String),
(status = 404, description = "Extension not found"),
(status = 500, description = "Internal server error")
)
)]
pub async fn remove_extension(Path(name): Path<String>) -> Result<Json<String>, ErrorResponse> {
let key = goose::config::extensions::name_to_key(&name);
goose::config::remove_extension(&key);
Ok(Json(format!("Removed extension {}", name)))
}
#[utoipa::path(
get,
path = "/config",
@@ -908,6 +989,9 @@ pub fn routes(state: Arc<AppState>) -> Router {
.route("/config/upsert", post(upsert_config))
.route("/config/remove", post(remove_config))
.route("/config/read", post(read_config))
.route("/config/extensions", get(get_extensions))
.route("/config/extensions", post(add_extension))
.route("/config/extensions/{name}", delete(remove_extension))
.route("/config/providers", get(providers))
.route("/config/providers/{name}/models", get(get_provider_models))
.route(
+47 -1
View File
@@ -9,11 +9,12 @@ use axum::{
routing::{delete, get, put},
Json, Router,
};
use goose::agents::ExtensionConfig;
use goose::recipe::Recipe;
#[cfg(feature = "nostr")]
use goose::session::nostr_share;
use goose::session::session_manager::{SessionInsights, SessionType};
use goose::session::Session;
use goose::session::{EnabledExtensionsState, Session};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
@@ -569,6 +570,47 @@ async fn fork_session(
}))
}
#[derive(Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SessionExtensionsResponse {
extensions: Vec<ExtensionConfig>,
}
#[utoipa::path(
get,
path = "/sessions/{session_id}/extensions",
params(
("session_id" = String, Path, description = "Unique identifier for the session")
),
responses(
(status = 200, description = "Session extensions retrieved successfully", body = SessionExtensionsResponse),
(status = 401, description = "Unauthorized - Invalid or missing API key"),
(status = 404, description = "Session not found"),
(status = 500, description = "Internal server error")
),
security(
("api_key" = [])
),
tag = "Session Management"
)]
async fn get_session_extensions(
State(state): State<Arc<AppState>>,
Path(session_id): Path<String>,
) -> Result<Json<SessionExtensionsResponse>, StatusCode> {
let session = state
.session_manager()
.get_session(&session_id, false)
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
let extensions = EnabledExtensionsState::extensions_or_default(
Some(&session.extension_data),
goose::config::Config::global(),
);
Ok(Json(SessionExtensionsResponse { extensions }))
}
pub fn routes(state: Arc<AppState>) -> Router {
Router::new()
.route("/sessions", get(list_sessions))
@@ -595,6 +637,10 @@ pub fn routes(state: Arc<AppState>) -> Router {
put(update_session_user_recipe_values),
)
.route("/sessions/{session_id}/fork", post(fork_session))
.route(
"/sessions/{session_id}/extensions",
get(get_session_extensions),
)
.with_state(state)
}
#[derive(Deserialize, ToSchema)]
+4 -6
View File
@@ -93,13 +93,12 @@ impl GooseAcpAgent {
&self,
req: RemoveConfigExtensionRequest,
) -> Result<EmptyResponse, agent_client_protocol::Error> {
let key = crate::config::extensions::name_to_key(&req.config_key);
let keys = crate::config::extensions::get_all_extension_names();
if !keys.iter().any(|k| k == &key) {
if !keys.iter().any(|k| k == &req.config_key) {
return Err(agent_client_protocol::Error::invalid_params()
.data(format!("Extension '{}' not found", req.config_key)));
}
crate::config::extensions::remove_extension(&key);
crate::config::extensions::remove_extension(&req.config_key);
Ok(EmptyResponse {})
}
@@ -107,13 +106,12 @@ impl GooseAcpAgent {
&self,
req: ToggleConfigExtensionRequest,
) -> Result<EmptyResponse, agent_client_protocol::Error> {
let key = crate::config::extensions::name_to_key(&req.config_key);
let keys = crate::config::extensions::get_all_extension_names();
if !keys.iter().any(|k| k == &key) {
if !keys.iter().any(|k| k == &req.config_key) {
return Err(agent_client_protocol::Error::invalid_params()
.data(format!("Extension '{}' not found", req.config_key)));
}
crate::config::extensions::set_extension_enabled(&key, req.enabled);
crate::config::extensions::set_extension_enabled(&req.config_key, req.enabled);
Ok(EmptyResponse {})
}