Jazzcort add timeout (#6772)

Signed-off-by: Jazzcort <jason101011113@gmail.com>
Co-authored-by: Jazzcort <jason101011113@gmail.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-01-28 10:59:20 -05:00
committed by GitHub
parent 4d608690b8
commit a4ad231d60
11 changed files with 68 additions and 30 deletions
+36 -3
View File
@@ -112,6 +112,38 @@ pub struct SessionOptions {
pub container: Option<String>, pub container: Option<String>,
} }
#[derive(Debug, Clone)]
pub struct StreamableHttpOptions {
pub url: String,
pub timeout: u64,
}
fn parse_streamable_http_extension(input: &str) -> Result<StreamableHttpOptions, String> {
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::<u64>() {
timeout = seconds;
}
}
}
Ok(StreamableHttpOptions { url, timeout })
}
/// Extension configuration options shared between Session and Run commands /// Extension configuration options shared between Session and Run commands
#[derive(Args, Debug, Clone, Default)] #[derive(Args, Debug, Clone, Default)]
pub struct ExtensionOptions { pub struct ExtensionOptions {
@@ -128,10 +160,11 @@ pub struct ExtensionOptions {
long = "with-streamable-http-extension", long = "with-streamable-http-extension",
value_name = "URL", value_name = "URL",
help = "Add streamable HTTP extensions (can be specified multiple times)", 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...'", 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 action = clap::ArgAction::Append,
value_parser = parse_streamable_http_extension
)] )]
pub streamable_http_extensions: Vec<String>, pub streamable_http_extensions: Vec<StreamableHttpOptions>,
#[arg( #[arg(
long = "with-builtin", long = "with-builtin",
+10 -1
View File
@@ -1,3 +1,4 @@
use crate::cli::StreamableHttpOptions;
use crate::session::build_session; use crate::session::build_session;
use crate::session::SessionBuilderConfig; use crate::session::SessionBuilderConfig;
use crate::{logging, CliSession}; use crate::{logging, CliSession};
@@ -34,13 +35,21 @@ pub async fn agent_generator(
requirements: ExtensionRequirements, requirements: ExtensionRequirements,
session_id: String, session_id: String,
) -> BenchAgent { ) -> BenchAgent {
let streamable_http_extensions: Vec<StreamableHttpOptions> = requirements
.streamable_http
.iter()
.map(|s| StreamableHttpOptions {
url: s.clone(),
timeout: goose::config::DEFAULT_EXTENSION_TIMEOUT,
})
.collect();
let base_session = build_session(SessionBuilderConfig { let base_session = build_session(SessionBuilderConfig {
session_id: Some(session_id), session_id: Some(session_id),
resume: false, resume: false,
fork: false, fork: false,
no_session: false, no_session: false,
extensions: requirements.external, extensions: requirements.external,
streamable_http_extensions: requirements.streamable_http, streamable_http_extensions,
builtins: requirements.builtin, builtins: requirements.builtin,
recipe: None, recipe: None,
additional_system_prompt: None, additional_system_prompt: None,
+8 -2
View File
@@ -1,3 +1,5 @@
use crate::cli::StreamableHttpOptions;
use super::output; use super::output;
use super::CliSession; use super::CliSession;
use console::style; use console::style;
@@ -87,7 +89,7 @@ pub struct SessionBuilderConfig {
/// List of stdio extension commands to add /// List of stdio extension commands to add
pub extensions: Vec<String>, pub extensions: Vec<String>,
/// List of streamable HTTP extension commands to add /// List of streamable HTTP extension commands to add
pub streamable_http_extensions: Vec<String>, pub streamable_http_extensions: Vec<StreamableHttpOptions>,
/// List of builtin extension commands to add /// List of builtin extension commands to add
pub builtins: Vec<String>, pub builtins: Vec<String>,
/// Recipe for the session /// Recipe for the session
@@ -578,6 +580,7 @@ pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
) )
.await; .await;
if let Err(e) = session if let Err(e) = session
.agent .agent
.persist_extension_state(&session_id.clone()) .persist_extension_state(&session_id.clone())
@@ -629,7 +632,10 @@ mod tests {
fork: false, fork: false,
no_session: false, no_session: false,
extensions: vec!["echo test".to_string()], 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()], builtins: vec!["developer".to_string()],
recipe: None, recipe: None,
additional_system_prompt: Some("Test prompt".to_string()), additional_system_prompt: Some("Test prompt".to_string()),
+1
View File
@@ -9,6 +9,7 @@ mod prompt;
mod task_execution_display; mod task_execution_display;
mod thinking; mod thinking;
use crate::cli::StreamableHttpOptions;
use crate::session::task_execution_display::{ use crate::session::task_execution_display::{
format_task_execution_notification, TASK_EXECUTION_NOTIFICATION_TYPE, format_task_execution_notification, TASK_EXECUTION_NOTIFICATION_TYPE,
}; };
+1 -3
View File
@@ -418,9 +418,7 @@ export function AppInner() {
window.electron.reactReady(); window.electron.reactReady();
} catch (error) { } catch (error) {
console.error('Error sending reactReady:', error); console.error('Error sending reactReady:', error);
setFatalError( setFatalError(`React ready notification failed: ${errorMessage(error, 'Unknown error')}`);
`React ready notification failed: ${errorMessage(error, 'Unknown error')}`
);
} }
}, []); }, []);
@@ -90,9 +90,7 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
} }
} catch (error) { } catch (error) {
console.error('Error saving threshold:', error); console.error('Error saving threshold:', error);
window.alert( window.alert(`Failed to save threshold: ${errorMessage(error, 'Unknown error')}`);
`Failed to save threshold: ${errorMessage(error, 'Unknown error')}`
);
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@@ -206,9 +206,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
setSchedules(fetchedSchedules); setSchedules(fetchedSchedules);
} catch (error) { } catch (error) {
console.error('Failed to fetch schedules:', error); console.error('Failed to fetch schedules:', error);
setApiError( setApiError(errorMessage(error, 'An unknown error occurred while fetching schedules.'));
errorMessage(error, 'An unknown error occurred while fetching schedules.')
);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -375,8 +373,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
await fetchSchedules(); await fetchSchedules();
} catch (error) { } catch (error) {
console.error(`Failed to kill running job "${id}":`, error); console.error(`Failed to kill running job "${id}":`, error);
const errorMsg = const errorMsg = errorMessage(error, `Unknown error killing job "${id}".`);
errorMessage(error, `Unknown error killing job "${id}".`);
setApiError(errorMsg); setApiError(errorMsg);
toastError({ toastError({
title: 'Kill Job Error', title: 'Kill Job Error',
@@ -413,8 +410,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose: _onClose }) => {
} }
} catch (error) { } catch (error) {
console.error(`Failed to inspect running job "${id}":`, error); console.error(`Failed to inspect running job "${id}":`, error);
const errorMsg = const errorMsg = errorMessage(error, `Unknown error inspecting job "${id}".`);
errorMessage(error, `Unknown error inspecting job "${id}".`);
setApiError(errorMsg); setApiError(errorMsg);
toastError({ toastError({
title: 'Inspect Job Error', title: 'Inspect Job Error',
@@ -189,9 +189,7 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
setIsShareModalOpen(true); setIsShareModalOpen(true);
} catch (error) { } catch (error) {
console.error('Error sharing session:', error); console.error('Error sharing session:', error);
toast.error( toast.error(`Failed to share session: ${errorMessage(error, 'Unknown error')}`);
`Failed to share session: ${errorMessage(error, 'Unknown error')}`
);
} finally { } finally {
setIsSharing(false); setIsSharing(false);
} }
@@ -476,7 +476,9 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
); );
} catch (error) { } catch (error) {
console.error('Error deleting session:', 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(); await loadSessions();
}, [sessionToDelete, loadSessions]); }, [sessionToDelete, loadSessions]);
+1 -6
View File
@@ -247,12 +247,7 @@ export function registerUpdateIpcHandlers() {
log.error('Error downloading update:', error); log.error('Error downloading update:', error);
const version = githubUpdateInfo.latestVersion || lastUpdateState?.latestVersion || 'unknown'; const version = githubUpdateInfo.latestVersion || lastUpdateState?.latestVersion || 'unknown';
const method = isUsingGitHubFallback ? 'github-fallback' : 'electron-updater'; const method = isUsingGitHubFallback ? 'github-fallback' : 'electron-updater';
trackUpdateDownloadCompleted( trackUpdateDownloadCompleted(false, version, method, errorMessage(error, 'unknown'));
false,
version,
method,
errorMessage(error, 'unknown')
);
return { return {
success: false, success: false,
error: errorMessage(error, 'Unknown error'), error: errorMessage(error, 'Unknown error'),
+3 -1
View File
@@ -58,7 +58,9 @@ export async function loadSession(sessionId: string, forceRefresh = false): Prom
return session; return session;
} catch (error) { } 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 { } finally {
inFlightRequests.delete(sessionId); inFlightRequests.delete(sessionId);
} }