feat: non-editable bundled extensions (#2114)
This commit is contained in:
@@ -390,6 +390,11 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"bundled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this extension is bundled with Goose",
|
||||
"nullable": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@@ -434,6 +439,11 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"bundled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this extension is bundled with Goose",
|
||||
"nullable": true
|
||||
},
|
||||
"cmd": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -470,6 +480,11 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"bundled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this extension is bundled with Goose",
|
||||
"nullable": true
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@@ -501,6 +516,11 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"bundled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this extension is bundled with Goose",
|
||||
"nullable": true
|
||||
},
|
||||
"instructions": {
|
||||
"type": "string",
|
||||
"description": "Instructions for how to use these tools",
|
||||
|
||||
@@ -24,6 +24,10 @@ export type Envs = {
|
||||
* Represents the different types of MCP extensions that can be added to the manager
|
||||
*/
|
||||
export type ExtensionConfig = {
|
||||
/**
|
||||
* Whether this extension is bundled with Goose
|
||||
*/
|
||||
bundled?: boolean | null;
|
||||
description?: string | null;
|
||||
envs?: Envs;
|
||||
/**
|
||||
@@ -35,6 +39,10 @@ export type ExtensionConfig = {
|
||||
uri: string;
|
||||
} | {
|
||||
args: Array<string>;
|
||||
/**
|
||||
* Whether this extension is bundled with Goose
|
||||
*/
|
||||
bundled?: boolean | null;
|
||||
cmd: string;
|
||||
description?: string | null;
|
||||
envs?: Envs;
|
||||
@@ -45,6 +53,10 @@ export type ExtensionConfig = {
|
||||
timeout?: number | null;
|
||||
type: 'stdio';
|
||||
} | {
|
||||
/**
|
||||
* Whether this extension is bundled with Goose
|
||||
*/
|
||||
bundled?: boolean | null;
|
||||
display_name?: string | null;
|
||||
/**
|
||||
* The name used to identify this extension
|
||||
@@ -53,6 +65,10 @@ export type ExtensionConfig = {
|
||||
timeout?: number | null;
|
||||
type: 'builtin';
|
||||
} | {
|
||||
/**
|
||||
* Whether this extension is bundled with Goose
|
||||
*/
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Instructions for how to use these tools
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"enabled": true,
|
||||
"type": "builtin",
|
||||
"env_keys": [],
|
||||
"timeout": 300
|
||||
"timeout": 300,
|
||||
"bundled": true
|
||||
},
|
||||
{
|
||||
"id": "computercontroller",
|
||||
@@ -17,7 +18,8 @@
|
||||
"enabled": false,
|
||||
"type": "builtin",
|
||||
"env_keys": [],
|
||||
"timeout": 300
|
||||
"timeout": 300,
|
||||
"bundled": true
|
||||
},
|
||||
{
|
||||
"id": "memory",
|
||||
@@ -27,7 +29,8 @@
|
||||
"enabled": false,
|
||||
"type": "builtin",
|
||||
"env_keys": [],
|
||||
"timeout": 300
|
||||
"timeout": 300,
|
||||
"bundled": true
|
||||
},
|
||||
{
|
||||
"id": "jetbrains",
|
||||
@@ -37,7 +40,8 @@
|
||||
"enabled": false,
|
||||
"type": "builtin",
|
||||
"env_keys": [],
|
||||
"timeout": 300
|
||||
"timeout": 300,
|
||||
"bundled": true
|
||||
},
|
||||
{
|
||||
"id": "tutorial",
|
||||
@@ -46,6 +50,7 @@
|
||||
"description": "Access interactive tutorials and guides",
|
||||
"enabled": false,
|
||||
"type": "builtin",
|
||||
"env_keys": []
|
||||
"env_keys": [],
|
||||
"bundled": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -33,68 +33,58 @@ export async function syncBundledExtensions(
|
||||
addExtensionFn: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Create a set of existing extension IDs for quick lookup
|
||||
const existingExtensionKeys = new Set(existingExtensions.map((ext) => nameToKey(ext.name)));
|
||||
|
||||
// Cast the imported JSON data to the expected type
|
||||
const bundledExtensions = bundledExtensionsData as BundledExtension[];
|
||||
|
||||
// Track how many extensions were added
|
||||
let addedCount = 0;
|
||||
|
||||
// Check each built-in extension
|
||||
// Process each bundled extension
|
||||
for (const bundledExt of bundledExtensions) {
|
||||
// Only add if the extension doesn't already exist -- use the id
|
||||
if (!existingExtensionKeys.has(bundledExt.id)) {
|
||||
console.log(`Adding built-in extension: ${bundledExt.id}`);
|
||||
let extConfig: ExtensionConfig;
|
||||
switch (bundledExt.type) {
|
||||
case 'builtin':
|
||||
extConfig = {
|
||||
name: bundledExt.name,
|
||||
display_name: bundledExt.display_name,
|
||||
type: bundledExt.type,
|
||||
timeout: bundledExt.timeout ?? 300,
|
||||
};
|
||||
break;
|
||||
case 'stdio':
|
||||
extConfig = {
|
||||
name: bundledExt.name,
|
||||
description: bundledExt.description,
|
||||
type: bundledExt.type,
|
||||
timeout: bundledExt.timeout,
|
||||
cmd: bundledExt.cmd,
|
||||
args: bundledExt.args,
|
||||
envs: bundledExt.envs,
|
||||
};
|
||||
break;
|
||||
case 'sse':
|
||||
extConfig = {
|
||||
name: bundledExt.name,
|
||||
description: bundledExt.description,
|
||||
type: bundledExt.type,
|
||||
timeout: bundledExt.timeout,
|
||||
uri: bundledExt.uri,
|
||||
};
|
||||
}
|
||||
// Add the extension with its default enabled state
|
||||
try {
|
||||
await addExtensionFn(bundledExt.name, extConfig, bundledExt.enabled);
|
||||
addedCount++;
|
||||
} catch (error) {
|
||||
console.error(`Failed to add built-in extension ${bundledExt.name}:`, error);
|
||||
// Continue with other extensions even if one fails
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find if this extension already exists
|
||||
const existingExt = existingExtensions.find((ext) => nameToKey(ext.name) === bundledExt.id);
|
||||
|
||||
if (addedCount > 0) {
|
||||
console.log(`Added ${addedCount} built-in extensions.`);
|
||||
} else {
|
||||
console.log('All built-in extensions already present.');
|
||||
// Skip if extension exists and is already marked as bundled
|
||||
if (existingExt?.bundled) continue;
|
||||
|
||||
// Create the config for this extension
|
||||
let extConfig: ExtensionConfig;
|
||||
switch (bundledExt.type) {
|
||||
case 'builtin':
|
||||
extConfig = {
|
||||
name: bundledExt.name,
|
||||
display_name: bundledExt.display_name,
|
||||
type: bundledExt.type,
|
||||
timeout: bundledExt.timeout ?? 300,
|
||||
bundled: true,
|
||||
};
|
||||
break;
|
||||
case 'stdio':
|
||||
extConfig = {
|
||||
name: bundledExt.name,
|
||||
description: bundledExt.description,
|
||||
type: bundledExt.type,
|
||||
timeout: bundledExt.timeout,
|
||||
cmd: bundledExt.cmd,
|
||||
args: bundledExt.args,
|
||||
envs: bundledExt.envs,
|
||||
bundled: true,
|
||||
};
|
||||
break;
|
||||
case 'sse':
|
||||
extConfig = {
|
||||
name: bundledExt.name,
|
||||
description: bundledExt.description,
|
||||
type: bundledExt.type,
|
||||
timeout: bundledExt.timeout,
|
||||
uri: bundledExt.uri,
|
||||
bundled: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Add or update the extension, preserving enabled state if it exists
|
||||
const enabled = existingExt ? existingExt.enabled : bundledExt.enabled;
|
||||
await addExtensionFn(bundledExt.name, extConfig, enabled);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add built-in extensions:', error);
|
||||
console.error('Failed to sync built-in extensions:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
|
||||
));
|
||||
};
|
||||
|
||||
// Bundled extensions and builtins are not editable
|
||||
// Over time we can take the first part of the conditional away as people have bundled: true in their config.yaml entries
|
||||
const editable = !(extension.type === 'builtin' || extension.bundled);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex justify-between rounded-lg transition-colors border border-borderSubtle p-4 pt-3 hover:border-borderProminent hover:cursor-pointer"
|
||||
@@ -70,8 +74,7 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
|
||||
className="flex items-center justify-end gap-2 w-max-[10%]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Only show config button for non-builtin extensions */}
|
||||
{extension.type !== 'builtin' && (
|
||||
{editable && (
|
||||
<button
|
||||
className="text-textSubtle hover:text-textStandard"
|
||||
onClick={() => onConfigure(extension)}
|
||||
|
||||
Reference in New Issue
Block a user