diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json
index 011a55f20eac..f0f203b91616 100644
--- a/ui/desktop/openapi.json
+++ b/ui/desktop/openapi.json
@@ -10,7 +10,7 @@
"license": {
"name": "Apache-2.0"
},
- "version": "1.0.13"
+ "version": "1.0.14"
},
"paths": {
"/config": {
diff --git a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx
index ece4fafdc781..fa7edcc537d3 100644
--- a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx
+++ b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx
@@ -4,65 +4,29 @@ import { Switch } from '../../ui/switch';
import { Plus, X } from 'lucide-react';
import { Gear } from '../../icons/Gear';
import { GPSIcon } from '../../ui/icons';
-import { useConfig } from '../../ConfigContext';
+import { useConfig, FixedExtensionEntry } from '../../ConfigContext';
import Modal from '../../Modal';
import { Input } from '../../ui/input';
import Select from 'react-select';
import { createDarkSelectStyles, darkSelectTheme } from '../../ui/select-styles';
-
-interface ExtensionConfig {
- args?: string[];
- cmd?: string;
- enabled: boolean;
- envs?: Record;
- name: string;
- type: 'stdio' | 'sse' | 'builtin';
-}
-
-interface ExtensionItem {
- id: string;
- title: string;
- subtitle: string;
- enabled: boolean;
- canConfigure: boolean;
- config: ExtensionConfig;
-}
-
-interface EnvVar {
- key: string;
- value: string;
-}
-
-// Helper function to get a friendly title from extension name
-const getFriendlyTitle = (name: string): string => {
- return name
- .split('-')
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(' ');
-};
-
-// Helper function to get a subtitle based on extension type and configuration
-const getSubtitle = (config: ExtensionConfig): string => {
- if (config.type === 'builtin') {
- return 'Built-in extension';
- }
- return `${config.type.toUpperCase()} extension${config.cmd ? ` (${config.cmd})` : ''}`;
-};
+import { ExtensionConfig } from '../../../api/types.gen';
export default function ExtensionsSection() {
- const { config, read, updateExtension, addExtension } = useConfig();
- const [extensions, setExtensions] = useState([]);
- const [selectedExtension, setSelectedExtension] = useState(null);
+ const { toggleExtension, getExtensions, addExtension } = useConfig();
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [extensions, setExtensions] = useState([]);
+ const [selectedExtension, setSelectedExtension] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [formData, setFormData] = useState<{
name: string;
- type: 'stdio' | 'sse';
+ type: 'stdio' | 'sse' | 'builtin';
cmd?: string;
args?: string[];
endpoint?: string;
enabled: boolean;
- envVars: EnvVar[];
+ envVars: { key: string; value: string }[];
}>({
name: '',
type: 'stdio',
@@ -73,63 +37,82 @@ export default function ExtensionsSection() {
envVars: [],
});
- useEffect(() => {
- const extensions = read('extensions', false);
- if (extensions) {
- const extensionItems: ExtensionItem[] = Object.entries(extensions).map(([name, ext]) => {
- const extensionConfig = ext as ExtensionConfig;
- return {
- id: name,
- title: getFriendlyTitle(name),
- subtitle: getSubtitle(extensionConfig),
- enabled: extensionConfig.enabled,
- canConfigure: extensionConfig.type === 'stdio' && !!extensionConfig.envs,
- config: extensionConfig,
- };
- });
- setExtensions(extensionItems);
+ // Helper function to get a friendly title from extension name
+ const getFriendlyTitle = (name: string): string => {
+ return name
+ .split('-')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+ };
+
+ // Helper function to get a subtitle based on extension type and configuration
+ const getSubtitle = (config: ExtensionConfig): string => {
+ if (config.type === 'builtin') {
+ return 'Built-in extension';
+ }
+ if (config.type === 'stdio') {
+ return `STDIO extension${config.cmd ? ` (${config.cmd})` : ''}`;
}
- }, [read]);
+ if (config.type === 'sse') {
+ return `SSE extension${config.uri ? ` (${config.uri})` : ''}`;
+ }
+ return `Unknown type of extension`;
+ };
+ const fetchExtensions = async () => {
+ setLoading(true);
+ try {
+ const extensionsList = await getExtensions(true); // Force refresh
+ // Sort extensions by name to maintain consistent order
+ const sortedExtensions = [...extensionsList].sort((a, b) => a.name.localeCompare(b.name));
+ setExtensions(sortedExtensions);
+ setError(null);
+ } catch (err) {
+ setError('Failed to load extensions');
+ console.error('Error loading extensions:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchExtensions();
+ }, []);
useEffect(() => {
if (selectedExtension) {
- const envVars = selectedExtension.config.envs
- ? Object.entries(selectedExtension.config.envs).map(([key, value]) => ({
- key,
- value: value as string,
- }))
- : [];
+ // Type guard: Check if 'envs' property exists for this variant
+ const hasEnvs = selectedExtension.type === 'sse' || selectedExtension.type === 'stdio';
+
+ const envVars =
+ hasEnvs && selectedExtension.envs
+ ? Object.entries(selectedExtension.envs).map(([key, value]) => ({
+ key,
+ value: value as string,
+ }))
+ : [];
setFormData({
- name: selectedExtension.config.name,
- type: selectedExtension.config.type as 'stdio' | 'sse',
- cmd: selectedExtension.config.type === 'stdio' ? selectedExtension.config.cmd : undefined,
- args: selectedExtension.config.args || [],
- endpoint:
- selectedExtension.config.type === 'sse' ? selectedExtension.config.cmd : undefined,
- enabled: selectedExtension.config.enabled,
+ name: selectedExtension.name,
+ type: selectedExtension.type,
+ cmd: selectedExtension.type === 'stdio' ? selectedExtension.cmd : undefined,
+ args: selectedExtension.type === 'stdio' ? selectedExtension.args : [],
+ endpoint: selectedExtension.type === 'sse' ? selectedExtension.uri : undefined,
+ enabled: selectedExtension.enabled,
envVars,
});
}
}, [selectedExtension]);
- const handleExtensionToggle = async (id: string) => {
- const extension = extensions.find((ext) => ext.id === id);
- if (extension) {
- const updatedConfig = {
- ...extension.config,
- enabled: !extension.config.enabled,
- };
-
- try {
- await updateExtension(id, updatedConfig);
- } catch (error) {
- console.error('Failed to update extension:', error);
- }
+ const handleExtensionToggle = async (name: string) => {
+ try {
+ await toggleExtension(name);
+ fetchExtensions(); // Refresh the list after toggling
+ } catch (error) {
+ console.error('Failed to toggle extension:', error);
}
};
- const handleConfigureClick = (extension: ExtensionItem) => {
+ const handleConfigureClick = (extension: FixedExtensionEntry) => {
setSelectedExtension(extension);
setIsModalOpen(true);
};
@@ -145,24 +128,35 @@ export default function ExtensionsSection() {
{} as Record
);
- const extensionConfig = {
- name: formData.name,
- type: formData.type,
- enabled: formData.enabled,
- envs,
- ...(formData.type === 'stdio'
- ? {
- cmd: formData.cmd,
- args: formData.args,
- }
- : {
- cmd: formData.endpoint,
- }),
- };
+ let extensionConfig: ExtensionConfig;
+
+ if (formData.type === 'stdio') {
+ extensionConfig = {
+ type: 'stdio',
+ name: formData.name,
+ cmd: formData.cmd,
+ args: formData.args,
+ ...(Object.keys(envs).length > 0 ? { envs } : {}),
+ };
+ } else if (formData.type === 'sse') {
+ extensionConfig = {
+ type: 'sse',
+ name: formData.name,
+ uri: formData.endpoint, // Assuming endpoint maps to uri for SSE type
+ ...(Object.keys(envs).length > 0 ? { envs } : {}),
+ };
+ } else {
+ // For other types
+ extensionConfig = {
+ type: formData.type,
+ name: formData.name,
+ };
+ }
try {
- await addExtension(formData.name, extensionConfig);
+ await addExtension(formData.name, extensionConfig, formData.enabled);
handleModalClose();
+ fetchExtensions(); // Refresh the list after adding
} catch (error) {
console.error('Failed to add extension:', error);
}
@@ -207,7 +201,6 @@ export default function ExtensionsSection() {
envVars: newEnvVars,
});
};
-
const handleSaveConfig = async () => {
if (!selectedExtension) return;
@@ -221,24 +214,36 @@ export default function ExtensionsSection() {
{} as Record
);
- const updatedConfig = {
- name: formData.name,
- type: formData.type,
- enabled: formData.enabled,
- envs,
- ...(formData.type === 'stdio'
- ? {
- cmd: formData.cmd,
- args: formData.args,
- }
- : {
- cmd: formData.endpoint,
- }),
- };
+ let extensionConfig: ExtensionConfig;
+
+ if (formData.type === 'stdio') {
+ extensionConfig = {
+ type: 'stdio',
+ name: formData.name,
+ cmd: formData.cmd,
+ args: formData.args,
+ ...(Object.keys(envs).length > 0 ? { envs } : {}),
+ };
+ } else if (formData.type === 'sse') {
+ extensionConfig = {
+ type: 'sse',
+ name: formData.name,
+ uri: formData.endpoint, // Assuming endpoint maps to uri for SSE type
+ ...(Object.keys(envs).length > 0 ? { envs } : {}),
+ };
+ } else {
+ // For other types
+ extensionConfig = {
+ type: formData.type,
+ name: formData.name,
+ };
+ }
try {
- await updateExtension(selectedExtension.id, updatedConfig);
+ // CHANGE: Use addExtension instead of updateExtension
+ await addExtension(formData.name, extensionConfig, formData.enabled);
handleModalClose();
+ fetchExtensions(); // Refresh the list after updating
} catch (error) {
console.error('Failed to update extension configuration:', error);
}
@@ -256,14 +261,17 @@ export default function ExtensionsSection() {
{extensions.map((extension, index) => (
-
+
-
{extension.title}
-
{extension.subtitle}
+
+ {getFriendlyTitle(extension.name)}
+
+
{getSubtitle(extension)}
- {extension.canConfigure && (
+ {/* Only show config button for non-builtin extensions */}
+ {extension.type !== 'builtin' && (
@@ -329,10 +337,10 @@ export default function ExtensionsSection() {