From b570be301429b7c42d6ab3de23baf0802224c3ef Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Mon, 24 Mar 2025 13:54:39 -0400 Subject: [PATCH] feat: settings v2 extension add refactor (#1815) --- .../extensions/ExtensionsSection.tsx | 5 +- .../settings_v2/extensions/index.ts | 103 ++++++++++++------ ui/desktop/src/main.ts | 6 +- 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx index 6f86ed86..0c257030 100644 --- a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx +++ b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx @@ -12,6 +12,7 @@ import { getDefaultFormData, } from './utils'; import { useAgent } from '../../../agent/UpdateAgent'; +import { activateExtension } from '.'; export default function ExtensionsSection() { const { toggleExtension, getExtensions, addExtension, removeExtension } = useConfig(); @@ -61,7 +62,7 @@ export default function ExtensionsSection() { const extensionConfig = createExtensionConfig(formData); try { - await addExtension(formData.name, extensionConfig, formData.enabled); + await activateExtension(formData.name, extensionConfig, addExtension); console.log('attempting to add extension'); await updateAgent(extensionConfig); handleModalClose(); @@ -75,7 +76,7 @@ export default function ExtensionsSection() { const extensionConfig = createExtensionConfig(formData); try { - await addExtension(formData.name, extensionConfig, formData.enabled); + await activateExtension(formData.name, extensionConfig, addExtension); handleModalClose(); fetchExtensions(); // Refresh the list after updating } catch (error) { diff --git a/ui/desktop/src/components/settings_v2/extensions/index.ts b/ui/desktop/src/components/settings_v2/extensions/index.ts index 754bae8f..02adfb89 100644 --- a/ui/desktop/src/components/settings_v2/extensions/index.ts +++ b/ui/desktop/src/components/settings_v2/extensions/index.ts @@ -36,6 +36,72 @@ function handleError(message: string, shouldThrow = false): void { } } +// Update the path to the binary based on the command +async function replaceWithShims(cmd: string) { + const binaryPathMap: Record = { + goosed: await window.electron.getBinaryPath('goosed'), + npx: await window.electron.getBinaryPath('npx'), + uvx: await window.electron.getBinaryPath('uvx'), + }; + + if (binaryPathMap[cmd]) { + console.log('--------> Replacing command with shim ------>', cmd, binaryPathMap[cmd]); + cmd = binaryPathMap[cmd]; + } + + return cmd; +} + +/** + * Activates an extension by adding it to both the config system and the API. + * @param name The extension name + * @param config The extension configuration + * @param addExtensionFn Function to add extension to config + * @returns Promise that resolves when activation is complete + */ +export async function activateExtension( + name: string, + config: ExtensionConfig, + addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise +): Promise { + try { + // First add to the config system + await addExtensionFn(nameToKey(name), config, true); + + // Then call the API endpoint + const response = await fetch(getApiUrl('/extensions/add'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + type: config.type, + name: nameToKey(name), + cmd: await replaceWithShims(config.cmd), + args: config.args || [], + env_keys: config.envs ? Object.keys(config.envs) : undefined, + timeout: config.timeout, + }), + }); + + const data = await response.json(); + + if (!data.error) { + toast.success(`Extension "${name}" has been successfully enabled`); + } else { + const errorMessage = `Error adding ${name} extension${data.message ? `. ${data.message}` : ''}`; + console.error(errorMessage); + toast.error(errorMessage, { autoClose: false }); + } + } catch (error) { + const errorMessage = `Failed to add ${name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`; + console.error(errorMessage); + toast.error(errorMessage, { autoClose: false }); + throw error; + } +} + export async function addExtensionFromDeepLink( url: string, addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise, @@ -116,42 +182,7 @@ export async function addExtensionFromDeepLink( } // If no env vars are required, proceed with adding the extension - try { - // First add to the config system - await addExtensionFn(nameToKey(name), config, true); - - // Then call the API endpoint - const response = await fetch(getApiUrl('/extensions/add'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ - type: config.type, - name: nameToKey(name), - cmd: config.cmd, - args: config.args || [], - env_keys: config.envs ? Object.keys(config.envs) : undefined, - timeout: config.timeout, - }), - }); - - const data = await response.json(); - - if (!data.error) { - toast.success(`Extension "${name}" has been successfully enabled`); - } else { - const errorMessage = `Error adding ${name} extension${data.message ? `. ${data.message}` : ''}`; - console.error(errorMessage); - toast.error(errorMessage, { autoClose: false }); - } - } catch (error) { - const errorMessage = `Failed to add ${name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`; - console.error(errorMessage); - toast.error(errorMessage, { autoClose: false }); - throw error; - } + await activateExtension(name, config, addExtensionFn); } /** diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 4544bc4f..ae407f51 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -78,8 +78,8 @@ app.on('open-url', async (event, url) => { } } - // Handle different types of deep links - if (parsedUrl.pathname === '/extension') { + // Handle extension install links + if (parsedUrl.hostname === 'extension') { firstOpenWindow.webContents.send('add-extension', pendingDeepLink); } }); @@ -360,7 +360,7 @@ ipcMain.on('react-ready', (event) => { console.log('Processing pending deep link:', pendingDeepLink); const parsedUrl = new URL(pendingDeepLink); - if (parsedUrl.pathname === '/extension') { + if (parsedUrl.hostname === 'extension') { console.log('Sending add-extension event'); firstOpenWindow.webContents.send('add-extension', pendingDeepLink); }