feat: configurable extension timeouts via ACP _meta and global default (#8295)
Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -31,7 +31,7 @@ use sacp::schema::{
|
|||||||
ConfigOptionUpdate, Content, ContentBlock, ContentChunk, CurrentModeUpdate, EmbeddedResource,
|
ConfigOptionUpdate, Content, ContentBlock, ContentChunk, CurrentModeUpdate, EmbeddedResource,
|
||||||
EmbeddedResourceResource, FileSystemCapabilities, ImageContent, InitializeRequest,
|
EmbeddedResourceResource, FileSystemCapabilities, ImageContent, InitializeRequest,
|
||||||
InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest,
|
InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest,
|
||||||
LoadSessionResponse, McpCapabilities, McpServer, ModelId, ModelInfo, NewSessionRequest,
|
LoadSessionResponse, McpCapabilities, McpServer, Meta, ModelId, ModelInfo, NewSessionRequest,
|
||||||
NewSessionResponse, PermissionOption, PermissionOptionKind, PromptCapabilities, PromptRequest,
|
NewSessionResponse, PermissionOption, PermissionOptionKind, PromptCapabilities, PromptRequest,
|
||||||
PromptResponse, RequestPermissionOutcome, RequestPermissionRequest, ResourceLink,
|
PromptResponse, RequestPermissionOutcome, RequestPermissionRequest, ResourceLink,
|
||||||
SessionCapabilities, SessionCloseCapabilities, SessionConfigOption,
|
SessionCapabilities, SessionCloseCapabilities, SessionConfigOption,
|
||||||
@@ -90,34 +90,46 @@ pub struct GooseAcpAgent {
|
|||||||
disable_session_naming: bool,
|
disable_session_naming: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_timeout_from_meta(meta: &Option<Meta>) -> Option<u64> {
|
||||||
|
meta.as_ref()
|
||||||
|
.and_then(|m| m.get("timeout"))
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
}
|
||||||
|
|
||||||
fn mcp_server_to_extension_config(mcp_server: McpServer) -> Result<ExtensionConfig, String> {
|
fn mcp_server_to_extension_config(mcp_server: McpServer) -> Result<ExtensionConfig, String> {
|
||||||
match mcp_server {
|
match mcp_server {
|
||||||
McpServer::Stdio(stdio) => Ok(ExtensionConfig::Stdio {
|
McpServer::Stdio(stdio) => {
|
||||||
name: stdio.name,
|
let timeout = extract_timeout_from_meta(&stdio.meta);
|
||||||
description: String::new(),
|
Ok(ExtensionConfig::Stdio {
|
||||||
cmd: stdio.command.to_string_lossy().to_string(),
|
name: stdio.name,
|
||||||
args: stdio.args,
|
description: String::new(),
|
||||||
envs: Envs::new(stdio.env.into_iter().map(|e| (e.name, e.value)).collect()),
|
cmd: stdio.command.to_string_lossy().to_string(),
|
||||||
env_keys: vec![],
|
args: stdio.args,
|
||||||
timeout: None,
|
envs: Envs::new(stdio.env.into_iter().map(|e| (e.name, e.value)).collect()),
|
||||||
bundled: Some(false),
|
env_keys: vec![],
|
||||||
available_tools: vec![],
|
timeout,
|
||||||
}),
|
bundled: Some(false),
|
||||||
McpServer::Http(http) => Ok(ExtensionConfig::StreamableHttp {
|
available_tools: vec![],
|
||||||
name: http.name,
|
})
|
||||||
description: String::new(),
|
}
|
||||||
uri: http.url,
|
McpServer::Http(http) => {
|
||||||
envs: Envs::default(),
|
let timeout = extract_timeout_from_meta(&http.meta);
|
||||||
env_keys: vec![],
|
Ok(ExtensionConfig::StreamableHttp {
|
||||||
headers: http
|
name: http.name,
|
||||||
.headers
|
description: String::new(),
|
||||||
.into_iter()
|
uri: http.url,
|
||||||
.map(|h| (h.name, h.value))
|
envs: Envs::default(),
|
||||||
.collect(),
|
env_keys: vec![],
|
||||||
timeout: None,
|
headers: http
|
||||||
bundled: Some(false),
|
.headers
|
||||||
available_tools: vec![],
|
.into_iter()
|
||||||
}),
|
.map(|h| (h.name, h.value))
|
||||||
|
.collect(),
|
||||||
|
timeout,
|
||||||
|
bundled: Some(false),
|
||||||
|
available_tools: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
McpServer::Sse(_) => Err("SSE is unsupported, migrate to streamable_http".to_string()),
|
McpServer::Sse(_) => Err("SSE is unsupported, migrate to streamable_http".to_string()),
|
||||||
_ => Err("Unknown MCP server type".to_string()),
|
_ => Err("Unknown MCP server type".to_string()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ static RE_ENV_BRACES: Lazy<regex::Regex> =
|
|||||||
static RE_ENV_SIMPLE: Lazy<regex::Regex> =
|
static RE_ENV_SIMPLE: Lazy<regex::Regex> =
|
||||||
Lazy::new(|| regex::Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").expect("valid regex"));
|
Lazy::new(|| regex::Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").expect("valid regex"));
|
||||||
|
|
||||||
|
fn resolve_timeout(timeout: Option<u64>) -> u64 {
|
||||||
|
timeout.unwrap_or_else(|| {
|
||||||
|
Config::global()
|
||||||
|
.get_goose_default_extension_timeout()
|
||||||
|
.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
struct Extension {
|
struct Extension {
|
||||||
pub config: ExtensionConfig,
|
pub config: ExtensionConfig,
|
||||||
/// Resolved config snapshot (with secrets from keyring substituted)
|
/// Resolved config snapshot (with secrets from keyring substituted)
|
||||||
@@ -275,7 +283,7 @@ async fn child_process_client(
|
|||||||
|
|
||||||
let client_result = McpClient::connect_with_container(
|
let client_result = McpClient::connect_with_container(
|
||||||
transport,
|
transport,
|
||||||
Duration::from_secs(timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT)),
|
Duration::from_secs(resolve_timeout(*timeout)),
|
||||||
provider,
|
provider,
|
||||||
docker_container,
|
docker_container,
|
||||||
client_name,
|
client_name,
|
||||||
@@ -441,8 +449,7 @@ async fn create_streamable_http_client(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let timeout_duration =
|
let timeout_duration = Duration::from_secs(resolve_timeout(timeout));
|
||||||
Duration::from_secs(timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT));
|
|
||||||
|
|
||||||
let client_res = McpClient::connect(
|
let client_res = McpClient::connect(
|
||||||
transport,
|
transport,
|
||||||
@@ -636,7 +643,7 @@ impl ExtensionManager {
|
|||||||
(def.client_factory)(context)
|
(def.client_factory)(context)
|
||||||
} else {
|
} else {
|
||||||
// Builtin MCP server extension
|
// Builtin MCP server extension
|
||||||
let timeout_secs = timeout.unwrap_or(crate::config::DEFAULT_EXTENSION_TIMEOUT);
|
let timeout_secs = resolve_timeout(timeout);
|
||||||
let extension_fn =
|
let extension_fn =
|
||||||
get_builtin_extension(normalized_name.as_str()).ok_or_else(|| {
|
get_builtin_extension(normalized_name.as_str()).ok_or_else(|| {
|
||||||
ExtensionError::ConfigError(format!("Unknown extension: {}", name))
|
ExtensionError::ConfigError(format!("Unknown extension: {}", name))
|
||||||
|
|||||||
@@ -1064,6 +1064,7 @@ config_value!(GEMINI3_THINKING_LEVEL, String);
|
|||||||
config_value!(CLAUDE_THINKING_TYPE, String);
|
config_value!(CLAUDE_THINKING_TYPE, String);
|
||||||
config_value!(CLAUDE_THINKING_EFFORT, String);
|
config_value!(CLAUDE_THINKING_EFFORT, String);
|
||||||
config_value!(CLAUDE_THINKING_BUDGET, i32);
|
config_value!(CLAUDE_THINKING_BUDGET, i32);
|
||||||
|
config_value!(GOOSE_DEFAULT_EXTENSION_TIMEOUT, u64);
|
||||||
|
|
||||||
fn find_workspace_or_exe_root() -> Option<PathBuf> {
|
fn find_workspace_or_exe_root() -> Option<PathBuf> {
|
||||||
let exe = std::env::current_exe().ok()?;
|
let exe = std::env::current_exe().ok()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user