Remove dead code and old settings migration (#4180)
This commit is contained in:
@@ -3,11 +3,10 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Recipe, generateDeepLink } from '../recipe';
|
||||
import { Parameter } from '../recipe/index';
|
||||
|
||||
import { FullExtensionConfig } from '../extensions';
|
||||
import { Geese } from './icons/Geese';
|
||||
import Copy from './icons/Copy';
|
||||
import { Check, Save, Calendar } from 'lucide-react';
|
||||
import { useConfig } from './ConfigContext';
|
||||
import { ExtensionConfig, useConfig } from './ConfigContext';
|
||||
import { FixedExtensionEntry } from './ConfigContext';
|
||||
import RecipeActivityEditor from './RecipeActivityEditor';
|
||||
import RecipeInfoModal from './RecipeInfoModal';
|
||||
@@ -186,7 +185,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
}
|
||||
return cleanExtension;
|
||||
})
|
||||
.filter(Boolean) as FullExtensionConfig[],
|
||||
.filter(Boolean) as ExtensionConfig[],
|
||||
};
|
||||
console.log('Final config extensions:', config.extensions);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Recipe, generateDeepLink } from '../recipe';
|
||||
import { Parameter } from '../recipe/index';
|
||||
import { FullExtensionConfig } from '../extensions';
|
||||
import { Geese } from './icons/Geese';
|
||||
import Copy from './icons/Copy';
|
||||
import { Check, Save, Calendar, X } from 'lucide-react';
|
||||
import { useConfig } from './ConfigContext';
|
||||
import { ExtensionConfig, useConfig } from './ConfigContext';
|
||||
import { FixedExtensionEntry } from './ConfigContext';
|
||||
import RecipeActivityEditor from './RecipeActivityEditor';
|
||||
import RecipeInfoModal from './RecipeInfoModal';
|
||||
@@ -171,7 +170,7 @@ export default function ViewRecipeModal({ isOpen, onClose, config }: ViewRecipeM
|
||||
}
|
||||
return cleanExtension;
|
||||
})
|
||||
.filter(Boolean) as FullExtensionConfig[],
|
||||
.filter(Boolean) as ExtensionConfig[],
|
||||
};
|
||||
|
||||
return updatedConfig;
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
import { getApiUrl } from './config';
|
||||
import { toast } from 'react-toastify';
|
||||
import { safeJsonParse } from './utils/jsonUtils';
|
||||
|
||||
import builtInExtensionsData from './built-in-extensions.json';
|
||||
import { toastError, toastLoading, toastSuccess } from './toasts';
|
||||
|
||||
// Hardcoded default extension timeout in seconds
|
||||
export const DEFAULT_EXTENSION_TIMEOUT = 300;
|
||||
|
||||
// ExtensionConfig type matching the Rust version
|
||||
// TODO: refactor this
|
||||
export type ExtensionConfig =
|
||||
| {
|
||||
type: 'sse';
|
||||
name: string;
|
||||
uri: string;
|
||||
env_keys?: string[];
|
||||
timeout?: number;
|
||||
}
|
||||
| {
|
||||
type: 'streamable_http';
|
||||
name: string;
|
||||
uri: string;
|
||||
env_keys?: string[];
|
||||
headers?: Record<string, string>;
|
||||
timeout?: number;
|
||||
}
|
||||
| {
|
||||
type: 'stdio';
|
||||
name: string;
|
||||
cmd: string;
|
||||
args: string[];
|
||||
env_keys?: string[];
|
||||
timeout?: number;
|
||||
}
|
||||
| {
|
||||
type: 'builtin';
|
||||
name: string;
|
||||
env_keys?: string[];
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
// FullExtensionConfig type matching all the fields that come in deep links and are stored in local storage
|
||||
export type FullExtensionConfig = ExtensionConfig & {
|
||||
id: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export interface ExtensionPayload {
|
||||
name?: string;
|
||||
type?: string;
|
||||
cmd?: string;
|
||||
args?: string[];
|
||||
uri?: string;
|
||||
env_keys?: string[];
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export const BUILT_IN_EXTENSIONS = builtInExtensionsData as FullExtensionConfig[];
|
||||
|
||||
function sanitizeName(name: string) {
|
||||
return name.toLowerCase().replace(/-/g, '').replace(/_/g, '').replace(/\s/g, '');
|
||||
}
|
||||
|
||||
export async function addExtension(
|
||||
extension: FullExtensionConfig,
|
||||
silent: boolean = false
|
||||
): Promise<Response> {
|
||||
try {
|
||||
console.log('Adding extension:', extension);
|
||||
// Create the config based on the extension type
|
||||
const config = {
|
||||
type: extension.type,
|
||||
...(extension.type === 'stdio' && {
|
||||
name: sanitizeName(extension.name),
|
||||
cmd: await replaceWithShims(extension.cmd),
|
||||
args: extension.args || [],
|
||||
}),
|
||||
...(extension.type === 'sse' && {
|
||||
name: sanitizeName(extension.name),
|
||||
uri: extension.uri,
|
||||
}),
|
||||
...(extension.type === 'streamable_http' && {
|
||||
name: sanitizeName(extension.name),
|
||||
uri: extension.uri,
|
||||
}),
|
||||
...(extension.type === 'builtin' && {
|
||||
name: sanitizeName(extension.name),
|
||||
}),
|
||||
env_keys: extension.env_keys,
|
||||
timeout: extension.timeout,
|
||||
};
|
||||
|
||||
let toastId;
|
||||
if (!silent) toastId = toastLoading({ title: extension.name, msg: 'Adding extension...' });
|
||||
|
||||
const response = await fetch(getApiUrl('/extensions/add'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||
},
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMsg = `Server returned ${response.status}: ${response.statusText}. Response: ${responseText}`;
|
||||
console.error(errorMsg);
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
toastError({
|
||||
title: extension.name,
|
||||
msg: 'Failed to add extension',
|
||||
traceback: errorMsg,
|
||||
toastOptions: { autoClose: false },
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
// Only try to parse JSON if we got a successful response and have JSON content
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse response as JSON:', e);
|
||||
data = { error: true, message: responseText };
|
||||
}
|
||||
|
||||
if (!data.error) {
|
||||
if (!silent) {
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
toastSuccess({ title: extension.name, msg: `Successfully enabled extension` });
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
var errorMessage = `Error adding extension`;
|
||||
// Attempt to extract the message from inside StdioProcessError()
|
||||
// NOTE: this may change if the error response from /extensions/add changes
|
||||
const regex = /StdioProcessError\("(.*?)"\)/;
|
||||
const match = data.message.match(regex);
|
||||
|
||||
if (match) {
|
||||
const extracted = match[1];
|
||||
// only display the message if it is less than 100 chars
|
||||
errorMessage = extracted.length > 100 ? errorMessage : extracted;
|
||||
}
|
||||
|
||||
if (toastId) toast.dismiss(toastId);
|
||||
toastError({
|
||||
title: extension.name,
|
||||
msg: errorMessage,
|
||||
traceback: data.message,
|
||||
toastOptions: { autoClose: false },
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
const errorMessage = `Failed to add ${extension.name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
console.error(errorMessage);
|
||||
toastError({
|
||||
title: extension.name,
|
||||
msg: 'Failed to add extension',
|
||||
traceback: error instanceof Error ? error.message : String(error),
|
||||
toastOptions: { autoClose: false },
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeExtension(name: string, silent: boolean = false): Promise<Response> {
|
||||
try {
|
||||
const response = await fetch(getApiUrl('/extensions/remove'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||
},
|
||||
body: JSON.stringify(sanitizeName(name)),
|
||||
});
|
||||
|
||||
const data = await safeJsonParse<{ error: boolean; message: string }>(response);
|
||||
|
||||
if (!data.error) {
|
||||
if (!silent) {
|
||||
toastSuccess({ title: name, msg: 'Successfully disabled extension' });
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
const errorMessage = `Error removing ${name} extension${data.message ? `. ${data.message}` : ''}`;
|
||||
console.error(errorMessage);
|
||||
toastError({
|
||||
title: name,
|
||||
msg: 'Error removing extension',
|
||||
traceback: data.message,
|
||||
toastOptions: { autoClose: false },
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
const errorMessage = `Failed to remove ${name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
console.error(errorMessage);
|
||||
toastError({
|
||||
title: name,
|
||||
msg: 'Error removing extension',
|
||||
traceback: error instanceof Error ? error.message : String(error),
|
||||
toastOptions: { autoClose: false },
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the path to the binary based on the command
|
||||
export async function replaceWithShims(cmd: string) {
|
||||
const binaryPathMap: Record<string, string> = {
|
||||
goosed: await window.electron.getBinaryPath('goosed'),
|
||||
jbang: await window.electron.getBinaryPath('jbang'),
|
||||
npx: await window.electron.getBinaryPath('npx'),
|
||||
uvx: await window.electron.getBinaryPath('uvx'),
|
||||
'npx.cmd': await window.electron.getBinaryPath('npx.cmd'),
|
||||
};
|
||||
|
||||
if (binaryPathMap[cmd]) {
|
||||
console.log('--------> Replacing command with shim ------>', cmd, binaryPathMap[cmd]);
|
||||
cmd = binaryPathMap[cmd];
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getApiUrl } from '../config';
|
||||
import { FullExtensionConfig } from '../extensions';
|
||||
import { initializeAgent } from '../agent';
|
||||
import {
|
||||
initializeBundledExtensions,
|
||||
@@ -8,16 +7,7 @@ import {
|
||||
} from '../components/settings/extensions';
|
||||
import { extractExtensionConfig } from '../components/settings/extensions/utils';
|
||||
import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigContext';
|
||||
// TODO: remove when removing migration logic
|
||||
import { toastService } from '../toasts';
|
||||
import {
|
||||
ExtensionQuery,
|
||||
RecipeParameter,
|
||||
SubRecipe,
|
||||
addExtension as apiAddExtension,
|
||||
updateSessionConfig,
|
||||
extendPrompt,
|
||||
} from '../api';
|
||||
import { RecipeParameter, SubRecipe, updateSessionConfig, extendPrompt } from '../api';
|
||||
import { addSubRecipesToAgent } from '../recipe/add_sub_recipe_on_agent';
|
||||
|
||||
export interface Provider {
|
||||
@@ -119,81 +109,6 @@ export const updateSystemPromptWithParameters = async (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrates extensions from localStorage to config.yaml (settings v2)
|
||||
* This function handles the migration from settings v1 to v2 by:
|
||||
* 1. Reading extensions from localStorage
|
||||
* 2. Adding non-builtin extensions to config.yaml
|
||||
* 3. Marking the migration as complete
|
||||
*
|
||||
* NOTE: This logic can be removed eventually when enough versions have passed
|
||||
* We leave the existing user settings in localStorage, in case users downgrade
|
||||
* or things need to be reverted.
|
||||
*/
|
||||
export const migrateExtensionsToSettingsV3 = async () => {
|
||||
console.log('need to perform extension migration v3');
|
||||
|
||||
const userSettingsStr = localStorage.getItem('user_settings');
|
||||
let localStorageExtensions: FullExtensionConfig[] = [];
|
||||
|
||||
try {
|
||||
if (userSettingsStr) {
|
||||
const userSettings = JSON.parse(userSettingsStr);
|
||||
localStorageExtensions = userSettings.extensions ?? [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse user settings:', error);
|
||||
}
|
||||
|
||||
if (localStorageExtensions.length === 0) {
|
||||
localStorage.setItem('configVersion', '3');
|
||||
console.log('No extensions to migrate. Config version set to 3.');
|
||||
return;
|
||||
}
|
||||
|
||||
const migrationErrors: { name: string; error: unknown }[] = [];
|
||||
|
||||
// Process extensions in parallel for better performance
|
||||
const migrationPromises = localStorageExtensions
|
||||
.filter((extension) => extension.type !== 'builtin') // Skip builtins as before
|
||||
.map(async (extension) => {
|
||||
console.log(`Migrating extension ${extension.name} to config.yaml`);
|
||||
try {
|
||||
const query: ExtensionQuery = {
|
||||
name: extension.name,
|
||||
config: extension,
|
||||
enabled: extension.enabled,
|
||||
};
|
||||
await apiAddExtension({
|
||||
body: query,
|
||||
throwOnError: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Failed to migrate extension ${extension.name}:`, err);
|
||||
migrationErrors.push({
|
||||
name: extension.name,
|
||||
error: `failed migration with ${JSON.stringify(err)}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(migrationPromises);
|
||||
|
||||
if (migrationErrors.length === 0) {
|
||||
localStorage.setItem('configVersion', '3');
|
||||
console.log('Extension migration complete. Config version set to 3.');
|
||||
} else {
|
||||
const errorSummaryStr = migrationErrors
|
||||
.map(({ name, error }) => `- ${name}: ${JSON.stringify(error)}`)
|
||||
.join('\n');
|
||||
toastService.error({
|
||||
title: 'Config Migration Error',
|
||||
msg: 'There was a problem updating your config file',
|
||||
traceback: errorSummaryStr,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const initializeSystem = async (
|
||||
provider: string,
|
||||
model: string,
|
||||
@@ -254,22 +169,6 @@ export const initializeSystem = async (
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: remove when we want to stop migration logic
|
||||
// Check if we need to migrate extensions from localStorage to config.yaml
|
||||
const configVersion = localStorage.getItem('configVersion');
|
||||
const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3;
|
||||
|
||||
if (shouldMigrateExtensions) {
|
||||
await migrateExtensionsToSettingsV3();
|
||||
}
|
||||
|
||||
/* NOTE:
|
||||
* If we've migrated and this is a version update, refreshedExtensions should be > 0
|
||||
* and we'll want to syncBundledExtensions to ensure any new extensions are added.
|
||||
* Otherwise if the user has never opened goose - refreshedExtensions will be 0
|
||||
* and we want to fall into the case to initializeBundledExtensions.
|
||||
*/
|
||||
|
||||
// Initialize or sync built-in extensions into config.yaml
|
||||
let refreshedExtensions = await options.getExtensions(false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user