diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index d97d58a12213..bcbed312011f 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -15,9 +15,7 @@ use goose::providers::base::ProviderMetadata; super::routes::config_management::read_config, super::routes::config_management::add_extension, super::routes::config_management::remove_extension, - super::routes::config_management::toggle_extension, super::routes::config_management::get_extensions, - super::routes::config_management::update_extension, super::routes::config_management::read_all_config, super::routes::config_management::providers ), diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 21b1d9db8b33..a0caa8b3dcff 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -1,6 +1,5 @@ use crate::routes::utils::check_provider_configured; use crate::state::AppState; -use axum::routing::put; use axum::{ extract::State, routing::{delete, get, post}, @@ -188,7 +187,7 @@ pub async fn get_extensions( path = "/config/extensions", request_body = ExtensionQuery, responses( - (status = 200, description = "Extension added successfully", body = String), + (status = 200, description = "Extension added or updated successfully", body = String), (status = 400, description = "Invalid request"), (status = 500, description = "Internal server error") ) @@ -200,12 +199,24 @@ pub async fn add_extension( ) -> Result, StatusCode> { verify_secret_key(&headers, &state)?; + // Get existing extensions to check if this is an update + let extensions = ExtensionManager::get_all().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let key = name_to_key(&extension_query.name); + + let is_update = extensions.iter().any(|e| e.config.key() == key); + // Use ExtensionManager to set the extension match ExtensionManager::set(ExtensionEntry { enabled: extension_query.enabled, config: extension_query.config, }) { - Ok(_) => Ok(Json(format!("Added extension {}", extension_query.name))), + Ok(_) => { + if is_update { + Ok(Json(format!("Updated extension {}", extension_query.name))) + } else { + Ok(Json(format!("Added extension {}", extension_query.name))) + } + } Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), } } @@ -234,89 +245,6 @@ pub async fn remove_extension( } } -#[utoipa::path( - put, - path = "/config/extensions/{name}", - request_body = ExtensionQuery, - responses( - (status = 200, description = "Extension updated successfully", body = String), - (status = 404, description = "Extension not found"), - (status = 500, description = "Internal server error") - ) -)] -pub async fn update_extension( - State(state): State, - headers: HeaderMap, - axum::extract::Path(name): axum::extract::Path, - Json(extension_query): Json, -) -> Result, StatusCode> { - verify_secret_key(&headers, &state)?; - - let key = name_to_key(&name); - - // Check if extension exists - let extensions = ExtensionManager::get_all().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - if !extensions.iter().any(|entry| entry.config.key() == key) { - return Err(StatusCode::NOT_FOUND); - } - - // Use ExtensionManager to update the extension - match ExtensionManager::set(ExtensionEntry { - enabled: extension_query.enabled, - config: extension_query.config, - }) { - Ok(_) => Ok(Json(format!("Updated extension {}", extension_query.name))), - Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), - } -} - -#[utoipa::path( - post, - path = "/extensions/{name}/toggle", - responses( - (status = 200, description = "Extension toggled successfully", body = String), - (status = 404, description = "Extension not found"), - (status = 500, description = "Internal server error") - ) -)] -pub async fn toggle_extension( - State(state): State, - headers: HeaderMap, - axum::extract::Path(name): axum::extract::Path, -) -> Result, StatusCode> { - verify_secret_key(&headers, &state)?; - - let key = name_to_key(&name); - - // Get the extension - let extensions = ExtensionManager::get_all().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - let extension = extensions - .iter() - .find(|e| e.config.key() == key) - .ok_or(StatusCode::NOT_FOUND)?; - - // Create a new entry with toggled enabled state - let updated_entry = ExtensionEntry { - enabled: !extension.enabled, - config: extension.config.clone(), - }; - - // Update using ExtensionManager - match ExtensionManager::set(updated_entry) { - Ok(_) => { - let status = if !extension.enabled { - "enabled" - } else { - "disabled" - }; - Ok(Json(format!("Extension {} {}", name, status))) - } - Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), - } -} - #[utoipa::path( get, path = "/config", @@ -382,9 +310,7 @@ pub fn routes(state: AppState) -> Router { .route("/config/read", post(read_config)) .route("/config/extensions", get(get_extensions)) .route("/config/extensions", post(add_extension)) - .route("/config/extensions/:name", put(update_extension)) .route("/config/extensions/:name", delete(remove_extension)) - .route("/extensions/:name/toggle", post(toggle_extension)) .route("/config/providers", get(providers)) .with_state(state) } diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 0b9292491108..011a55f20eac 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -72,7 +72,7 @@ }, "responses": { "200": { - "description": "Extension added successfully", + "description": "Extension added or updated successfully", "content": { "text/plain": { "schema": { @@ -91,50 +91,6 @@ } }, "/config/extensions/{name}": { - "put": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "update_extension", - "parameters": [ - { - "name": "name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ExtensionQuery" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Extension updated successfully", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Extension not found" - }, - "500": { - "description": "Internal server error" - } - } - }, "delete": { "tags": [ "super::routes::config_management" @@ -292,42 +248,6 @@ } } } - }, - "/extensions/{name}/toggle": { - "post": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "toggle_extension", - "parameters": [ - { - "name": "name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Extension toggled successfully", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "404": { - "description": "Extension not found" - }, - "500": { - "description": "Internal server error" - } - } - } } }, "components": { diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index c8057f9633c0..bddb20a816e3 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; -import type { ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, UpdateExtensionData, UpdateExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse, ToggleExtensionData, ToggleExtensionResponse } from './types.gen'; +import type { ReadAllConfigData, ReadAllConfigResponse, GetExtensionsData, GetExtensionsResponse, AddExtensionData, AddExtensionResponse, RemoveExtensionData, RemoveExtensionResponse, ProvidersData, ProvidersResponse2, ReadConfigData, RemoveConfigData, RemoveConfigResponse, UpsertConfigData, UpsertConfigResponse } from './types.gen'; import { client as _heyApiClient } from './client.gen'; export type Options = ClientOptions & { @@ -50,17 +50,6 @@ export const removeExtension = (options: O }); }; -export const updateExtension = (options: Options) => { - return (options.client ?? _heyApiClient).put({ - url: '/config/extensions/{name}', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options?.headers - } - }); -}; - export const providers = (options?: Options) => { return (options?.client ?? _heyApiClient).get({ url: '/config/providers', @@ -99,11 +88,4 @@ export const upsertConfig = (options: Opti ...options?.headers } }); -}; - -export const toggleExtension = (options: Options) => { - return (options.client ?? _heyApiClient).post({ - url: '/extensions/{name}/toggle', - ...options - }); }; \ No newline at end of file diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 1889cfe1c2e3..dcd2a69296df 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -183,7 +183,7 @@ export type AddExtensionErrors = { export type AddExtensionResponses = { /** - * Extension added successfully + * Extension added or updated successfully */ 200: string; }; @@ -219,35 +219,6 @@ export type RemoveExtensionResponses = { export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses]; -export type UpdateExtensionData = { - body: ExtensionQuery; - path: { - name: string; - }; - query?: never; - url: '/config/extensions/{name}'; -}; - -export type UpdateExtensionErrors = { - /** - * Extension not found - */ - 404: unknown; - /** - * Internal server error - */ - 500: unknown; -}; - -export type UpdateExtensionResponses = { - /** - * Extension updated successfully - */ - 200: string; -}; - -export type UpdateExtensionResponse = UpdateExtensionResponses[keyof UpdateExtensionResponses]; - export type ProvidersData = { body?: never; path?: never; @@ -335,35 +306,6 @@ export type UpsertConfigResponses = { export type UpsertConfigResponse = UpsertConfigResponses[keyof UpsertConfigResponses]; -export type ToggleExtensionData = { - body?: never; - path: { - name: string; - }; - query?: never; - url: '/extensions/{name}/toggle'; -}; - -export type ToggleExtensionErrors = { - /** - * Extension not found - */ - 404: unknown; - /** - * Internal server error - */ - 500: unknown; -}; - -export type ToggleExtensionResponses = { - /** - * Extension toggled successfully - */ - 200: string; -}; - -export type ToggleExtensionResponse = ToggleExtensionResponses[keyof ToggleExtensionResponses]; - export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; \ No newline at end of file diff --git a/ui/desktop/src/components/ConfigContext.tsx b/ui/desktop/src/components/ConfigContext.tsx index cb425e66c8cb..dda126969cce 100644 --- a/ui/desktop/src/components/ConfigContext.tsx +++ b/ui/desktop/src/components/ConfigContext.tsx @@ -5,10 +5,8 @@ import { removeConfig, upsertConfig, getExtensions as apiGetExtensions, - toggleExtension as apiToggleExtension, addExtension as apiAddExtension, removeExtension as apiRemoveExtension, - updateExtension as apiUpdateExtension, providers, } from '../api'; import { client } from '../api/client.gen'; @@ -17,12 +15,16 @@ import type { UpsertConfigQuery, ConfigKeyQuery, ExtensionResponse, - ExtensionEntry, ProviderDetails, ExtensionQuery, ExtensionConfig, } from '../api/types.gen'; +// Define a local version that matches the structure of the imported one +export type FixedExtensionEntry = ExtensionConfig & { + enabled: boolean; +}; + // Initialize client configuration client.setConfig({ baseUrl: window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT'), @@ -35,15 +37,15 @@ client.setConfig({ interface ConfigContextType { config: ConfigResponse['config']; providersList: ProviderDetails[]; + extensionsList: FixedExtensionEntry[]; upsert: (key: string, value: unknown, is_secret: boolean) => Promise; read: (key: string, is_secret: boolean) => Promise; remove: (key: string, is_secret: boolean) => Promise; addExtension: (name: string, config: ExtensionConfig, enabled: boolean) => Promise; - updateExtension: (name: string, config: ExtensionConfig, enabled: boolean) => Promise; toggleExtension: (name: string) => Promise; removeExtension: (name: string) => Promise; getProviders: (b: boolean) => Promise; - getExtensions: (b: boolean) => Promise; + getExtensions: (b: boolean) => Promise; } interface ConfigProviderProps { @@ -55,7 +57,7 @@ const ConfigContext = createContext(undefined); export const ConfigProvider: React.FC = ({ children }) => { const [config, setConfig] = useState({}); const [providersList, setProvidersList] = useState([]); - const [extensionsList, setExtensionsList] = useState([]); + const [extensionsList, setExtensionsList] = useState([]); useEffect(() => { // Load all configuration data and providers on mount @@ -128,18 +130,15 @@ export const ConfigProvider: React.FC = ({ children }) => { await reloadConfig(); }; - const updateExtension = async (name: string, config: ExtensionConfig, enabled: boolean) => { - const query: ExtensionQuery = { name, config, enabled }; - await apiUpdateExtension({ - body: query, - path: { name: name }, - }); - await reloadConfig(); - }; - const toggleExtension = async (name: string) => { - await apiToggleExtension({ path: { name: name } }); - await reloadConfig(); + // Get current extensions to find the one we need to toggle + const exts = await getExtensions(true); + const extension = exts.find((ext) => ext.name === name); + + if (extension) { + // Toggle the enabled state and update using addExtension + await addExtension(name, extension, !extension.enabled); + } }; const getProviders = async (forceRefresh = false): Promise => { @@ -153,7 +152,7 @@ export const ConfigProvider: React.FC = ({ children }) => { return providersList; }; - const getExtensions = async (forceRefresh = false): Promise => { + const getExtensions = async (forceRefresh = false): Promise => { if (forceRefresh || extensionsList.length === 0) { // If a refresh is forced, or we don't have providers yet const response = await apiGetExtensions(); @@ -169,17 +168,17 @@ export const ConfigProvider: React.FC = ({ children }) => { () => ({ config, providersList, + extensionsList, upsert, read, remove, addExtension, - updateExtension, removeExtension, toggleExtension, getProviders, getExtensions, }), - [config, providersList] + [config, providersList, extensionsList] ); // Functions don't need to be dependencies as they don't change return {children};