diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 445cab4e..986b9e08 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -112,6 +112,38 @@ pub struct SessionOptions { pub container: Option, } +#[derive(Debug, Clone)] +pub struct StreamableHttpOptions { + pub url: String, + pub timeout: u64, +} + +fn parse_streamable_http_extension(input: &str) -> Result { + let mut input_iter = input.split_whitespace(); + let (mut url, mut timeout) = (String::new(), goose::config::DEFAULT_EXTENSION_TIMEOUT); + + if let Some(url_str) = input_iter.next() { + url.push_str(url_str); + } + + for kv_pair in input_iter { + if !kv_pair.contains('=') { + continue; + } + + let (key, value) = kv_pair.split_once('=').unwrap(); + + // We Can have more keys here for setting other properties + if key == "timeout" { + if let Ok(seconds) = value.parse::() { + timeout = seconds; + } + } + } + + Ok(StreamableHttpOptions { url, timeout }) +} + /// Extension configuration options shared between Session and Run commands #[derive(Args, Debug, Clone, Default)] pub struct ExtensionOptions { @@ -128,10 +160,11 @@ pub struct ExtensionOptions { long = "with-streamable-http-extension", value_name = "URL", help = "Add streamable HTTP extensions (can be specified multiple times)", - long_help = "Add streamable HTTP extensions from a URL. Can be specified multiple times. Format: 'url...'", - action = clap::ArgAction::Append + long_help = "Add streamable HTTP extensions from a URL. Can be specified multiple times. Format: 'url...' or 'url... timeout=100' to set up timeout other than default", + action = clap::ArgAction::Append, + value_parser = parse_streamable_http_extension )] - pub streamable_http_extensions: Vec, + pub streamable_http_extensions: Vec, #[arg( long = "with-builtin", diff --git a/crates/goose-cli/src/commands/bench.rs b/crates/goose-cli/src/commands/bench.rs index 7d364fe2..cf682c93 100644 --- a/crates/goose-cli/src/commands/bench.rs +++ b/crates/goose-cli/src/commands/bench.rs @@ -1,3 +1,4 @@ +use crate::cli::StreamableHttpOptions; use crate::session::build_session; use crate::session::SessionBuilderConfig; use crate::{logging, CliSession}; @@ -34,13 +35,21 @@ pub async fn agent_generator( requirements: ExtensionRequirements, session_id: String, ) -> BenchAgent { + let streamable_http_extensions: Vec = requirements + .streamable_http + .iter() + .map(|s| StreamableHttpOptions { + url: s.clone(), + timeout: goose::config::DEFAULT_EXTENSION_TIMEOUT, + }) + .collect(); let base_session = build_session(SessionBuilderConfig { session_id: Some(session_id), resume: false, fork: false, no_session: false, extensions: requirements.external, - streamable_http_extensions: requirements.streamable_http, + streamable_http_extensions, builtins: requirements.builtin, recipe: None, additional_system_prompt: None, diff --git a/crates/goose-cli/src/session/builder.rs b/crates/goose-cli/src/session/builder.rs index 315c4bcb..4a76e657 100644 --- a/crates/goose-cli/src/session/builder.rs +++ b/crates/goose-cli/src/session/builder.rs @@ -1,3 +1,5 @@ +use crate::cli::StreamableHttpOptions; + use super::output; use super::CliSession; use console::style; @@ -87,7 +89,7 @@ pub struct SessionBuilderConfig { /// List of stdio extension commands to add pub extensions: Vec, /// List of streamable HTTP extension commands to add - pub streamable_http_extensions: Vec, + pub streamable_http_extensions: Vec, /// List of builtin extension commands to add pub builtins: Vec, /// Recipe for the session @@ -578,6 +580,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession { ) .await; + if let Err(e) = session .agent .persist_extension_state(&session_id.clone()) @@ -629,7 +632,10 @@ mod tests { fork: false, no_session: false, extensions: vec!["echo test".to_string()], - streamable_http_extensions: vec!["http://localhost:8080/mcp".to_string()], + streamable_http_extensions: vec![StreamableHttpOptions { + url: "http://localhost:8080/mcp".to_string(), + timeout: goose::config::DEFAULT_EXTENSION_TIMEOUT, + }], builtins: vec!["developer".to_string()], recipe: None, additional_system_prompt: Some("Test prompt".to_string()), diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 3d3e6533..7d31aae8 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -9,6 +9,7 @@ mod prompt; mod task_execution_display; mod thinking; +use crate::cli::StreamableHttpOptions; use crate::session::task_execution_display::{ format_task_execution_notification, TASK_EXECUTION_NOTIFICATION_TYPE, }; diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 52983918..576869a0 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -418,9 +418,7 @@ export function AppInner() { window.electron.reactReady(); } catch (error) { console.error('Error sending reactReady:', error); - setFatalError( - `React ready notification failed: ${errorMessage(error, 'Unknown error')}` - ); + setFatalError(`React ready notification failed: ${errorMessage(error, 'Unknown error')}`); } }, []); diff --git a/ui/desktop/src/components/alerts/AlertBox.tsx b/ui/desktop/src/components/alerts/AlertBox.tsx index 9ab80c26..f5723d0d 100644 --- a/ui/desktop/src/components/alerts/AlertBox.tsx +++ b/ui/desktop/src/components/alerts/AlertBox.tsx @@ -90,9 +90,7 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => { } } catch (error) { console.error('Error saving threshold:', error); - window.alert( - `Failed to save threshold: ${errorMessage(error, 'Unknown error')}` - ); + window.alert(`Failed to save threshold: ${errorMessage(error, 'Unknown error')}`); } finally { setIsSaving(false); } diff --git a/ui/desktop/src/components/schedule/SchedulesView.tsx b/ui/desktop/src/components/schedule/SchedulesView.tsx index 9f96c06d..2cea448c 100644 --- a/ui/desktop/src/components/schedule/SchedulesView.tsx +++ b/ui/desktop/src/components/schedule/SchedulesView.tsx @@ -206,9 +206,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { setSchedules(fetchedSchedules); } catch (error) { console.error('Failed to fetch schedules:', error); - setApiError( - errorMessage(error, 'An unknown error occurred while fetching schedules.') - ); + setApiError(errorMessage(error, 'An unknown error occurred while fetching schedules.')); } finally { setIsLoading(false); } @@ -375,8 +373,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { await fetchSchedules(); } catch (error) { console.error(`Failed to kill running job "${id}":`, error); - const errorMsg = - errorMessage(error, `Unknown error killing job "${id}".`); + const errorMsg = errorMessage(error, `Unknown error killing job "${id}".`); setApiError(errorMsg); toastError({ title: 'Kill Job Error', @@ -413,8 +410,7 @@ const SchedulesView: React.FC = ({ onClose: _onClose }) => { } } catch (error) { console.error(`Failed to inspect running job "${id}":`, error); - const errorMsg = - errorMessage(error, `Unknown error inspecting job "${id}".`); + const errorMsg = errorMessage(error, `Unknown error inspecting job "${id}".`); setApiError(errorMsg); toastError({ title: 'Inspect Job Error', diff --git a/ui/desktop/src/components/sessions/SessionHistoryView.tsx b/ui/desktop/src/components/sessions/SessionHistoryView.tsx index 65aa2290..95d781c9 100644 --- a/ui/desktop/src/components/sessions/SessionHistoryView.tsx +++ b/ui/desktop/src/components/sessions/SessionHistoryView.tsx @@ -189,9 +189,7 @@ const SessionHistoryView: React.FC = ({ setIsShareModalOpen(true); } catch (error) { console.error('Error sharing session:', error); - toast.error( - `Failed to share session: ${errorMessage(error, 'Unknown error')}` - ); + toast.error(`Failed to share session: ${errorMessage(error, 'Unknown error')}`); } finally { setIsSharing(false); } diff --git a/ui/desktop/src/components/sessions/SessionListView.tsx b/ui/desktop/src/components/sessions/SessionListView.tsx index 366cab60..b973d732 100644 --- a/ui/desktop/src/components/sessions/SessionListView.tsx +++ b/ui/desktop/src/components/sessions/SessionListView.tsx @@ -476,7 +476,9 @@ const SessionListView: React.FC = React.memo( ); } catch (error) { console.error('Error deleting session:', error); - toast.error(`Failed to delete session "${sessionName}": ${errorMessage(error, 'Unknown error')}`); + toast.error( + `Failed to delete session "${sessionName}": ${errorMessage(error, 'Unknown error')}` + ); } await loadSessions(); }, [sessionToDelete, loadSessions]); diff --git a/ui/desktop/src/utils/autoUpdater.ts b/ui/desktop/src/utils/autoUpdater.ts index 4d428c58..8fae1d75 100644 --- a/ui/desktop/src/utils/autoUpdater.ts +++ b/ui/desktop/src/utils/autoUpdater.ts @@ -247,12 +247,7 @@ export function registerUpdateIpcHandlers() { log.error('Error downloading update:', error); const version = githubUpdateInfo.latestVersion || lastUpdateState?.latestVersion || 'unknown'; const method = isUsingGitHubFallback ? 'github-fallback' : 'electron-updater'; - trackUpdateDownloadCompleted( - false, - version, - method, - errorMessage(error, 'unknown') - ); + trackUpdateDownloadCompleted(false, version, method, errorMessage(error, 'unknown')); return { success: false, error: errorMessage(error, 'Unknown error'), diff --git a/ui/desktop/src/utils/sessionCache.ts b/ui/desktop/src/utils/sessionCache.ts index 1daaf383..36065779 100644 --- a/ui/desktop/src/utils/sessionCache.ts +++ b/ui/desktop/src/utils/sessionCache.ts @@ -58,7 +58,9 @@ export async function loadSession(sessionId: string, forceRefresh = false): Prom return session; } catch (error) { - throw new Error(`Error loading session ${sessionId}: ${errorMessage(error, 'Unknown error')}`); + throw new Error( + `Error loading session ${sessionId}: ${errorMessage(error, 'Unknown error')}` + ); } finally { inFlightRequests.delete(sessionId); }