mirror of
https://github.com/aaif-goose/goose.git
synced 2026-06-02 06:14:27 +02:00
refactor: convert desktop v1 and goose-server extensions to ACP+ (#9448)
This commit is contained in:
@@ -392,9 +392,6 @@ 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,
|
||||
@@ -428,8 +425,6 @@ 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,
|
||||
@@ -449,7 +444,6 @@ 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,
|
||||
@@ -491,8 +485,6 @@ 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,
|
||||
@@ -525,7 +517,6 @@ derive_utoipa!(IconTheme as IconThemeSchema);
|
||||
super::routes::session::UpdateSessionUserRecipeValuesResponse,
|
||||
super::routes::session::ForkRequest,
|
||||
super::routes::session::ForkResponse,
|
||||
super::routes::session::SessionExtensionsResponse,
|
||||
Message,
|
||||
MessageContent,
|
||||
MessageMetadata,
|
||||
@@ -644,8 +635,6 @@ 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,
|
||||
|
||||
@@ -99,18 +99,6 @@ 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,
|
||||
@@ -693,72 +681,6 @@ 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",
|
||||
@@ -1358,8 +1280,6 @@ 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)
|
||||
@@ -1408,15 +1328,11 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
agent_add_extension(
|
||||
State(state.clone()),
|
||||
Json(AddExtensionRequest {
|
||||
session_id: session.id.clone(),
|
||||
config: frontend_extension(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let agent = state.get_agent(session.id.clone()).await.unwrap();
|
||||
agent
|
||||
.add_extension(frontend_extension(), &session.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let Json(tools) = get_tools(
|
||||
State(state.clone()),
|
||||
|
||||
@@ -9,7 +9,6 @@ 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;
|
||||
@@ -22,7 +21,7 @@ use goose::providers::catalog::{
|
||||
use goose::providers::create_with_default_model;
|
||||
use goose::providers::providers as get_providers;
|
||||
use goose::{
|
||||
agents::execute_commands, agents::ExtensionConfig, config::permission::PermissionLevel,
|
||||
agents::execute_commands, config::permission::PermissionLevel,
|
||||
slash_commands::recipe_slash_command,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -31,20 +30,6 @@ 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,
|
||||
@@ -299,72 +284,6 @@ 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",
|
||||
@@ -989,9 +908,6 @@ 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(
|
||||
|
||||
@@ -9,12 +9,11 @@ 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::{EnabledExtensionsState, Session};
|
||||
use goose::session::Session;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -570,47 +569,6 @@ 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))
|
||||
@@ -637,10 +595,6 @@ 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)]
|
||||
|
||||
@@ -93,12 +93,13 @@ 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 == &req.config_key) {
|
||||
if !keys.iter().any(|k| k == &key) {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data(format!("Extension '{}' not found", req.config_key)));
|
||||
}
|
||||
crate::config::extensions::remove_extension(&req.config_key);
|
||||
crate::config::extensions::remove_extension(&key);
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
@@ -106,12 +107,13 @@ 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 == &req.config_key) {
|
||||
if !keys.iter().any(|k| k == &key) {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
.data(format!("Extension '{}' not found", req.config_key)));
|
||||
}
|
||||
crate::config::extensions::set_extension_enabled(&req.config_key, req.enabled);
|
||||
crate::config::extensions::set_extension_enabled(&key, req.enabled);
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
|
||||
@@ -47,45 +47,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/add_extension": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"super::routes::agent"
|
||||
],
|
||||
"operationId": "agent_add_extension",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AddExtensionRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension added",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized - invalid secret key"
|
||||
},
|
||||
"424": {
|
||||
"description": "Agent not initialized"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/call_tool": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -368,45 +329,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/remove_extension": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"super::routes::agent"
|
||||
],
|
||||
"operationId": "agent_remove_extension",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RemoveExtensionRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension removed",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized - invalid secret key"
|
||||
},
|
||||
"424": {
|
||||
"description": "Agent not initialized"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/restart": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -977,102 +899,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/extensions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "get_extensions",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "All extensions retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ExtensionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "add_extension",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ExtensionQuery"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension added or updated successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request"
|
||||
},
|
||||
"422": {
|
||||
"description": "Could not serialize config.yaml"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/extensions/{name}": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "remove_extension",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Extension removed successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Extension not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/permissions": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -3619,51 +3445,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/sessions/{session_id}/extensions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Session Management"
|
||||
],
|
||||
"operationId": "get_session_extensions",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
"description": "Unique identifier for the session",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Session extensions retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SessionExtensionsResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized - Invalid or missing API key"
|
||||
},
|
||||
"404": {
|
||||
"description": "Session not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/sessions/{session_id}/fork": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@@ -4141,21 +3922,6 @@
|
||||
"propertyName": "actionType"
|
||||
}
|
||||
},
|
||||
"AddExtensionRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"session_id",
|
||||
"config"
|
||||
],
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/components/schemas/ExtensionConfig"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Annotations": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5505,45 +5271,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExtensionQuery": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"config",
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/components/schemas/ExtensionConfig"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExtensionResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extensions"
|
||||
],
|
||||
"properties": {
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ExtensionEntry"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"FeaturesResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -7567,21 +7294,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"RemoveExtensionRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"session_id"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RepoVariantsResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -8236,20 +7948,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SessionExtensionsResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"extensions"
|
||||
],
|
||||
"properties": {
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ExtensionConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SessionInsights": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -1,11 +1,96 @@
|
||||
import type { ExtensionResponse, ExtensionEntry } from '../api';
|
||||
import type { ExtensionEntry, ExtensionConfig } from '../api';
|
||||
import { getAcpClient } from './acpConnection';
|
||||
|
||||
export async function getConfiguredExtensions(): Promise<ExtensionResponse> {
|
||||
export interface ConfiguredExtensionsResponse {
|
||||
extensions: ExtensionEntry[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all configured extensions via ACP (`_goose/unstable/config/extensions/list`).
|
||||
*/
|
||||
export async function getConfiguredExtensions(): Promise<ConfiguredExtensionsResponse> {
|
||||
const client = await getAcpClient();
|
||||
const response = await client.goose.configExtensionsList_unstable({});
|
||||
return {
|
||||
extensions: response.extensions as ExtensionEntry[],
|
||||
warnings: response.warnings,
|
||||
warnings: response.warnings ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add (or update) an extension in the user's global goose config via ACP
|
||||
* (`_goose/unstable/config/extensions/add`).
|
||||
*/
|
||||
export async function addConfiguredExtension(
|
||||
name: string,
|
||||
config: ExtensionConfig,
|
||||
enabled: boolean
|
||||
): Promise<void> {
|
||||
const client = await getAcpClient();
|
||||
// Server expects a JSON object matching one of the ExtensionConfig variants,
|
||||
// and injects `name` itself. We strip `name` from the body to match that shape.
|
||||
const extensionConfig = { ...config } as Record<string, unknown>;
|
||||
delete extensionConfig.name;
|
||||
|
||||
await client.goose.configExtensionsAdd_unstable({
|
||||
name,
|
||||
extensionConfig,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension from the user's global goose config via ACP
|
||||
* (`_goose/unstable/config/extensions/remove`). The server normalizes the
|
||||
* supplied `configKey` via `name_to_key`, so passing the raw extension name
|
||||
* is sufficient and matches how the previous REST route worked.
|
||||
*/
|
||||
export async function removeConfiguredExtension(name: string): Promise<void> {
|
||||
const client = await getAcpClient();
|
||||
await client.goose.configExtensionsRemove_unstable({
|
||||
configKey: name,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extension to a running session's agent via ACP
|
||||
* (`_goose/unstable/session/extensions/add`).
|
||||
*/
|
||||
export async function addSessionExtension(
|
||||
sessionId: string,
|
||||
config: ExtensionConfig
|
||||
): Promise<void> {
|
||||
const client = await getAcpClient();
|
||||
await client.goose.sessionExtensionsAdd_unstable({
|
||||
sessionId,
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an extension from a running session's agent via ACP
|
||||
* (`_goose/unstable/session/extensions/remove`).
|
||||
*/
|
||||
export async function removeSessionExtension(
|
||||
sessionId: string,
|
||||
name: string
|
||||
): Promise<void> {
|
||||
const client = await getAcpClient();
|
||||
await client.goose.sessionExtensionsRemove_unstable({
|
||||
sessionId,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of extensions associated with a given session via ACP
|
||||
* (`_goose/unstable/session/extensions/list`).
|
||||
*/
|
||||
export async function getSessionExtensions(
|
||||
sessionId: string
|
||||
): Promise<ExtensionEntry[]> {
|
||||
const client = await getAcpClient();
|
||||
const response = await client.goose.sessionExtensionsList_unstable({ sessionId });
|
||||
return response.extensions as ExtensionEntry[];
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -25,11 +25,6 @@ export type ActionRequiredData = {
|
||||
user_data: unknown;
|
||||
};
|
||||
|
||||
export type AddExtensionRequest = {
|
||||
config: ExtensionConfig;
|
||||
session_id: string;
|
||||
};
|
||||
|
||||
export type Annotations = {
|
||||
audience?: Array<Role>;
|
||||
lastModified?: string;
|
||||
@@ -493,17 +488,6 @@ export type ExtensionLoadResult = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
export type ExtensionQuery = {
|
||||
config: ExtensionConfig;
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ExtensionResponse = {
|
||||
extensions: Array<ExtensionEntry>;
|
||||
warnings?: Array<string>;
|
||||
};
|
||||
|
||||
export type FeaturesResponse = {
|
||||
/**
|
||||
* Map of feature name to enabled status
|
||||
@@ -1152,11 +1136,6 @@ export type RedactedThinkingContent = {
|
||||
data: string;
|
||||
};
|
||||
|
||||
export type RemoveExtensionRequest = {
|
||||
name: string;
|
||||
session_id: string;
|
||||
};
|
||||
|
||||
export type RepoVariantsResponse = {
|
||||
available_memory_bytes: number;
|
||||
downloaded_quants: Array<string>;
|
||||
@@ -1348,10 +1327,6 @@ export type SessionDisplayInfo = {
|
||||
workingDir: string;
|
||||
};
|
||||
|
||||
export type SessionExtensionsResponse = {
|
||||
extensions: Array<ExtensionConfig>;
|
||||
};
|
||||
|
||||
export type SessionInsights = {
|
||||
totalSessions: number;
|
||||
totalTokens: number;
|
||||
@@ -1781,37 +1756,6 @@ export type ConfirmToolActionResponses = {
|
||||
200: unknown;
|
||||
};
|
||||
|
||||
export type AgentAddExtensionData = {
|
||||
body: AddExtensionRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/agent/add_extension';
|
||||
};
|
||||
|
||||
export type AgentAddExtensionErrors = {
|
||||
/**
|
||||
* Unauthorized - invalid secret key
|
||||
*/
|
||||
401: unknown;
|
||||
/**
|
||||
* Agent not initialized
|
||||
*/
|
||||
424: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type AgentAddExtensionResponses = {
|
||||
/**
|
||||
* Extension added
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type AgentAddExtensionResponse = AgentAddExtensionResponses[keyof AgentAddExtensionResponses];
|
||||
|
||||
export type CallToolData = {
|
||||
body: CallToolRequest;
|
||||
path?: never;
|
||||
@@ -1982,37 +1926,6 @@ export type ReadResourceResponses = {
|
||||
|
||||
export type ReadResourceResponse2 = ReadResourceResponses[keyof ReadResourceResponses];
|
||||
|
||||
export type AgentRemoveExtensionData = {
|
||||
body: RemoveExtensionRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/agent/remove_extension';
|
||||
};
|
||||
|
||||
export type AgentRemoveExtensionErrors = {
|
||||
/**
|
||||
* Unauthorized - invalid secret key
|
||||
*/
|
||||
401: unknown;
|
||||
/**
|
||||
* Agent not initialized
|
||||
*/
|
||||
424: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type AgentRemoveExtensionResponses = {
|
||||
/**
|
||||
* Extension removed
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type AgentRemoveExtensionResponse = AgentRemoveExtensionResponses[keyof AgentRemoveExtensionResponses];
|
||||
|
||||
export type RestartAgentData = {
|
||||
body: RestartAgentRequest;
|
||||
path?: never;
|
||||
@@ -2448,89 +2361,6 @@ export type UpdateCustomProviderResponses = {
|
||||
|
||||
export type UpdateCustomProviderResponse = UpdateCustomProviderResponses[keyof UpdateCustomProviderResponses];
|
||||
|
||||
export type GetExtensionsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/extensions';
|
||||
};
|
||||
|
||||
export type GetExtensionsErrors = {
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type GetExtensionsResponses = {
|
||||
/**
|
||||
* All extensions retrieved successfully
|
||||
*/
|
||||
200: ExtensionResponse;
|
||||
};
|
||||
|
||||
export type GetExtensionsResponse = GetExtensionsResponses[keyof GetExtensionsResponses];
|
||||
|
||||
export type AddExtensionData = {
|
||||
body: ExtensionQuery;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/extensions';
|
||||
};
|
||||
|
||||
export type AddExtensionErrors = {
|
||||
/**
|
||||
* Invalid request
|
||||
*/
|
||||
400: unknown;
|
||||
/**
|
||||
* Could not serialize config.yaml
|
||||
*/
|
||||
422: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type AddExtensionResponses = {
|
||||
/**
|
||||
* Extension added or updated successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type AddExtensionResponse = AddExtensionResponses[keyof AddExtensionResponses];
|
||||
|
||||
export type RemoveExtensionData = {
|
||||
body?: never;
|
||||
path: {
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/extensions/{name}';
|
||||
};
|
||||
|
||||
export type RemoveExtensionErrors = {
|
||||
/**
|
||||
* Extension not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type RemoveExtensionResponses = {
|
||||
/**
|
||||
* Extension removed successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses];
|
||||
|
||||
export type UpsertPermissionsData = {
|
||||
body: UpsertPermissionsQuery;
|
||||
path?: never;
|
||||
@@ -4496,42 +4326,6 @@ export type ExportSessionResponses = {
|
||||
|
||||
export type ExportSessionResponse = ExportSessionResponses[keyof ExportSessionResponses];
|
||||
|
||||
export type GetSessionExtensionsData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Unique identifier for the session
|
||||
*/
|
||||
session_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/sessions/{session_id}/extensions';
|
||||
};
|
||||
|
||||
export type GetSessionExtensionsErrors = {
|
||||
/**
|
||||
* Unauthorized - Invalid or missing API key
|
||||
*/
|
||||
401: unknown;
|
||||
/**
|
||||
* Session not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type GetSessionExtensionsResponses = {
|
||||
/**
|
||||
* Session extensions retrieved successfully
|
||||
*/
|
||||
200: SessionExtensionsResponse;
|
||||
};
|
||||
|
||||
export type GetSessionExtensionsResponse = GetSessionExtensionsResponses[keyof GetSessionExtensionsResponses];
|
||||
|
||||
export type ForkSessionData = {
|
||||
body: ForkRequest;
|
||||
path: {
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { readAllConfig, readConfig, removeConfig, upsertConfig, providers } from '../api';
|
||||
import {
|
||||
readAllConfig,
|
||||
readConfig,
|
||||
removeConfig,
|
||||
upsertConfig,
|
||||
addExtension as apiAddExtension,
|
||||
removeExtension as apiRemoveExtension,
|
||||
providers,
|
||||
} from '../api';
|
||||
import { getConfiguredExtensions } from '../acp/extensions';
|
||||
getConfiguredExtensions,
|
||||
addConfiguredExtension,
|
||||
removeConfiguredExtension,
|
||||
} from '../acp/extensions';
|
||||
import { pruneDeprecatedBundledExtensions, syncBundledExtensions } from './settings/extensions';
|
||||
import type {
|
||||
ConfigResponse,
|
||||
UpsertConfigQuery,
|
||||
ConfigKeyQuery,
|
||||
ProviderDetails,
|
||||
ExtensionQuery,
|
||||
ExtensionConfig,
|
||||
} from '../api';
|
||||
|
||||
@@ -113,10 +108,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
|
||||
const addExtension = useCallback(
|
||||
async (name: string, config: ExtensionConfig, enabled: boolean) => {
|
||||
const query: ExtensionQuery = { name, config, enabled };
|
||||
await apiAddExtension({
|
||||
body: query,
|
||||
});
|
||||
await addConfiguredExtension(name, config, enabled);
|
||||
await reloadConfig();
|
||||
// Refresh extensions list after successful addition
|
||||
await refreshExtensions();
|
||||
@@ -126,7 +118,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
|
||||
const removeExtension = useCallback(
|
||||
async (name: string) => {
|
||||
await apiRemoveExtension({ path: { name: name } });
|
||||
await removeConfiguredExtension(name);
|
||||
await reloadConfig();
|
||||
// Refresh extensions list after successful removal
|
||||
await refreshExtensions();
|
||||
@@ -206,11 +198,10 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
config: ExtensionConfig,
|
||||
enabled: boolean
|
||||
) => {
|
||||
const query: ExtensionQuery = { name, config, enabled };
|
||||
await apiAddExtension({ body: query });
|
||||
await addConfiguredExtension(name, config, enabled);
|
||||
};
|
||||
const removeExtensionForSync = async (name: string) => {
|
||||
await apiRemoveExtension({ path: { name } });
|
||||
await removeConfiguredExtension(name);
|
||||
};
|
||||
extensions = await pruneDeprecatedBundledExtensions(extensions, removeExtensionForSync);
|
||||
await syncBundledExtensions(extensions, addExtensionForSync);
|
||||
|
||||
@@ -8,7 +8,8 @@ import { FixedExtensionEntry, useConfig } from '../ConfigContext';
|
||||
import { toastService } from '../../toasts';
|
||||
import { formatExtensionName } from '../settings/extensions/subcomponents/ExtensionList';
|
||||
import { nameToKey } from '../settings/extensions/utils';
|
||||
import { ExtensionConfig, getSessionExtensions } from '../../api';
|
||||
import { ExtensionConfig } from '../../api';
|
||||
import { getSessionExtensions } from '../../acp/extensions';
|
||||
import { addToAgent, removeFromAgent } from '../settings/extensions/agent-api';
|
||||
import {
|
||||
setExtensionOverride,
|
||||
@@ -119,14 +120,9 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await getSessionExtensions({
|
||||
path: { session_id: sessionId },
|
||||
});
|
||||
|
||||
if (response.data?.extensions) {
|
||||
setSessionExtensions(response.data.extensions);
|
||||
setIsSessionExtensionsLoaded(true);
|
||||
}
|
||||
const extensions = await getSessionExtensions(sessionId);
|
||||
setSessionExtensions(extensions);
|
||||
setIsSessionExtensionsLoaded(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch session extensions:', error);
|
||||
setIsSessionExtensionsLoaded(true);
|
||||
@@ -197,13 +193,8 @@ export const BottomMenuExtensionSelection = ({ sessionId }: BottomMenuExtensionS
|
||||
}
|
||||
|
||||
sortTimeoutRef.current = setTimeout(async () => {
|
||||
const response = await getSessionExtensions({
|
||||
path: { session_id: sessionId },
|
||||
});
|
||||
|
||||
if (response.data?.extensions) {
|
||||
setSessionExtensions(response.data.extensions);
|
||||
}
|
||||
const extensions = await getSessionExtensions(sessionId);
|
||||
setSessionExtensions(extensions);
|
||||
setPendingSort(false);
|
||||
setIsTransitioning(false);
|
||||
setTogglingExtension(null);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { toastService } from '../../../toasts';
|
||||
import { agentAddExtension, ExtensionConfig, agentRemoveExtension } from '../../../api';
|
||||
import { ExtensionConfig } from '../../../api';
|
||||
import { addSessionExtension, removeSessionExtension } from '../../../acp/extensions';
|
||||
import { errorMessage } from '../../../utils/conversionUtils';
|
||||
import {
|
||||
createExtensionRecoverHints,
|
||||
@@ -20,10 +21,7 @@ export async function addToAgent(
|
||||
: 0;
|
||||
|
||||
try {
|
||||
await agentAddExtension({
|
||||
body: { session_id: sessionId, config: extensionConfig },
|
||||
throwOnError: true,
|
||||
});
|
||||
await addSessionExtension(sessionId, extensionConfig);
|
||||
if (showToast) {
|
||||
toastService.dismiss(toastId);
|
||||
toastService.success({
|
||||
@@ -61,10 +59,7 @@ export async function removeFromAgent(
|
||||
: 0;
|
||||
|
||||
try {
|
||||
await agentRemoveExtension({
|
||||
body: { session_id: sessionId, name: extensionName },
|
||||
throwOnError: true,
|
||||
});
|
||||
await removeSessionExtension(sessionId, extensionName);
|
||||
if (showToast) {
|
||||
toastService.dismiss(toastId);
|
||||
toastService.success({
|
||||
|
||||
Reference in New Issue
Block a user