extensions: add a display name field (#1759)

This commit is contained in:
Lily Delalande
2025-03-19 15:42:57 -04:00
committed by GitHub
parent 15f6973bcf
commit 70efaed793
14 changed files with 124 additions and 11 deletions

View File

@@ -10,6 +10,27 @@ use serde_json::{json, Value};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
fn get_display_name(extension_id: &str) -> String {
match extension_id {
"developer" => "Developer Tools".to_string(),
"computercontroller" => "Computer Controller".to_string(),
"googledrive" => "Google Drive".to_string(),
"memory" => "Memory".to_string(),
"tutorial" => "Tutorial".to_string(),
"jetbrains" => "JetBrains".to_string(),
// Add other extensions as needed
_ => {
extension_id
.chars()
.next()
.unwrap_or_default()
.to_uppercase()
.collect::<String>()
+ &extension_id[1..]
}
}
}
pub async fn handle_configure() -> Result<(), Box<dyn Error>> { pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
let config = Config::global(); let config = Config::global();
@@ -39,6 +60,7 @@ pub async fn handle_configure() -> Result<(), Box<dyn Error>> {
enabled: true, enabled: true,
config: ExtensionConfig::Builtin { config: ExtensionConfig::Builtin {
name: "developer".to_string(), name: "developer".to_string(),
display_name: Some(goose::config::DEFAULT_DISPLAY_NAME.to_string()),
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
}, },
})?; })?;
@@ -464,10 +486,13 @@ pub fn configure_extensions_dialog() -> Result<(), Box<dyn Error>> {
}) })
.interact()?; .interact()?;
let display_name = get_display_name(&extension);
ExtensionManager::set(ExtensionEntry { ExtensionManager::set(ExtensionEntry {
enabled: true, enabled: true,
config: ExtensionConfig::Builtin { config: ExtensionConfig::Builtin {
name: extension.clone(), name: extension.clone(),
display_name: Some(display_name),
timeout: Some(timeout), timeout: Some(timeout),
}, },
})?; })?;

View File

@@ -133,6 +133,7 @@ impl Session {
for name in builtin_name.split(',') { for name in builtin_name.split(',') {
let config = ExtensionConfig::Builtin { let config = ExtensionConfig::Builtin {
name: name.trim().to_string(), name: name.trim().to_string(),
display_name: None,
// TODO: should set a timeout // TODO: should set a timeout
timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT), timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),
}; };

View File

@@ -45,6 +45,7 @@ enum ExtensionConfigRequest {
Builtin { Builtin {
/// The name of the built-in extension. /// The name of the built-in extension.
name: String, name: String,
display_name: Option<String>,
timeout: Option<u64>, timeout: Option<u64>,
}, },
} }
@@ -157,9 +158,15 @@ async fn add_extension(
timeout, timeout,
} }
} }
ExtensionConfigRequest::Builtin { name, timeout } => { ExtensionConfigRequest::Builtin {
ExtensionConfig::Builtin { name, timeout } name,
} display_name,
timeout,
} => ExtensionConfig::Builtin {
name,
display_name,
timeout,
},
}; };
// Acquire a lock on the agent and attempt to add the extension. // Acquire a lock on the agent and attempt to add the extension.

View File

@@ -134,7 +134,12 @@ impl Capabilities {
); );
Box::new(McpClient::new(service)) Box::new(McpClient::new(service))
} }
ExtensionConfig::Builtin { name, timeout } => { #[allow(unused_variables)]
ExtensionConfig::Builtin {
name,
display_name,
timeout,
} => {
// For builtin extensions, we run the current executable with mcp and extension name // For builtin extensions, we run the current executable with mcp and extension name
let cmd = std::env::current_exe() let cmd = std::env::current_exe()
.expect("should find the current executable") .expect("should find the current executable")

View File

@@ -78,6 +78,7 @@ pub enum ExtensionConfig {
Builtin { Builtin {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
display_name: Option<String>, // needed for the UI
timeout: Option<u64>, timeout: Option<u64>,
}, },
} }
@@ -86,6 +87,7 @@ impl Default for ExtensionConfig {
fn default() -> Self { fn default() -> Self {
Self::Builtin { Self::Builtin {
name: config::DEFAULT_EXTENSION.to_string(), name: config::DEFAULT_EXTENSION.to_string(),
display_name: Some(config::DEFAULT_DISPLAY_NAME.to_string()),
timeout: Some(config::DEFAULT_EXTENSION_TIMEOUT), timeout: Some(config::DEFAULT_EXTENSION_TIMEOUT),
} }
} }

View File

@@ -8,6 +8,7 @@ use utoipa::ToSchema;
pub const DEFAULT_EXTENSION: &str = "developer"; pub const DEFAULT_EXTENSION: &str = "developer";
pub const DEFAULT_EXTENSION_TIMEOUT: u64 = 300; pub const DEFAULT_EXTENSION_TIMEOUT: u64 = 300;
pub const DEFAULT_EXTENSION_DESCRIPTION: &str = ""; pub const DEFAULT_EXTENSION_DESCRIPTION: &str = "";
pub const DEFAULT_DISPLAY_NAME: &str = "Developer";
#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] #[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
pub struct ExtensionEntry { pub struct ExtensionEntry {
@@ -42,6 +43,7 @@ impl ExtensionManager {
enabled: true, enabled: true,
config: ExtensionConfig::Builtin { config: ExtensionConfig::Builtin {
name: DEFAULT_EXTENSION.to_string(), name: DEFAULT_EXTENSION.to_string(),
display_name: Some(DEFAULT_DISPLAY_NAME.to_string()),
timeout: Some(DEFAULT_EXTENSION_TIMEOUT), timeout: Some(DEFAULT_EXTENSION_TIMEOUT),
}, },
}, },

View File

@@ -7,6 +7,7 @@ pub use base::{Config, ConfigError, APP_STRATEGY};
pub use experiments::ExperimentManager; pub use experiments::ExperimentManager;
pub use extensions::{ExtensionEntry, ExtensionManager}; pub use extensions::{ExtensionEntry, ExtensionManager};
pub use extensions::DEFAULT_DISPLAY_NAME;
pub use extensions::DEFAULT_EXTENSION; pub use extensions::DEFAULT_EXTENSION;
pub use extensions::DEFAULT_EXTENSION_DESCRIPTION; pub use extensions::DEFAULT_EXTENSION_DESCRIPTION;
pub use extensions::DEFAULT_EXTENSION_TIMEOUT; pub use extensions::DEFAULT_EXTENSION_TIMEOUT;

View File

@@ -392,6 +392,10 @@
"type" "type"
], ],
"properties": { "properties": {
"display_name": {
"type": "string",
"nullable": true
},
"name": { "name": {
"type": "string", "type": "string",
"description": "The name used to identify this extension" "description": "The name used to identify this extension"

View File

@@ -43,6 +43,7 @@ export type ExtensionConfig = {
timeout?: number | null; timeout?: number | null;
type: 'stdio'; type: 'stdio';
} | { } | {
display_name?: string | null;
/** /**
* The name used to identify this extension * The name used to identify this extension
*/ */

View File

@@ -118,7 +118,7 @@ export default function ExtensionsSection() {
<div className="flex gap-4 pt-4 w-full"> <div className="flex gap-4 pt-4 w-full">
<Button <Button
className="flex items-center gap-2 flex-1 justify-center text-textSubtle bg-black dark:bg-white hover:bg-subtle" className="flex items-center gap-2 flex-1 justify-center text-white dark:text-textSubtle bg-black dark:bg-white hover:bg-subtle"
onClick={() => setIsAddModalOpen(true)} onClick={() => setIsAddModalOpen(true)}
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />

View File

@@ -1,11 +1,12 @@
import type { ExtensionConfig } from '../../../api/types.gen'; import type { ExtensionConfig } from '../../../api/types.gen';
import builtInExtensionsData from '../../../built-in-extensions.json'; import builtInExtensionsData from './built-in-extensions.json';
import { FixedExtensionEntry } from '@/src/components/ConfigContext'; import { FixedExtensionEntry } from '../../ConfigContext';
// Type definition for built-in extensions from JSON // Type definition for built-in extensions from JSON
type BuiltinExtension = { type BuiltinExtension = {
id: string; id: string;
name: string; name: string;
display_name: string;
description: string; description: string;
enabled: boolean; enabled: boolean;
type: 'builtin'; type: 'builtin';
@@ -50,13 +51,14 @@ export async function syncBuiltInExtensions(
// Check each built-in extension // Check each built-in extension
for (const builtinExt of builtinExtensions) { for (const builtinExt of builtinExtensions) {
// Only add if the extension doesn't already exist // Only add if the extension doesn't already exist -- use the id
if (!existingExtensionKeys.has(builtinExt.id)) { if (!existingExtensionKeys.has(builtinExt.id)) {
console.log(`Adding built-in extension: ${builtinExt.id}`); console.log(`Adding built-in extension: ${builtinExt.id}`);
// Convert to the ExtensionConfig format // Convert to the ExtensionConfig format
const extConfig: ExtensionConfig = { const extConfig: ExtensionConfig = {
name: builtinExt.name, name: builtinExt.name,
display_name: builtinExt.display_name,
type: 'builtin', type: 'builtin',
timeout: builtinExt.timeout ?? 300, timeout: builtinExt.timeout ?? 300,
}; };

View File

@@ -0,0 +1,51 @@
[
{
"id": "developer",
"name": "developer",
"display_name": "Developer",
"description": "General development tools useful for software engineering.",
"enabled": true,
"type": "builtin",
"env_keys": [],
"timeout": 300
},
{
"id": "computercontroller",
"name": "computercontroller",
"display_name": "Computer Controller",
"description": "General computer control tools that don't require you to be a developer or engineer.",
"enabled": false,
"type": "builtin",
"env_keys": [],
"timeout": 300
},
{
"id": "memory",
"name": "memory",
"display_name": "Memory",
"description": "Teach goose your preferences as you go.",
"enabled": false,
"type": "builtin",
"env_keys": [],
"timeout": 300
},
{
"id": "jetbrains",
"display_name": "Jetbrains",
"name": "jetbrains",
"description": "Integration with any Jetbrains IDE",
"enabled": false,
"type": "builtin",
"env_keys": [],
"timeout": 300
},
{
"id": "tutorial",
"name": "tutorial",
"display_name": "Tutorial",
"description": "Access interactive tutorials and guides",
"enabled": false,
"type": "builtin",
"env_keys": []
}
]

View File

@@ -24,7 +24,7 @@ export default function ExtensionItem({ extension, onToggle, onConfigure }: Exte
return ( return (
<div className="rounded-lg border border-borderSubtle p-4 mb-2"> <div className="rounded-lg border border-borderSubtle p-4 mb-2">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h3 className="font-medium text-textStandard">{getFriendlyTitle(extension.name)}</h3> <h3 className="font-medium text-textStandard">{getFriendlyTitle(extension)}</h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Only show config button for non-builtin extensions */} {/* Only show config button for non-builtin extensions */}
{extension.type !== 'builtin' && ( {extension.type !== 'builtin' && (

View File

@@ -28,9 +28,21 @@ export default function ExtensionList({ extensions, onToggle, onConfigure }: Ext
// Helper functions // Helper functions
// Helper function to get a friendly title from extension name // Helper function to get a friendly title from extension name
export function getFriendlyTitle(name: string): string { export function getFriendlyTitle(extension: FixedExtensionEntry): string {
let name = '';
// if it's a builtin, check if there's a display_name (old configs didn't have this field)
if (extension.type === 'builtin' && 'display_name' in extension && extension.display_name) {
// If we have a display_name for a builtin, use it directly
return extension.display_name;
} else {
// For non-builtins or builtins without display_name
name = extension.name;
}
// Format the name to be more readable
return name return name
.split('-') .split(/[-_]/) // Split on hyphens and underscores
.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' '); .join(' ');
} }