diff --git a/crates/goose-agent/src/operation.rs b/crates/goose-agent/src/operation.rs index acd27d89e..fc5a83c04 100644 --- a/crates/goose-agent/src/operation.rs +++ b/crates/goose-agent/src/operation.rs @@ -64,9 +64,7 @@ pub fn ends_turn(messages: &[Message]) -> bool { && !last.content.iter().any(|content| { matches!( content, - MessageContent::ToolRequest(_) - | MessageContent::FrontendToolRequest(_) - | MessageContent::ActionRequired(_) + MessageContent::ToolRequest(_) | MessageContent::ActionRequired(_) ) }) }) diff --git a/crates/goose-cli/src/scenario_tests/scenario_runner.rs b/crates/goose-cli/src/scenario_tests/scenario_runner.rs index 7b125cae7..ec23b4fdc 100644 --- a/crates/goose-cli/src/scenario_tests/scenario_runner.rs +++ b/crates/goose-cli/src/scenario_tests/scenario_runner.rs @@ -225,7 +225,6 @@ where }, Arc::new(mock_client), None, - None, ) .await; diff --git a/crates/goose-context-management/src/format.rs b/crates/goose-context-management/src/format.rs index de333746f..cda1f1d3f 100644 --- a/crates/goose-context-management/src/format.rs +++ b/crates/goose-context-management/src/format.rs @@ -57,13 +57,6 @@ pub fn format_message_for_compacting(msg: &Message) -> String { id )), }, - MessageContent::FrontendToolRequest(req) => { - if let Ok(call) = &req.tool_call { - Some(format!("frontend_tool_request: {}", call.name)) - } else { - Some("frontend_tool_request: [error]".to_string()) - } - } MessageContent::Thinking(_) => None, MessageContent::RedactedThinking(_) => None, MessageContent::SystemNotification(notification) => { diff --git a/crates/goose-provider-types/src/conversation.rs b/crates/goose-provider-types/src/conversation.rs index 1ba7cea67..118c5b5ad 100644 --- a/crates/goose-provider-types/src/conversation.rs +++ b/crates/goose-provider-types/src/conversation.rs @@ -476,13 +476,6 @@ fn fix_tool_calling(mut messages: Vec) -> (Vec, Vec) { resp.id )); } - MessageContentBlock::FrontendToolRequest(req) => { - content_to_remove.push(idx); - issues.push(format!( - "Removed frontend tool request '{}' from assistant message", - req.id - )); - } MessageContentBlock::ToolRequest(req) => { pending_tool_requests.insert(req.id.clone()); } diff --git a/crates/goose-provider-types/src/conversation/message.rs b/crates/goose-provider-types/src/conversation/message.rs index 9ad830336..1d5a3cf75 100644 --- a/crates/goose-provider-types/src/conversation/message.rs +++ b/crates/goose-provider-types/src/conversation/message.rs @@ -233,14 +233,6 @@ pub struct RedactedThinkingContentBlock { pub data: String, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FrontendToolRequest { - pub id: String, - #[serde(with = "tool_result_serde")] - pub tool_call: ToolResult, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum SystemNotificationType { @@ -300,7 +292,6 @@ pub enum MessageContentBlock { ToolResponse(ToolResponse), ToolConfirmationRequest(ToolConfirmationRequest), ActionRequired(ActionRequired), - FrontendToolRequest(FrontendToolRequest), Thinking(ThinkingContentBlock), RedactedThinking(RedactedThinkingContentBlock), SystemNotification(SystemNotificationContent), @@ -340,10 +331,6 @@ impl fmt::Display for MessageContentBlock { write!(f, "[ActionRequired: ToolConfirmationResponse for {}]", id) } }, - MessageContentBlock::FrontendToolRequest(r) => match &r.tool_call { - Ok(tool_call) => write!(f, "[FrontendToolRequest: {}]", tool_call.name), - Err(e) => write!(f, "[FrontendToolRequest: Error: {}]", e), - }, MessageContentBlock::Thinking(t) => write!(f, "[Thinking: {}]", t.thinking), MessageContentBlock::RedactedThinking(_r) => write!(f, "[RedactedThinking]"), MessageContentBlock::SystemNotification(r) => { @@ -560,16 +547,6 @@ impl MessageContentBlock { MessageContentBlock::RedactedThinking(RedactedThinkingContentBlock { data: data.into() }) } - pub fn frontend_tool_request>( - id: S, - tool_call: ToolResult, - ) -> Self { - MessageContentBlock::FrontendToolRequest(FrontendToolRequest { - id: id.into(), - tool_call, - }) - } - pub fn system_notification>( notification_type: SystemNotificationType, msg: S, @@ -1102,14 +1079,6 @@ impl Message { )) } - pub fn with_frontend_tool_request>( - self, - id: S, - tool_call: ToolResult, - ) -> Self { - self.with_content(MessageContentBlock::frontend_tool_request(id, tool_call)) - } - /// Add thinking content to the message pub fn with_thinking, S2: Into>( self, diff --git a/crates/goose-provider-types/src/formats/anthropic.rs b/crates/goose-provider-types/src/formats/anthropic.rs index 1eaa064f0..aec33e4d1 100644 --- a/crates/goose-provider-types/src/formats/anthropic.rs +++ b/crates/goose-provider-types/src/formats/anthropic.rs @@ -408,16 +408,6 @@ fn format_messages_with_options( MessageContentBlock::Image(image) => { content.push(convert_image(image, &ImageFormat::Anthropic)); } - MessageContentBlock::FrontendToolRequest(tool_request) => { - if let Ok(tool_call) = &tool_request.tool_call { - content.push(json!({ - TYPE_FIELD: TOOL_USE_TYPE, - ID_FIELD: tool_request.id, - NAME_FIELD: tool_call.name, - INPUT_FIELD: args_to_input_value(tool_call.arguments.clone()) - })); - } - } } } @@ -2102,23 +2092,6 @@ mod tests { assert_eq!(input, &json!({})); } - #[test] - fn test_parameterless_frontend_tool_request_serializes_input_as_empty_object() { - // Same regression as above, but exercises the FrontendToolRequest - // branch which is reached for UI-originated tool calls. - let messages = vec![Message::assistant().with_frontend_tool_request( - "frontend_tool_1", - Ok(CallToolRequestParams::new("list_things")), - )]; - - let spec = format_messages(&messages); - - let input = &spec[0]["content"][0]["input"]; - assert!(input.is_object(), "expected object, got {input:?}"); - assert!(!input.is_null()); - assert_eq!(input, &json!({})); - } - fn cfg(name: &str) -> ModelConfig { ModelConfig::new(name) } diff --git a/crates/goose-provider-types/src/formats/databricks.rs b/crates/goose-provider-types/src/formats/databricks.rs index 917ae7a2c..d45b456ab 100644 --- a/crates/goose-provider-types/src/formats/databricks.rs +++ b/crates/goose-provider-types/src/formats/databricks.rs @@ -244,17 +244,6 @@ fn format_messages( })); } } - MessageContentBlock::FrontendToolRequest(req) => { - let text = match &req.tool_call { - Ok(tool_call) => format!( - "Frontend tool request: {} ({})", - tool_call.name, - serde_json::to_string_pretty(&tool_call.arguments).unwrap() - ), - Err(e) => format!("Frontend tool request error: {}", e), - }; - content_array.push(json!({"type": "text", "text": text})); - } MessageContentBlock::SystemNotification(_) | MessageContentBlock::Error(_) | MessageContentBlock::ToolConfirmationRequest(_) diff --git a/crates/goose-provider-types/src/formats/openai.rs b/crates/goose-provider-types/src/formats/openai.rs index 5ee9b23b9..7233a96e4 100644 --- a/crates/goose-provider-types/src/formats/openai.rs +++ b/crates/goose-provider-types/src/formats/openai.rs @@ -426,39 +426,6 @@ pub fn format_messages_with_options( })); } } - MessageContentBlock::FrontendToolRequest(request) => match &request.tool_call { - Ok(tool_call) => { - let sanitized_name = sanitize_function_name(&tool_call.name); - let arguments_str = match &tool_call.arguments { - Some(args) => { - serde_json::to_string(args).unwrap_or_else(|_| "{}".to_string()) - } - None => "{}".to_string(), - }; - - let tool_calls = converted - .as_object_mut() - .unwrap() - .entry("tool_calls") - .or_insert(json!([])); - - tool_calls.as_array_mut().unwrap().push(json!({ - "id": request.id, - "type": "function", - "function": { - "name": sanitized_name, - "arguments": arguments_str, - } - })); - } - Err(e) => { - output.push(json!({ - "role": "tool", - "content": format!("Error: {}", e), - "tool_call_id": request.id - })); - } - }, } } @@ -3008,58 +2975,6 @@ mod tests { Ok(()) } - #[test] - fn test_format_messages_frontend_tool_request_with_none_arguments() -> anyhow::Result<()> { - // Test that FrontendToolRequest with None arguments are formatted as "{}" string - let message = Message::assistant().with_frontend_tool_request( - "frontend_tool1", - Ok(CallToolRequestParams::new("frontend_test_tool")), - ); - - let spec = format_messages(&[message], &ImageFormat::OpenAi); - - assert_eq!(spec.len(), 1); - assert_eq!(spec[0]["role"], "assistant"); - assert!(spec[0]["tool_calls"].is_array()); - - let tool_call = &spec[0]["tool_calls"][0]; - assert_eq!(tool_call["id"], "frontend_tool1"); - assert_eq!(tool_call["type"], "function"); - assert_eq!(tool_call["function"]["name"], "frontend_test_tool"); - // This should be the string "{}", not null - assert_eq!(tool_call["function"]["arguments"], "{}"); - - Ok(()) - } - - #[test] - fn test_format_messages_frontend_tool_request_with_some_arguments() -> anyhow::Result<()> { - // Test that FrontendToolRequest with Some arguments are properly JSON-serialized - let message = Message::assistant().with_frontend_tool_request( - "frontend_tool1", - Ok(CallToolRequestParams::new("frontend_test_tool") - .with_arguments(object!({"action": "click", "element": "button"}))), - ); - - let spec = format_messages(&[message], &ImageFormat::OpenAi); - - assert_eq!(spec.len(), 1); - assert_eq!(spec[0]["role"], "assistant"); - assert!(spec[0]["tool_calls"].is_array()); - - let tool_call = &spec[0]["tool_calls"][0]; - assert_eq!(tool_call["id"], "frontend_tool1"); - assert_eq!(tool_call["type"], "function"); - assert_eq!(tool_call["function"]["name"], "frontend_test_tool"); - // This should be a JSON string representation - let args_str = tool_call["function"]["arguments"].as_str().unwrap(); - let parsed_args: Value = serde_json::from_str(args_str)?; - assert_eq!(parsed_args["action"], "click"); - assert_eq!(parsed_args["element"], "button"); - - Ok(()) - } - #[test] fn test_format_messages_multiple_text_blocks() -> anyhow::Result<()> { let message = Message::user() diff --git a/crates/goose-provider-types/src/formats/openai_responses.rs b/crates/goose-provider-types/src/formats/openai_responses.rs index e5e1a877b..821c8f7cb 100644 --- a/crates/goose-provider-types/src/formats/openai_responses.rs +++ b/crates/goose-provider-types/src/formats/openai_responses.rs @@ -568,43 +568,6 @@ fn add_message_items(input_items: &mut Vec, messages: &[Message], support } } } - MessageContentBlock::FrontendToolRequest(request) => { - if !text_items.is_empty() { - input_items.push(json!({ - "type": "message", - "role": role, - "content": text_items - })); - text_items = Vec::new(); - } - - match &request.tool_call { - Ok(tool_call) => { - let sanitized_name = sanitize_function_name(&tool_call.name); - let arguments_str = tool_call - .arguments - .as_ref() - .map(|args| { - serde_json::to_string(args).unwrap_or_else(|_| "{}".to_string()) - }) - .unwrap_or_else(|| "{}".to_string()); - - input_items.push(json!({ - "type": "function_call", - "call_id": request.id, - "name": sanitized_name, - "arguments": arguments_str - })); - } - Err(e) => { - input_items.push(json!({ - "type": "function_call_output", - "call_id": request.id, - "output": format!("Error: {}", e.message) - })); - } - } - } _ => {} } } @@ -2956,48 +2919,6 @@ mod tests { Ok(()) } - #[test] - fn test_frontend_tool_request_serialized_in_responses_request() { - use crate::conversation::message::Message; - use rmcp::model::{CallToolResult, ContentBlock}; - - let messages = vec![ - Message::assistant().with_frontend_tool_request( - "call_ft1", - Ok(CallToolRequestParams::new("browser_click") - .with_arguments(object!({"selector": "#btn"}))), - ), - Message::user().with_content(MessageContentBlock::tool_response( - "call_ft1", - Ok(CallToolResult::success(vec![ContentBlock::text("clicked")])), - )), - ]; - - let model_config = ModelConfig { - model_name: "gpt-5.5".to_string(), - context_limit: None, - temperature: None, - max_tokens: None, - toolshim: false, - toolshim_model: None, - request_params: None, - reasoning: None, - supports_vision: None, - request_headers: None, - }; - - let result = create_responses_request(&model_config, "", &messages, &[]).unwrap(); - let input = result["input"].as_array().unwrap(); - - assert_eq!(input[0]["type"], "function_call"); - assert_eq!(input[0]["call_id"], "call_ft1"); - assert_eq!(input[0]["name"], "browser_click"); - - assert_eq!(input[1]["type"], "function_call_output"); - assert_eq!(input[1]["call_id"], "call_ft1"); - assert_eq!(input[1]["output"], "clicked"); - } - #[test] fn test_responses_request_sanitizes_replayed_function_call_names() { use crate::conversation::message::Message; @@ -3008,8 +2929,8 @@ mod tests { Ok(CallToolRequestParams::new("Crack Catcher") .with_arguments(object!({"prompt": "verify the work"}))), ), - Message::assistant().with_frontend_tool_request( - "call_frontend_agent", + Message::assistant().with_tool_request( + "call_review_agent", Ok(CallToolRequestParams::new("@Review Agent") .with_arguments(object!({"prompt": "check it"}))), ), @@ -3036,7 +2957,7 @@ mod tests { assert_eq!(input[0]["name"], "Crack_Catcher"); assert_eq!(input[1]["type"], "function_call"); - assert_eq!(input[1]["call_id"], "call_frontend_agent"); + assert_eq!(input[1]["call_id"], "call_review_agent"); assert_eq!(input[1]["name"], "_Review_Agent"); } @@ -3105,43 +3026,4 @@ mod tests { .unwrap() .contains("invalid arguments")); } - - #[test] - fn test_frontend_tool_request_error_emits_function_call_output() { - use crate::conversation::message::Message; - use rmcp::model::{ErrorCode, ErrorData}; - - let messages = vec![Message::assistant().with_frontend_tool_request( - "call_ft_err", - Err(ErrorData { - code: ErrorCode::INTERNAL_ERROR, - message: "malformed arguments".into(), - data: None, - }), - )]; - - let model_config = ModelConfig { - model_name: "gpt-5.5".to_string(), - context_limit: None, - temperature: None, - max_tokens: None, - toolshim: false, - toolshim_model: None, - request_params: None, - reasoning: None, - supports_vision: None, - request_headers: None, - }; - - let result = create_responses_request(&model_config, "", &messages, &[]).unwrap(); - let input = result["input"].as_array().unwrap(); - - assert_eq!(input.len(), 1); - assert_eq!(input[0]["type"], "function_call_output"); - assert_eq!(input[0]["call_id"], "call_ft_err"); - assert!(input[0]["output"] - .as_str() - .unwrap() - .contains("malformed arguments")); - } } diff --git a/crates/goose-provider-types/src/formats/snowflake.rs b/crates/goose-provider-types/src/formats/snowflake.rs index 348f35d5a..5913a8c17 100644 --- a/crates/goose-provider-types/src/formats/snowflake.rs +++ b/crates/goose-provider-types/src/formats/snowflake.rs @@ -74,9 +74,6 @@ pub fn format_messages(messages: &[Message]) -> Vec { // Skip redacted thinking for now } MessageContentBlock::Image(_) => continue, // Snowflake doesn't support image content yet - MessageContentBlock::FrontendToolRequest(_tool_request) => { - // Skip frontend tool requests - } } } diff --git a/crates/goose/src/acp/provider.rs b/crates/goose/src/acp/provider.rs index f6e14498a..43fd24ec5 100644 --- a/crates/goose/src/acp/provider.rs +++ b/crates/goose/src/acp/provider.rs @@ -1875,9 +1875,6 @@ pub fn extension_configs_to_mcp_servers(configs: &[ExtensionConfig]) -> Vec { - tracing::debug!(name, "skipping SSE extension, migrate to streamable_http"); - } _ => {} } } @@ -4276,17 +4273,6 @@ mod tests { } } - #[test] - fn test_sse_skips() { - let config = ExtensionConfig::Sse { - name: "test-sse".into(), - description: String::new(), - uri: Some("https://example.com/sse".into()), - }; - let result = extension_configs_to_mcp_servers(&[config]); - assert!(result.is_empty()); - } - #[test] fn test_filter_supported_servers_skips_http_without_capability() { let config = ExtensionConfig::StreamableHttp { diff --git a/crates/goose/src/acp/server.rs b/crates/goose/src/acp/server.rs index a789c5ade..7e0338e3a 100644 --- a/crates/goose/src/acp/server.rs +++ b/crates/goose/src/acp/server.rs @@ -1120,7 +1120,7 @@ impl GooseAcpAgent { agent .extension_manager - .add_client("developer".into(), developer_config, client, info, None) + .add_client("developer".into(), developer_config, client, info) .await; } diff --git a/crates/goose/src/acp/server/extensions.rs b/crates/goose/src/acp/server/extensions.rs index 423b540ac..56eb9449b 100644 --- a/crates/goose/src/acp/server/extensions.rs +++ b/crates/goose/src/acp/server/extensions.rs @@ -231,9 +231,6 @@ fn config_to_goose_extension( available_tools: available_tools_to_wire(available_tools), } } - ExtensionConfig::Frontend { .. } - | ExtensionConfig::InlinePython { .. } - | ExtensionConfig::Sse { .. } => return Ok(None), }; Ok(Some(extension)) } @@ -630,64 +627,6 @@ mod tests { assert_eq!(http.headers[0].value, "Bearer ${API_TOKEN}"); } - #[test] - fn inline_python_config_is_skipped() { - let config = ExtensionConfig::InlinePython { - name: "python-tools".to_string(), - description: "Python tools".to_string(), - code: "print('hello')".to_string(), - timeout: Some(12), - dependencies: Some(vec!["requests".to_string()]), - available_tools: vec!["fetch".to_string()], - }; - - let extension = config_to_goose_extension(&config).expect("conversion should succeed"); - - assert!(extension.is_none()); - } - - #[test] - fn frontend_config_is_skipped() { - let tool = rmcp::model::Tool::new( - "pick_color", - "Pick a color", - serde_json::json!({ - "type": "object", - "properties": { - "hex": { "type": "string" } - } - }) - .as_object() - .expect("schema should be object") - .clone(), - ); - let config = ExtensionConfig::Frontend { - name: "frontend-tools".to_string(), - description: "Frontend tools".to_string(), - tools: vec![tool], - instructions: Some("Use frontend tools carefully".to_string()), - bundled: None, - available_tools: vec!["pick_color".to_string()], - }; - - let extension = config_to_goose_extension(&config).expect("conversion should succeed"); - - assert!(extension.is_none()); - } - - #[test] - fn sse_config_is_skipped() { - let config = ExtensionConfig::Sse { - name: "legacy-sse".to_string(), - description: "Legacy SSE".to_string(), - uri: Some("https://example.com/sse".to_string()), - }; - - let extension = config_to_goose_extension(&config).expect("conversion should succeed"); - - assert!(extension.is_none()); - } - #[test] fn goose_mcp_stdio_extension_converts_to_config_without_literal_envs() { let extension = GooseExtension::Mcp { diff --git a/crates/goose/src/acp/server/recipe/conversions.rs b/crates/goose/src/acp/server/recipe/conversions.rs index 82b183529..1585177ec 100644 --- a/crates/goose/src/acp/server/recipe/conversions.rs +++ b/crates/goose/src/acp/server/recipe/conversions.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Result}; +use anyhow::Result; use goose_sdk_types::custom_requests::{ RecipeAuthorDto, RecipeDto, RecipeExtensionDto, RecipeParameterDto, RecipeParameterInputTypeDto, RecipeParameterRequirementDto, RecipeResponseDto, @@ -453,17 +453,10 @@ impl TryFrom for RecipeExtensionDto { bundled, available_tools: available_tools_to_wire(available_tools), }, - ExtensionConfig::Sse { .. } => bail_unsupported_extension("sse")?, - ExtensionConfig::Frontend { .. } => bail_unsupported_extension("frontend")?, - ExtensionConfig::InlinePython { .. } => bail_unsupported_extension("inline_python")?, }) } } -fn bail_unsupported_extension(extension_type: &str) -> Result { - bail!("recipe extension type `{extension_type}` is not supported by RecipeDto") -} - fn available_tools_to_wire(available_tools: Vec) -> Option> { if available_tools.is_empty() { None @@ -640,34 +633,4 @@ mod tests { assert_eq!(serialized["extensions"][2]["envs"]["REMOTE_MODE"], "true"); assert_eq!(serialized["extensions"][2]["available_tools"][0], "fetch"); } - - #[test] - fn recipe_dto_rejects_unsupported_internal_extension_variants() { - let recipe = Recipe { - version: "1.0.0".to_string(), - title: "Unsupported Extension".to_string(), - description: "Uses an unsupported recipe extension".to_string(), - instructions: Some("Run".to_string()), - prompt: None, - extensions: Some(vec![ExtensionConfig::InlinePython { - name: "inline".to_string(), - description: "Inline Python".to_string(), - code: "print('hello')".to_string(), - timeout: Some(30), - dependencies: None, - available_tools: Vec::new(), - }]), - settings: None, - activities: None, - author: None, - parameters: None, - response: None, - sub_recipes: None, - retry: None, - }; - - let err = RecipeDto::try_from(recipe).unwrap_err().to_string(); - assert!(err.contains("inline_python")); - assert!(err.contains("not supported")); - } } diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index f8b31069d..467c815c5 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -38,8 +38,8 @@ use crate::agents::state_machine::{ ToolExecutionOperation, ToolPairCompactionOperation, UnknownToolOperation, MAX_TURNS_MESSAGE, }; use crate::agents::types::{ - FrontendTool, SessionConfig, SharedProvider, ToolResultReceiver, - DEFAULT_ON_FAILURE_TIMEOUT_SECONDS, DEFAULT_RETRY_TIMEOUT_SECONDS, + SessionConfig, SharedProvider, DEFAULT_ON_FAILURE_TIMEOUT_SECONDS, + DEFAULT_RETRY_TIMEOUT_SECONDS, }; use crate::agents::AgentEvent; use crate::config::extensions::name_to_key; @@ -50,12 +50,11 @@ use crate::context_mgmt::{ }; use crate::conversation::message::{ ActionRequiredData, InferenceMetadata, Message, MessageContent, MessageUsage, ProviderMetadata, - SystemNotificationType, ToolRequest, + SystemNotificationType, }; use crate::conversation::{ debug_conversation_fix, fix_conversation, merge_consecutive_messages_for_request, Conversation, }; -use crate::mcp_utils::ToolResult; use crate::permission::permission_inspector::PermissionInspector; use crate::permission::permission_judge::PermissionCheckResult; use crate::permission::PermissionConfirmation; @@ -89,7 +88,6 @@ const COMPACTION_PROGRESS_TEXT: &str = "goose is compacting the conversation..." const MAX_EMPTY_TURN_RETRIES: u32 = 3; const EMPTY_TURN_MESSAGE: &str = "The model returned an empty response. Please resend your message to continue."; -const DEFAULT_FRONTEND_INSTRUCTIONS: &str = "The following tools are provided directly by the frontend and will be executed by the frontend when called."; fn provider_creation_error(error: anyhow::Error, context: impl fmt::Display) -> anyhow::Error { let message = format!("{context}: {error}"); @@ -189,12 +187,6 @@ pub struct ReplyContext { pub model_config: goose_providers::model::ModelConfig, } -pub struct ToolCategorizeResult { - pub frontend_requests: Vec, - pub remaining_requests: Vec, - pub filtered_response: Message, -} - #[derive(Debug, Clone, serde::Serialize)] pub struct ExtensionLoadResult { pub name: String, @@ -294,13 +286,8 @@ pub struct Agent { pub extension_manager: Arc, pub(super) final_output_tool: Arc>>, - pub(super) frontend_extensions: Mutex>, - pub(super) frontend_tools: Mutex>, - pub(super) frontend_instructions: Mutex>, pub(super) prompt_manager: Mutex, pub tool_confirmation_router: ToolConfirmationRouter, - pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult)>, - pub(super) tool_result_rx: ToolResultReceiver, pub(super) retry_manager: RetryManager, pub(super) tool_inspection_manager: ToolInspectionManager, @@ -418,7 +405,6 @@ impl Agent { } pub fn with_config(config: AgentConfig) -> Self { - let (tool_tx, tool_rx) = mpsc::channel(32); let provider = Arc::new(Mutex::new(None)); let goose_platform = config.goose_platform.clone(); @@ -461,13 +447,8 @@ impl Agent { use_login_shell_path, )), final_output_tool: Arc::new(Mutex::new(None)), - frontend_extensions: Mutex::new(HashMap::new()), - frontend_tools: Mutex::new(HashMap::new()), - frontend_instructions: Mutex::new(None), prompt_manager: Mutex::new(PromptManager::new()), tool_confirmation_router: ToolConfirmationRouter::new(), - tool_result_tx: tool_tx, - tool_result_rx: Arc::new(Mutex::new(tool_rx)), retry_manager: RetryManager::new(), tool_inspection_manager: Self::create_tool_inspection_manager( permission_manager, @@ -919,25 +900,6 @@ impl Agent { }) } - async fn categorize_tools( - &self, - response: &Message, - tools: &[rmcp::model::Tool], - toolshim_tools: &[rmcp::model::Tool], - suppress_replayed_thinking: bool, - ) -> ToolCategorizeResult { - // Categorize tool requests - let (frontend_requests, remaining_requests, filtered_response) = self - .categorize_tool_requests(response, tools, toolshim_tools, suppress_replayed_thinking) - .await; - - ToolCategorizeResult { - frontend_requests, - remaining_requests, - filtered_response, - } - } - async fn handle_approved_and_denied_tools( &self, permission_check_result: &PermissionCheckResult, @@ -1051,112 +1013,6 @@ impl Agent { self.container.lock().await.clone() } - /// Check if a tool is a frontend tool - pub async fn is_frontend_tool(&self, name: &str) -> bool { - self.frontend_tools.lock().await.contains_key(name) - } - - /// Get a reference to a frontend tool - pub async fn get_frontend_tool(&self, name: &str) -> Option { - self.frontend_tools.lock().await.get(name).cloned() - } - - async fn frontend_extension_configs(&self) -> Vec { - let mut configs = self - .frontend_extensions - .lock() - .await - .values() - .cloned() - .collect::>(); - configs.sort_by_key(|config| config.key()); - configs - } - - async fn frontend_tools_for_extension(&self, extension_name: Option<&str>) -> Vec { - let requested_extension = extension_name.map(name_to_key); - - self.frontend_extension_configs() - .await - .into_iter() - .filter_map(|config| { - let include = requested_extension - .as_ref() - .is_none_or(|name| *name == config.key()); - - match config { - ExtensionConfig::Frontend { tools, .. } if include => Some(tools), - _ => None, - } - }) - .flatten() - .collect() - } - - async fn rebuild_frontend_derived_state(&self, extensions: &HashMap) { - let multiple = extensions.len() > 1; - let mut tools = HashMap::new(); - let mut instructions = Vec::new(); - - for config in extensions.values() { - if let ExtensionConfig::Frontend { - name, - tools: ext_tools, - instructions: ext_instructions, - .. - } = config - { - for tool in ext_tools { - let tool_name = tool.name.to_string(); - tools.insert( - tool_name.clone(), - FrontendTool { - name: tool_name, - tool: tool.clone(), - }, - ); - } - - let text = ext_instructions - .clone() - .unwrap_or_else(|| DEFAULT_FRONTEND_INSTRUCTIONS.to_string()); - instructions.push(if multiple { - format!("{name}: {text}") - } else { - text - }); - } - } - - *self.frontend_tools.lock().await = tools; - *self.frontend_instructions.lock().await = if instructions.is_empty() { - None - } else { - Some(instructions.join("\n\n")) - }; - } - - async fn insert_frontend_extension(&self, extension: ExtensionConfig) { - let mut extensions = self.frontend_extensions.lock().await; - extensions.insert(extension.key(), extension); - self.rebuild_frontend_derived_state(&extensions).await; - } - - async fn remove_frontend_extension_by_key(&self, key: &str) -> bool { - let mut extensions = self.frontend_extensions.lock().await; - let removed = extensions.remove(key).is_some(); - if removed { - self.rebuild_frontend_derived_state(&extensions).await; - } - removed - } - - async fn extension_configs_for_persistence(&self) -> Vec { - let mut extension_configs = self.extension_manager.get_extension_configs().await; - extension_configs.extend(self.frontend_extension_configs().await); - extension_configs - } - pub async fn add_final_output_tool(&self, response: Response) -> Result<()> { let mut final_output_tool = self.final_output_tool.lock().await; let created_final_output_tool = @@ -1297,30 +1153,22 @@ impl Agent { ); debug!("WAITING_TOOL_START: {}", tool_call.name); - let result: ToolCallResult = if self.is_frontend_tool(&tool_call.name).await { - ToolCallResult::from(Err(ErrorData::new( - ErrorCode::INTERNAL_ERROR, - "Frontend tool execution required".to_string(), - None, - ))) - } else { - let result = self - .extension_manager - .dispatch_tool_call( - &ctx, - tool_call.clone(), - cancellation_token.unwrap_or_default(), - ) - .await; - result.unwrap_or_else(|error_data| { - #[cfg(feature = "telemetry")] - crate::posthog::emit_error( - "tool_execution_failed", - &format!("{}: {}", tool_call.name, error_data), - ); - ToolCallResult::from(Err(error_data)) - }) - }; + let result = self + .extension_manager + .dispatch_tool_call( + &ctx, + tool_call.clone(), + cancellation_token.unwrap_or_default(), + ) + .await; + let result = result.unwrap_or_else(|error_data| { + #[cfg(feature = "telemetry")] + crate::posthog::emit_error( + "tool_execution_failed", + &format!("{}: {}", tool_call.name, error_data), + ); + ToolCallResult::from(Err(error_data)) + }); debug!("WAITING_TOOL_END: {}", tool_call.name); @@ -1332,7 +1180,7 @@ impl Agent { /// Should be called after any extension add/remove operation pub async fn save_extension_state(&self, session: &SessionConfig) -> Result<()> { let extensions_state = - EnabledExtensionsState::new(self.extension_configs_for_persistence().await); + EnabledExtensionsState::new(self.extension_manager.get_extension_configs().await); let session_manager = self.config.session_manager.clone(); let mut session_data = session_manager.get_session(&session.id, false).await?; @@ -1353,8 +1201,11 @@ impl Agent { /// Save current extension state to session by session_id pub async fn persist_extension_state(&self, session_id: &str) -> Result<()> { - self.persist_extension_configs(session_id, self.extension_configs_for_persistence().await) - .await + self.persist_extension_configs( + session_id, + self.extension_manager.get_extension_configs().await, + ) + .await } /// Save the provided extension configuration to session metadata. @@ -1528,41 +1379,30 @@ impl Agent { .into_iter() .map(|config| { let ext_manager = Arc::clone(&self.extension_manager); - let agent = Arc::clone(self); let working_dir = working_dir.clone(); let container = container.clone(); let sid = session_id.to_string(); async move { let name = config.name().to_string(); - match &config { - ExtensionConfig::Frontend { .. } => { - agent.insert_frontend_extension(config.clone()).await; + match ext_manager + .add_extension(config, working_dir, container.as_ref(), Some(&sid)) + .await + { + Ok(_) => ExtensionLoadResult { + name, + success: true, + error: None, + }, + Err(e) => { + let error = e.to_string(); + warn!("Failed to load extension {}: {}", name, error); ExtensionLoadResult { name, - success: true, - error: None, + success: false, + error: Some(error), } } - _ => match ext_manager - .add_extension(config, working_dir, container.as_ref(), Some(&sid)) - .await - { - Ok(_) => ExtensionLoadResult { - name, - success: true, - error: None, - }, - Err(e) => { - let error = e.to_string(); - warn!("Failed to load extension {}: {}", name, error); - ExtensionLoadResult { - name, - success: false, - error: Some(error), - } - } - }, } } }) @@ -1593,39 +1433,23 @@ impl Agent { })?; let working_dir = Some(session.working_dir); - match &extension { - ExtensionConfig::Frontend { .. } => { - self.insert_frontend_extension(extension.clone()).await; - } - _ => { - let container = self.container.lock().await; - self.extension_manager - .add_extension( - extension.clone(), - working_dir, - container.as_ref(), - Some(session_id), - ) - .await?; - } - } + let container = self.container.lock().await; + self.extension_manager + .add_extension(extension, working_dir, container.as_ref(), Some(session_id)) + .await?; Ok(()) } pub async fn list_tools(&self, session_id: &str, extension_name: Option) -> Vec { + let include_final_output = extension_name.is_none(); let mut prefixed_tools = self .extension_manager - .get_prefixed_tools(session_id, extension_name.clone()) + .get_prefixed_tools(session_id, extension_name) .await .unwrap_or_default(); - prefixed_tools.extend( - self.frontend_tools_for_extension(extension_name.as_deref()) - .await, - ); - - if extension_name.is_none() { + if include_final_output { if let Some(final_output_tool) = self.final_output_tool.lock().await.as_ref() { prefixed_tools.push(final_output_tool.tool()); } @@ -1655,7 +1479,6 @@ impl Agent { } self.extension_manager.remove_extension_by_key(key).await?; - self.remove_frontend_extension_by_key(key).await; // Persist extension state after successful removal self.persist_extension_state(session_id) @@ -1669,22 +1492,14 @@ impl Agent { } pub async fn list_extensions(&self) -> Vec { - let mut extensions = self - .extension_manager + self.extension_manager .list_extensions() .await - .expect("Failed to list extensions"); - extensions.extend( - self.frontend_extension_configs() - .await - .into_iter() - .map(|config| config.name()), - ); - extensions + .expect("Failed to list extensions") } pub async fn get_extension_configs(&self) -> Vec { - self.extension_configs_for_persistence().await + self.extension_manager.get_extension_configs().await } /// Handle a confirmation response for a tool request @@ -1817,7 +1632,6 @@ impl Agent { goose_mode: &self.current_goose_mode, prompt_manager: &self.prompt_manager, tool_inspection_manager: &self.tool_inspection_manager, - frontend_instructions: &self.frontend_instructions, context_limit, }; let status_operation = @@ -2707,18 +2521,13 @@ impl Agent { } }); - let ToolCategorizeResult { - frontend_requests, - remaining_requests, - filtered_response, - } = self - .categorize_tools( + let (tool_requests, filtered_response) = self + .categorize_tool_requests( &response, &tools, &toolshim_tools, surfaced_thinking_in_turn, - ) - .await; + ); let filtered_response = if let Some(inference) = inference.as_ref() { filtered_response.with_inference(inference.clone()) @@ -2748,8 +2557,7 @@ impl Agent { tokio::task::yield_now().await; } - let num_tool_requests = frontend_requests.len() + remaining_requests.len(); - if num_tool_requests == 0 { + if tool_requests.is_empty() { let text = if response.is_user_visible() { filtered_response .user_visible_content() @@ -2766,25 +2574,13 @@ impl Agent { let mut request_to_response_map = HashMap::new(); let mut request_metadata: HashMap> = HashMap::new(); - for request in frontend_requests.iter().chain(remaining_requests.iter()) { + for request in &tool_requests { request_to_response_map.insert(request.id.clone(), Message::user().with_generated_id()); request_metadata.insert(request.id.clone(), request.metadata.clone()); } - for request in frontend_requests.iter() { - let response_msg = request_to_response_map.get_mut(&request.id) - .ok_or_else(|| anyhow::anyhow!("missing response entry for request {}", request.id))?; - let mut frontend_tool_stream = self.handle_frontend_tool_request( - request, - response_msg, - ); - - while let Some(msg) = frontend_tool_stream.try_next().await? { - yield AgentEvent::Message(msg); - } - } if goose_mode == GooseMode::Chat { - for request in remaining_requests.iter() { + for request in &tool_requests { // An unparseable tool call should surface the parse error // (added in the Err branch below), not a successful skip — // otherwise the model sees a malformed call as "skipped OK" @@ -2805,7 +2601,7 @@ impl Agent { let inspection_results = self.tool_inspection_manager .inspect_tools( &session_config.id, - &remaining_requests, + &tool_requests, conversation.messages(), goose_mode, ) @@ -2813,7 +2609,7 @@ impl Agent { let permission_check_result = self.tool_inspection_manager .process_inspection_results_with_permission_inspector( - &remaining_requests, + &tool_requests, &inspection_results, ) .unwrap_or_else(|| { @@ -2822,13 +2618,13 @@ impl Agent { needs_approval: vec![], denied: vec![], }; - result.needs_approval.extend(remaining_requests.iter().cloned()); + result.needs_approval.extend(tool_requests.iter().cloned()); result }); // Track extension requests let mut enable_extension_request_ids = vec![]; - for request in &remaining_requests { + for request in &tool_requests { if let Ok(tool_call) = &request.tool_call { if tool_call.name == MANAGE_EXTENSIONS_TOOL_NAME_COMPLETE { enable_extension_request_ids.push(request.id.clone()); @@ -3060,15 +2856,14 @@ impl Agent { let carrier_tool_call_id = if has_existing_message_id_carrier { None } else { - remaining_requests + tool_requests .first() - .or_else(|| frontend_requests.first()) .map(|request| request.id.as_str()) }; preferred_turn_usage_message_id = Some(response_message_id.to_owned()); - for request in frontend_requests.iter().chain(remaining_requests.iter()) { + for request in &tool_requests { let mut request_msg = if carrier_tool_call_id == Some(request.id.as_str()) { Message::assistant().with_id(response_message_id) @@ -3942,7 +3737,7 @@ impl Agent { .extension_manager .get_prefixed_tools(session_id, None) .await?; - let tools_info = tools + let tools_info: Vec<_> = tools .into_iter() .map(|tool| { ToolInfo::new( @@ -3957,15 +3752,10 @@ impl Agent { }) .collect(); - let plan_prompt = self.extension_manager.get_planning_prompt(tools_info).await; - - Ok(plan_prompt) - } - - pub async fn handle_tool_result(&self, id: String, result: ToolResult) { - if let Err(e) = self.tool_result_tx.send((id, result)).await { - error!("Failed to send tool result: {}", e); - } + let context = HashMap::from([("tools", serde_json::to_value(tools_info)?)]); + Ok(crate::prompt_template::render_template( + "plan.md", &context, + )?) } pub async fn create_recipe( @@ -3995,7 +3785,6 @@ impl Agent { let system_prompt = prompt_manager .builder() .with_extensions(extensions_info.into_iter()) - .with_frontend_instructions(self.frontend_instructions.lock().await.clone()) .with_goose_mode(goose_mode) .build(); diff --git a/crates/goose/src/agents/extension.rs b/crates/goose/src/agents/extension.rs index b9c1aa511..809b470ac 100644 --- a/crates/goose/src/agents/extension.rs +++ b/crates/goose/src/agents/extension.rs @@ -4,7 +4,6 @@ use crate::config; use crate::config::extensions::name_to_key; use crate::config::permission::PermissionLevel; use crate::config::Config; -use rmcp::model::Tool; use rmcp::service::ClientInitializeError; use rmcp::ServiceError as ClientError; use serde::Deserializer; @@ -160,17 +159,6 @@ impl Envs { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[serde(tag = "type")] pub enum ExtensionConfig { - /// SSE transport is no longer supported - kept only for config file compatibility - #[serde(rename = "sse")] - Sse { - #[serde(default)] - name: String, - #[serde(default)] - #[serde(deserialize_with = "deserialize_null_with_default")] - description: String, - #[serde(default)] - uri: Option, - }, /// Standard I/O client with command and arguments #[serde(rename = "stdio")] Stdio { @@ -275,43 +263,6 @@ pub enum ExtensionConfig { #[serde(skip_serializing_if = "Vec::is_empty")] available_tools: Vec, }, - /// Frontend-provided tools that will be called through the frontend - #[serde(rename = "frontend")] - Frontend { - /// The name used to identify this extension - name: String, - #[serde(default)] - #[serde(deserialize_with = "deserialize_null_with_default")] - description: String, - /// The tools provided by the frontend - tools: Vec, - /// Instructions for how to use these tools - instructions: Option, - #[serde(default)] - bundled: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] - available_tools: Vec, - }, - /// Inline Python code that will be executed using uvx - #[serde(rename = "inline_python")] - InlinePython { - /// The name used to identify this extension - name: String, - #[serde(default)] - #[serde(deserialize_with = "deserialize_null_with_default")] - description: String, - /// The Python code to execute - code: String, - /// Timeout in seconds - timeout: Option, - /// Python package dependencies required by this extension - #[serde(default)] - dependencies: Option>, - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] - available_tools: Vec, - }, } impl Default for ExtensionConfig { @@ -371,22 +322,6 @@ impl ExtensionConfig { } } - pub fn inline_python, T: Into>( - name: S, - code: S, - description: S, - timeout: T, - ) -> Self { - Self::InlinePython { - name: name.into(), - code: code.into(), - description: description.into(), - timeout: Some(timeout.into()), - dependencies: None, - available_tools: Vec::new(), - } - } - pub fn with_args(self, args: I) -> Self where I: IntoIterator, @@ -426,13 +361,10 @@ impl ExtensionConfig { pub fn name(&self) -> String { match self { - Self::Sse { name, .. } => name, Self::StreamableHttp { name, .. } => name, Self::Stdio { name, .. } => name, Self::Builtin { name, .. } => name, Self::Platform { name, .. } => name, - Self::Frontend { name, .. } => name, - Self::InlinePython { name, .. } => name, } .to_string() } @@ -440,7 +372,6 @@ impl ExtensionConfig { /// Check if a tool should be available to the LLM pub fn is_tool_available(&self, tool_name: &str) -> bool { let available_tools = match self { - Self::Sse { .. } => return false, // SSE is unsupported Self::StreamableHttp { available_tools, .. } @@ -452,12 +383,6 @@ impl ExtensionConfig { } | Self::Platform { available_tools, .. - } - | Self::InlinePython { - available_tools, .. - } - | Self::Frontend { - available_tools, .. } => available_tools, }; @@ -554,9 +479,6 @@ impl ExtensionConfig { impl std::fmt::Display for ExtensionConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ExtensionConfig::Sse { name, .. } => { - write!(f, "SSE({}: unsupported)", name) - } ExtensionConfig::StreamableHttp { name, uri, socket, .. } => { @@ -573,12 +495,6 @@ impl std::fmt::Display for ExtensionConfig { } ExtensionConfig::Builtin { name, .. } => write!(f, "Builtin({})", name), ExtensionConfig::Platform { name, .. } => write!(f, "Platform({})", name), - ExtensionConfig::Frontend { name, tools, .. } => { - write!(f, "Frontend({}: {} tools)", name, tools.len()) - } - ExtensionConfig::InlinePython { name, code, .. } => { - write!(f, "InlinePython({}: {} chars)", name, code.len()) - } } } } diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index d3d596cf6..daeaa8f17 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -17,7 +17,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Weak}; use std::task::{Context, Poll}; use std::time::Duration; -use tempfile::{tempdir, TempDir}; +#[cfg(test)] +use tempfile::tempdir; use tokio::io::AsyncReadExt; use tokio::process::Command; use tokio::sync::{mpsc, Mutex}; @@ -28,7 +29,7 @@ use tracing::{error, warn}; use super::container::Container; use super::extension::{ ExtensionConfig, ExtensionError, ExtensionInfo, ExtensionResult, PlatformExtensionContext, - ToolInfo, PLATFORM_EXTENSIONS, + PLATFORM_EXTENSIONS, }; use super::tool_execution::{ToolCallContext, ToolCallNotificationEmitter, ToolCallResult}; use super::types::SharedProvider; @@ -45,7 +46,6 @@ use crate::config::{get_all_extensions, Config}; use crate::oauth::{ oauth_flow, oauth_flow_with_challenge, GooseCredentialStore, StaticOAuthClientConfig, }; -use crate::prompt_template; use crate::subprocess::spawn_long_lived_mcp_subprocess; use rmcp::model::{ CallToolRequestParams, CallToolResult, ContentBlock, ErrorCode, ErrorData, GetPromptResult, @@ -131,7 +131,6 @@ struct Extension { client: McpClientBox, server_info: Option, - _temp_dir: Option, } impl Extension { @@ -140,14 +139,12 @@ impl Extension { resolved_config: ExtensionConfig, client: McpClientBox, server_info: Option, - temp_dir: Option, ) -> Self { Self { client, config, resolved_config, server_info, - _temp_dir: temp_dir, } } @@ -1463,19 +1460,12 @@ impl ExtensionManager { ); } - let mut temp_dir = None; - let effective_working_dir = working_dir .clone() .or_else(|| std::env::var("GOOSE_WORKING_DIR").ok().map(PathBuf::from)) .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()); let client: Box = match &config { - ExtensionConfig::Sse { .. } => { - return Err(ExtensionError::ConfigError( - "SSE is unsupported, migrate to streamable_http".to_string(), - )); - } ExtensionConfig::StreamableHttp { uri, timeout, @@ -1668,46 +1658,6 @@ impl ExtensionManager { .await?; Box::new(client) } - ExtensionConfig::InlinePython { - name, - code, - timeout, - dependencies, - .. - } => { - let dir = tempdir()?; - let file_path = dir.path().join(format!("{}.py", name)); - temp_dir = Some(dir); - std::fs::write(&file_path, code)?; - - let command = Command::new("uvx").configure(|command| { - command.arg("--with").arg("mcp"); - dependencies.iter().flatten().for_each(|dep| { - command.arg("--with").arg(dep); - }); - command.arg("python").arg(file_path.to_str().unwrap()); - }); - - let client = child_process_client( - command, - timeout, - self.provider.clone(), - &effective_working_dir, - container.map(|c| c.id().to_string()), - self.client_name.clone(), - self.mcp_client_capabilities(), - self.context.session_manager.action_required(), - Arc::downgrade(self), - ) - .await?; - - Box::new(client) - } - ExtensionConfig::Frontend { .. } => { - return Err(ExtensionError::ConfigError( - "Invalid extension type: Frontend extensions cannot be added as server extensions".to_string() - )); - } }; let server_info = client.get_info().cloned(); @@ -1715,13 +1665,7 @@ impl ExtensionManager { let mut extensions = self.extensions.lock().await; extensions.insert( sanitized_name, - Extension::new( - config, - resolved_config, - Arc::from(client), - server_info, - temp_dir, - ), + Extension::new(config, resolved_config, Arc::from(client), server_info), ); drop(extensions); self.invalidate_tools_cache_and_bump_version().await; @@ -1735,12 +1679,11 @@ impl ExtensionManager { config: ExtensionConfig, client: McpClientBox, info: Option, - temp_dir: Option, ) { let normalized = name_to_key(&name); self.extensions.lock().await.insert( normalized, - Extension::new(config.clone(), config.clone(), client, info, temp_dir), + Extension::new(config.clone(), config.clone(), client, info), ); self.invalidate_tools_cache_and_bump_version().await; } @@ -2056,14 +1999,6 @@ impl ExtensionManager { Ok(tools) } - /// Get the extension prompt including client instructions - pub async fn get_planning_prompt(&self, tools_info: Vec) -> String { - let mut context: HashMap<&str, Value> = HashMap::new(); - context.insert("tools", serde_json::to_value(tools_info).unwrap()); - - prompt_template::render_template("plan.md", &context).expect("Prompt should render") - } - // Function that gets executed for read_resource tool pub async fn read_resource_tool( &self, @@ -2625,12 +2560,9 @@ impl ExtensionManager { description } } - ExtensionConfig::Sse { .. } => "SSE extension (unsupported)", ExtensionConfig::Platform { description, .. } | ExtensionConfig::StreamableHttp { description, .. } - | ExtensionConfig::Stdio { description, .. } - | ExtensionConfig::Frontend { description, .. } - | ExtensionConfig::InlinePython { description, .. } => description, + | ExtensionConfig::Stdio { description, .. } => description, }; disabled_extensions.push(format!("- {} - {}", config.name(), description)); } @@ -2907,7 +2839,7 @@ mod tests { bundled: None, available_tools, }; - let extension = Extension::new(config.clone(), config.clone(), client, None, None); + let extension = Extension::new(config.clone(), config.clone(), client, None); self.extensions .lock() .await @@ -3968,11 +3900,10 @@ mod tests { temp_dir.path().to_path_buf(), )); - let config = ExtensionConfig::Frontend { + let config = ExtensionConfig::Platform { name: "test-ext".to_string(), description: "original".to_string(), - tools: vec![], - instructions: None, + display_name: None, bundled: None, available_tools: vec![], }; @@ -3982,7 +3913,6 @@ mod tests { config.clone(), Arc::new(MockClient {}), None, - None, ) .await; assert_eq!(em.extensions.lock().await.len(), 1); @@ -4006,19 +3936,17 @@ mod tests { temp_dir.path().to_path_buf(), )); - let config_a = ExtensionConfig::Frontend { + let config_a = ExtensionConfig::Platform { name: "test-ext".to_string(), description: "version-a".to_string(), - tools: vec![], - instructions: None, + display_name: None, bundled: None, available_tools: vec![], }; - let config_b = ExtensionConfig::Frontend { + let config_b = ExtensionConfig::Platform { name: "test-ext".to_string(), - description: "version-b".to_string(), // changed - tools: vec![], - instructions: None, + description: "version-b".to_string(), + display_name: None, bundled: None, available_tools: vec![], }; @@ -4028,16 +3956,15 @@ mod tests { config_a, Arc::new(MockClient {}), None, - None, ) .await; assert_eq!(em.extensions.lock().await.len(), 1); - // add_extension with changed config attempts to create a new client (fails here - // because Frontend configs cannot be added as server extensions), but must preserve - // the old extension so the session isn't left without it. let result = em.add_extension(config_b, None, None, None).await; - assert!(result.is_err(), "Frontend add_extension must return Err"); + assert!( + result.is_err(), + "unknown platform extension must return Err" + ); assert_eq!( em.extensions.lock().await.len(), 1, diff --git a/crates/goose/src/agents/gen_ai_telemetry.rs b/crates/goose/src/agents/gen_ai_telemetry.rs index cac9cfaee..5bf40474c 100644 --- a/crates/goose/src/agents/gen_ai_telemetry.rs +++ b/crates/goose/src/agents/gen_ai_telemetry.rs @@ -28,12 +28,11 @@ pub(super) fn simple_output_json(text: &str) -> String { pub(super) fn output_message_json(message: &Message) -> String { // Message does not retain provider finish reasons; tool requests are the only // distinct completion signal available after streaming. - let finish_reason = if message.content.iter().any(|content| { - matches!( - content, - MessageContent::ToolRequest(_) | MessageContent::FrontendToolRequest(_) - ) - }) { + let finish_reason = if message + .content + .iter() + .any(|content| matches!(content, MessageContent::ToolRequest(_))) + { "tool_call" } else { "stop" @@ -231,9 +230,6 @@ fn message_part_json(content: &MessageContent) -> Value { Err(error) => json!({ "error": error.to_string() }), }, }), - MessageContent::FrontendToolRequest(request) => { - tool_call_part(&request.id, &request.tool_call) - } MessageContent::Thinking(thinking) => json!({ "type": "reasoning", "content": thinking.thinking, diff --git a/crates/goose/src/agents/mcp_client.rs b/crates/goose/src/agents/mcp_client.rs index 96d8e35c2..7d41c8937 100644 --- a/crates/goose/src/agents/mcp_client.rs +++ b/crates/goose/src/agents/mcp_client.rs @@ -1355,13 +1355,7 @@ mod tests { available_tools: vec![], }; extension_manager - .add_client( - "dynamic".to_string(), - config, - tools_client.clone(), - None, - None, - ) + .add_client("dynamic".to_string(), config, tools_client.clone(), None) .await; let goose_client = GooseClient::new( diff --git a/crates/goose/src/agents/mod.rs b/crates/goose/src/agents/mod.rs index 5ac6f9d5d..10269dd2c 100644 --- a/crates/goose/src/agents/mod.rs +++ b/crates/goose/src/agents/mod.rs @@ -37,7 +37,7 @@ pub use schedule_tool::ScheduleTool; pub use subagent_handler::SUBAGENT_TOOL_REQUEST_TYPE; pub use subagent_task_config::TaskConfig; pub use tool_execution::ToolCallContext; -pub use types::{FrontendTool, RetryConfig, SessionConfig, SuccessCheck}; +pub use types::{RetryConfig, SessionConfig, SuccessCheck}; pub(crate) fn latest_provider_session_id<'a>( messages: &'a [crate::conversation::message::Message], diff --git a/crates/goose/src/agents/platform_extensions/code_execution.rs b/crates/goose/src/agents/platform_extensions/code_execution.rs index dbd8059b8..85a4f308d 100644 --- a/crates/goose/src/agents/platform_extensions/code_execution.rs +++ b/crates/goose/src/agents/platform_extensions/code_execution.rs @@ -800,7 +800,6 @@ mod tests { }, Arc::new(VisibilityClient), None, - None, ) .await; diff --git a/crates/goose/src/agents/prompt_manager.rs b/crates/goose/src/agents/prompt_manager.rs index c97cfd7b6..a31905055 100644 --- a/crates/goose/src/agents/prompt_manager.rs +++ b/crates/goose/src/agents/prompt_manager.rs @@ -46,7 +46,6 @@ pub struct SystemPromptBuilder<'a, M> { manager: &'a M, extensions_info: Vec, - frontend_instructions: Option, prompt_extras: IndexMap, subagents_enabled: bool, hints: Option, @@ -68,11 +67,6 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> { self } - pub fn with_frontend_instructions(mut self, frontend_instructions: Option) -> Self { - self.frontend_instructions = frontend_instructions; - self - } - pub fn with_prompt_extras( mut self, extras: impl IntoIterator, @@ -116,14 +110,6 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> { pub fn build(self) -> String { let mut extensions_info = self.extensions_info; - // Add frontend instructions to extensions_info to simplify json rendering - if let Some(frontend_instructions) = self.frontend_instructions { - extensions_info.push(ExtensionInfo::new( - "frontend", - &frontend_instructions, - false, - )); - } // Stable tool ordering is important for multi session prompt caching. extensions_info.sort_by(|a, b| a.name.cmp(&b.name)); @@ -272,7 +258,6 @@ impl PromptManager { manager: self, extensions_info: vec![], - frontend_instructions: None, prompt_extras: IndexMap::new(), subagents_enabled: false, hints: None, diff --git a/crates/goose/src/agents/reply_parts.rs b/crates/goose/src/agents/reply_parts.rs index 940ea1c33..e9945458b 100644 --- a/crates/goose/src/agents/reply_parts.rs +++ b/crates/goose/src/agents/reply_parts.rs @@ -218,7 +218,6 @@ impl Agent { let system_prompt = prompt_manager .builder() .with_extensions(extensions_info.into_iter()) - .with_frontend_instructions(self.frontend_instructions.lock().await.clone()) .with_code_execution_mode(code_execution_active) .with_hints(working_dir) .with_goose_mode(goose_mode) @@ -573,18 +572,13 @@ pub(crate) async fn stream_response_from_provider( } impl Agent { - /// Categorize tool requests from the response into different types - /// Returns: - /// - frontend_requests: Tool requests that should be handled by the frontend - /// - other_requests: All other tool requests (including requests to enable extensions) - /// - filtered_message: The original message with frontend tool requests removed - pub(crate) async fn categorize_tool_requests( + pub(crate) fn categorize_tool_requests( &self, response: &Message, tools: &[Tool], toolshim_tools: &[Tool], suppress_replayed_thinking: bool, - ) -> (Vec, Vec, Message) { + ) -> (Vec, Message) { let model_visible_tools = tools .iter() .chain(toolshim_tools.iter()) @@ -682,7 +676,6 @@ impl Agent { let has_tool_requests = !tool_requests.is_empty(); let should_suppress_replayed_thinking = suppress_replayed_thinking && has_tool_requests; - // Create a filtered message with frontend tool requests removed. // When a response contains tool calls, keep reasoning in the original // message for provider/state purposes but only suppress it from the // user-visible filtered message if the caller already surfaced @@ -703,23 +696,7 @@ impl Agent { }; next_request = deduped_requests.next(); - // Always keep externally-dispatched requests visible, even if - // their name happens to overlap a registered frontend tool — - // they're observation-only and must not be removed from history. - let should_include = if coerced_req.was_executed_externally() { - true - } else if let Ok(tool_call) = &coerced_req.tool_call { - !model_visible_tools - .iter() - .any(|tool| tool.name == tool_call.name) - || !self.is_frontend_tool(&tool_call.name).await - } else { - true - }; - - if should_include { - filtered_content.push(MessageContent::ToolRequest(coerced_req.clone())); - } + filtered_content.push(MessageContent::ToolRequest(coerced_req.clone())); } MessageContent::Thinking(_) | MessageContent::RedactedThinking(_) if should_suppress_replayed_thinking => {} @@ -741,29 +718,12 @@ impl Agent { filtered_message = filtered_message.with_id(id); } - // Categorize tool requests - let mut frontend_requests = Vec::new(); - let mut other_requests = Vec::new(); + let tool_requests = tool_requests + .into_iter() + .filter(|request| !request.was_executed_externally()) + .collect(); - for request in tool_requests { - // Skip externally-dispatched requests (e.g. claude-acp); the - // provider already executed the tool. Stays in filtered_message. - if request.was_executed_externally() { - continue; - } - if let Ok(tool_call) = &request.tool_call { - if self.is_frontend_tool(&tool_call.name).await { - frontend_requests.push(request); - } else { - other_requests.push(request); - } - } else { - // If there's an error in the tool call, add it to other_requests - other_requests.push(request); - } - } - - (frontend_requests, other_requests, filtered_message) + (tool_requests, filtered_message) } /// `post_compaction_context_tokens` is `Some` when this usage came from a @@ -873,42 +833,16 @@ pub fn is_tool_visible_to_model(tool: &Tool) -> bool { mod tests { use super::*; use crate::agents::gen_ai_telemetry::{self, test_support::SpanFieldCapture}; - use crate::agents::{AgentConfig, GoosePlatform}; - use crate::config::permission::PermissionLevel; - use crate::config::{GooseMode, PermissionManager}; use crate::conversation::message::{Message, SystemNotificationType}; use crate::providers::base::Provider; - use crate::session::{SessionManager, SessionType}; use async_trait::async_trait; use goose_providers::conversation::token_usage::{ProviderStats, ProviderUsage, Usage}; use goose_providers::model::ModelConfig; - use rmcp::model::{Annotations, Role, TextContent, ToolAnnotations}; + use rmcp::model::{Annotations, Role, TextContent}; use rmcp::object; use std::sync::Mutex; use std::time::{Duration, Instant}; - #[derive(Clone)] - struct MockProvider; - - #[async_trait] - impl Provider for MockProvider { - fn get_name(&self) -> &str { - "mock" - } - - async fn stream( - &self, - _model_config: &ModelConfig, - _system: &str, - _messages: &[Message], - _tools: &[Tool], - ) -> Result { - let message = Message::assistant().with_text("ok"); - let usage = ProviderUsage::new("mock".to_string(), Usage::default()); - Ok(stream_from_single_message(message, usage)) - } - } - #[derive(Clone)] struct GenAiTracingProvider; @@ -1133,143 +1067,6 @@ mod tests { ); } - #[tokio::test] - async fn prepare_tools_returns_sorted_tools_including_frontend() -> anyhow::Result<()> { - let data_dir = tempfile::tempdir()?; - let data_path = data_dir.path().to_path_buf(); - let session_manager = std::sync::Arc::new(SessionManager::new(data_path.clone())); - let agent = Agent::with_config(AgentConfig::new( - std::sync::Arc::clone(&session_manager), - std::sync::Arc::new(PermissionManager::new(data_path)), - None, - GooseMode::default(), - false, - GoosePlatform::GooseCli, - )); - - let session = session_manager - .create_session( - std::env::current_dir().unwrap(), - "test-prepare-tools".to_string(), - SessionType::Hidden, - GooseMode::default(), - ) - .await?; - - let model_config = ModelConfig::new("test-model"); - let provider = std::sync::Arc::new(MockProvider); - agent - .update_provider(provider, model_config, &session.id) - .await?; - - // Add unsorted frontend tools - let frontend_tools = vec![ - Tool::new( - "frontend__z_tool".to_string(), - "Z tool".to_string(), - object!({ "type": "object", "properties": { } }), - ), - Tool::new( - "frontend__a_tool".to_string(), - "A tool".to_string(), - object!({ "type": "object", "properties": { } }), - ), - ]; - - agent - .add_extension( - crate::agents::extension::ExtensionConfig::Frontend { - name: "frontend".to_string(), - description: "desc".to_string(), - tools: frontend_tools, - instructions: None, - bundled: None, - available_tools: vec![], - }, - &session.id, - ) - .await - .unwrap(); - - let (tools, _toolshim_tools, _system_prompt, _model_config) = agent - .prepare_tools_and_prompt(&session.id, session.working_dir.as_path()) - .await?; - - let names: Vec = tools.iter().map(|t| t.name.clone().into_owned()).collect(); - assert!(names.iter().any(|n| n == "frontend__a_tool")); - assert!(names.iter().any(|n| n == "frontend__z_tool")); - - // Verify the names are sorted ascending - let mut sorted = names.clone(); - sorted.sort(); - assert_eq!(names, sorted); - - Ok(()) - } - - #[tokio::test] - async fn prepare_toolshim_tools_applies_writable_annotations() -> anyhow::Result<()> { - let data_dir = tempfile::tempdir()?; - let data_path = data_dir.path().to_path_buf(); - let session_manager = Arc::new(SessionManager::new(data_path.clone())); - let permission_manager = Arc::new(PermissionManager::new(data_path)); - permission_manager - .update_smart_approve_permission("frontend__write_tool", PermissionLevel::AlwaysAllow); - let agent = Agent::with_config(AgentConfig::new( - Arc::clone(&session_manager), - Arc::clone(&permission_manager), - None, - GooseMode::SmartApprove, - false, - GoosePlatform::GooseCli, - )); - let session = session_manager - .create_session( - std::env::current_dir()?, - "test-toolshim-annotations".to_string(), - SessionType::Hidden, - GooseMode::SmartApprove, - ) - .await?; - let model_config = ModelConfig::new("test-model").with_toolshim(true); - agent - .update_provider(Arc::new(MockProvider), model_config, &session.id) - .await?; - agent - .add_extension( - crate::agents::extension::ExtensionConfig::Frontend { - name: "frontend".to_string(), - description: "desc".to_string(), - tools: vec![Tool::new( - "frontend__write_tool", - "Write tool", - object!({ "type": "object", "properties": { } }), - ) - .annotate(ToolAnnotations::new().read_only(false))], - instructions: None, - bundled: None, - available_tools: vec![], - }, - &session.id, - ) - .await?; - - let (tools, toolshim_tools, _, _) = agent - .prepare_tools_and_prompt(&session.id, session.working_dir.as_path()) - .await?; - - assert!(tools.is_empty()); - assert!(toolshim_tools - .iter() - .any(|tool| tool.name == "frontend__write_tool")); - assert_eq!( - permission_manager.get_smart_approve_permission("frontend__write_tool"), - Some(PermissionLevel::AskBefore) - ); - - Ok(()) - } - #[tokio::test] async fn test_stream_error_propagation() { use futures::StreamExt; @@ -1545,8 +1342,8 @@ mod tests { Ok(()) } - #[tokio::test] - async fn categorize_tool_requests_keeps_thinking_when_not_previously_streamed() { + #[test] + fn categorize_tool_requests_keeps_thinking_when_not_previously_streamed() { let agent = crate::agents::Agent::new(); let tool = Tool::new("test_tool", "a test tool", object!({ "type": "object" })); let mut response = Message::assistant() @@ -1557,11 +1354,10 @@ mod tests { ); response.metadata.output_token_limit_reached = true; - let (_frontend_requests, other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &[tool], &[], false) - .await; + let (tool_requests, filtered_message) = + agent.categorize_tool_requests(&response, &[tool], &[], false); - assert_eq!(other_requests.len(), 1); + assert_eq!(tool_requests.len(), 1); assert!(filtered_message.metadata.output_token_limit_reached); assert_eq!(filtered_message.content.len(), 2); assert!(matches!( @@ -1574,8 +1370,8 @@ mod tests { )); } - #[tokio::test] - async fn categorize_tool_requests_drops_replayed_thinking_after_streaming() { + #[test] + fn categorize_tool_requests_drops_replayed_thinking_after_streaming() { let agent = crate::agents::Agent::new(); let tool = Tool::new("test_tool", "a test tool", object!({ "type": "object" })); let response = Message::assistant() @@ -1585,11 +1381,10 @@ mod tests { Ok(rmcp::model::CallToolRequestParams::new("test_tool")), ); - let (_frontend_requests, other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &[tool], &[], true) - .await; + let (tool_requests, filtered_message) = + agent.categorize_tool_requests(&response, &[tool], &[], true); - assert_eq!(other_requests.len(), 1); + assert_eq!(tool_requests.len(), 1); assert_eq!(filtered_message.content.len(), 1); assert!(matches!( filtered_message.content[0], @@ -1597,8 +1392,8 @@ mod tests { )); } - #[tokio::test] - async fn categorize_tool_requests_excludes_assistant_only_text_from_user_events() { + #[test] + fn categorize_tool_requests_excludes_assistant_only_text_from_user_events() { let agent = crate::agents::Agent::new(); let assistant_only = TextContent::new("assistant-only") .with_annotations(Annotations::default().with_audience(vec![Role::Assistant])); @@ -1607,9 +1402,7 @@ mod tests { .with_text("user-visible") .with_thinking("visible reasoning", ""); - let (_frontend_requests, _other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &[], &[], false) - .await; + let (_, filtered_message) = agent.categorize_tool_requests(&response, &[], &[], false); assert_eq!(response.as_concat_text(), "assistant-only\nuser-visible"); assert_eq!(filtered_message.as_concat_text(), "user-visible"); @@ -1619,11 +1412,10 @@ mod tests { .any(|content| matches!(content, MessageContent::Thinking(_)))); } - #[tokio::test] - async fn categorize_tool_requests_skips_externally_dispatched_and_preserves_marker() { + #[test] + fn categorize_tool_requests_skips_externally_dispatched_and_preserves_marker() { // External requests must (1) survive coercion with goose.external_dispatch - // intact, (2) be excluded from both dispatch buckets, (3) stay in - // filtered_message. + // intact, (2) be excluded from dispatch, (3) stay in filtered_message. use crate::conversation::message::TOOL_META_EXTERNAL_DISPATCH_KEY; let agent = crate::agents::Agent::new(); @@ -1650,17 +1442,12 @@ mod tests { Some(serde_json::json!({ TOOL_META_EXTERNAL_DISPATCH_KEY: true })), ); - let (frontend_requests, other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &[registry_tool], &[], false) - .await; + let (tool_requests, filtered_message) = + agent.categorize_tool_requests(&response, &[registry_tool], &[], false); assert!( - frontend_requests.is_empty(), - "external request leaked into frontend_requests: {frontend_requests:?}" - ); - assert!( - other_requests.is_empty(), - "external request leaked into other_requests: {other_requests:?}" + tool_requests.is_empty(), + "external request leaked into tool requests: {tool_requests:?}" ); assert_eq!(filtered_message.content.len(), 2); let tool_req = match &filtered_message.content[0] { @@ -1689,49 +1476,24 @@ mod tests { .is_some_and(|request| request.tool_call.is_ok())); } - #[tokio::test] - async fn categorize_tool_requests_rejects_unadvertised_executable_tools() { - use crate::agents::types::FrontendTool; - + #[test] + fn categorize_tool_requests_rejects_unadvertised_executable_tools() { let agent = crate::agents::Agent::new(); - let app_only_tool = Tool::new( - "frontend__app_only", - "an app-only frontend tool", - object!({ "type": "object" }), - ) - .with_meta(rmcp::model::MetaObject( - serde_json::json!({ "ui": { "visibility": ["app"] } }) - .as_object() - .unwrap() - .clone(), - )); - agent.frontend_tools.lock().await.insert( - app_only_tool.name.to_string(), - FrontendTool { - name: app_only_tool.name.to_string(), - tool: app_only_tool, - }, - ); - let response = Message::assistant() .with_tool_request( "app-only", - Ok(rmcp::model::CallToolRequestParams::new( - "frontend__app_only", - )), + Ok(rmcp::model::CallToolRequestParams::new("app_only")), ) .with_tool_request( "off-list", Ok(rmcp::model::CallToolRequestParams::new("unadvertised")), ); - let (frontend_requests, other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &[], &[], false) - .await; + let (tool_requests, filtered_message) = + agent.categorize_tool_requests(&response, &[], &[], false); - assert!(frontend_requests.is_empty()); - assert_eq!(other_requests.len(), 2); - assert!(other_requests + assert_eq!(tool_requests.len(), 2); + assert!(tool_requests .iter() .all(|request| request.tool_call.is_err())); assert_eq!( @@ -1744,28 +1506,19 @@ mod tests { ); } - #[tokio::test] - async fn categorize_tool_requests_dispatches_advertised_tools() { - use crate::agents::types::FrontendTool; - + #[test] + fn categorize_tool_requests_dispatches_advertised_tools() { let agent = crate::agents::Agent::new(); let regular_tool = Tool::new( "regular_tool", "a regular tool", object!({ "type": "object" }), ); - let frontend_tool = Tool::new( - "frontend_tool", - "a frontend tool", + let additional_tool = Tool::new( + "additional_tool", + "an additional tool", object!({ "type": "object" }), ); - agent.frontend_tools.lock().await.insert( - frontend_tool.name.to_string(), - FrontendTool { - name: frontend_tool.name.to_string(), - tool: frontend_tool.clone(), - }, - ); let toolshim_tool = Tool::new( "toolshim_tool", "a toolshim tool", @@ -1781,25 +1534,22 @@ mod tests { Ok(rmcp::model::CallToolRequestParams::new("toolshim_tool")), ) .with_tool_request( - "frontend", - Ok(rmcp::model::CallToolRequestParams::new("frontend_tool")), + "additional", + Ok(rmcp::model::CallToolRequestParams::new("additional_tool")), ); - let (frontend_requests, other_requests, _) = agent - .categorize_tool_requests( - &response, - &[regular_tool, frontend_tool], - &[toolshim_tool], - false, - ) - .await; + let (tool_requests, _) = agent.categorize_tool_requests( + &response, + &[regular_tool, additional_tool], + &[toolshim_tool], + false, + ); - assert_eq!(frontend_requests.len(), 1); - assert_eq!(other_requests.len(), 2); + assert_eq!(tool_requests.len(), 3); } - #[tokio::test] - async fn categorize_tool_requests_canonicalizes_mangled_unprefixed_tool_name() { + #[test] + fn categorize_tool_requests_canonicalizes_mangled_unprefixed_tool_name() { // GLM's documented reproduction (#9486): a default Developer-extension // tool is advertised unprefixed ("shell"), owner only in metadata, and // the model emits "developer.shell". This must be rewritten to the @@ -1826,12 +1576,11 @@ mod tests { Ok(rmcp::model::CallToolRequestParams::new("developer.shell")), ); - let (_frontend_requests, other_requests, _filtered_message) = agent - .categorize_tool_requests(&response, &[shell_tool], &[], false) - .await; + let (tool_requests, _) = + agent.categorize_tool_requests(&response, &[shell_tool], &[], false); - assert_eq!(other_requests.len(), 1); - let tool_call = other_requests[0] + assert_eq!(tool_requests.len(), 1); + let tool_call = tool_requests[0] .tool_call .as_ref() .expect("mangled-but-recoverable name must not become an Err"); @@ -1841,8 +1590,8 @@ mod tests { ); } - #[tokio::test] - async fn categorize_tool_requests_canonicalizes_mangled_non_extension_manager_tool_name() { + #[test] + fn categorize_tool_requests_canonicalizes_mangled_non_extension_manager_tool_name() { // recipe__final_output is appended by Agent::list_tools outside the // extension manager (see #9486 review); it must recover the same way. let agent = crate::agents::Agent::new(); @@ -1860,20 +1609,19 @@ mod tests { )), ); - let (_frontend_requests, other_requests, _filtered_message) = agent - .categorize_tool_requests(&response, &[final_output_tool], &[], false) - .await; + let (tool_requests, _) = + agent.categorize_tool_requests(&response, &[final_output_tool], &[], false); - assert_eq!(other_requests.len(), 1); - let tool_call = other_requests[0] + assert_eq!(tool_requests.len(), 1); + let tool_call = tool_requests[0] .tool_call .as_ref() .expect("mangled-but-recoverable name must not become an Err"); assert_eq!(tool_call.name, "recipe__final_output"); } - #[tokio::test] - async fn categorize_tool_requests_rejects_unrecoverable_unadvertised_name() { + #[test] + fn categorize_tool_requests_rejects_unrecoverable_unadvertised_name() { let agent = crate::agents::Agent::new(); let tool = Tool::new( "shell", @@ -1888,12 +1636,11 @@ mod tests { )), ); - let (_frontend_requests, other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &[tool], &[], false) - .await; + let (tool_requests, filtered_message) = + agent.categorize_tool_requests(&response, &[tool], &[], false); - assert_eq!(other_requests.len(), 1); - assert!(other_requests[0].tool_call.is_err()); + assert_eq!(tool_requests.len(), 1); + assert!(tool_requests[0].tool_call.is_err()); let tool_call = filtered_message.content[0] .as_tool_request() .unwrap() @@ -1903,8 +1650,8 @@ mod tests { assert!(tool_call.message.contains("totally_unknown_tool")); } - #[tokio::test] - async fn categorize_tool_requests_dedups_duplicate_ids_in_provider_order() { + #[test] + fn categorize_tool_requests_dedups_duplicate_ids_in_provider_order() { // A malformed provider repeats id "dup". The first occurrence wins, the // later duplicate is dropped from both the dispatch bucket and the // filtered (history) message, and unique ids are kept. @@ -1929,11 +1676,10 @@ mod tests { Tool::new("third_tool", "third", object!({ "type": "object" })), ]; - let (_frontend_requests, other_requests, filtered_message) = agent - .categorize_tool_requests(&response, &tools, &[], false) - .await; + let (tool_requests, filtered_message) = + agent.categorize_tool_requests(&response, &tools, &[], false); - let kept: Vec<(&str, &str)> = other_requests + let kept: Vec<(&str, &str)> = tool_requests .iter() .map(|r| (r.id.as_str(), r.tool_call.as_ref().unwrap().name.as_ref())) .collect(); diff --git a/crates/goose/src/agents/state_machine/inference_preparation.rs b/crates/goose/src/agents/state_machine/inference_preparation.rs index c976914d6..34bb55ddd 100644 --- a/crates/goose/src/agents/state_machine/inference_preparation.rs +++ b/crates/goose/src/agents/state_machine/inference_preparation.rs @@ -22,7 +22,6 @@ pub struct GooseInferenceRequestPreparer<'a> { pub(crate) goose_mode: &'a Mutex, pub(crate) prompt_manager: &'a Mutex, pub(crate) tool_inspection_manager: &'a ToolInspectionManager, - pub(crate) frontend_instructions: &'a Mutex>, pub(crate) context_limit: usize, } @@ -32,7 +31,7 @@ impl InferenceRequestPreparer for GooseInferenceRequestPreparer<'_> { &self, session: &Session, conversation: &Conversation, - mut input: InferenceInput, + input: InferenceInput, ) -> Result { #[cfg(feature = "code-mode")] let code_execution_mode = self @@ -51,11 +50,6 @@ impl InferenceRequestPreparer for GooseInferenceRequestPreparer<'_> { } let tools = crate::agents::reply_parts::prepare_inference_tools(input.tools, code_execution_mode); - if let Some(frontend_instructions) = self.frontend_instructions.lock().await.clone() { - input - .prompt_parts - .push(("frontend".to_string(), frontend_instructions)); - } let system_prompt = self.prompt_manager.lock().await.build_system_prompt( &session.working_dir, input.prompt_parts, diff --git a/crates/goose/src/agents/state_machine/tests/pipeline.rs b/crates/goose/src/agents/state_machine/tests/pipeline.rs index 3dca2e206..76fe3b7c1 100644 --- a/crates/goose/src/agents/state_machine/tests/pipeline.rs +++ b/crates/goose/src/agents/state_machine/tests/pipeline.rs @@ -94,7 +94,6 @@ pub(super) struct TestPipeline { prompt_manager: TokioMutex, tool_inspection_manager: ToolInspectionManager, permission_manager: Arc, - frontend_instructions: TokioMutex>, hook_manager: HookManager, stop_hook_block_cap: u32, goal: TokioMutex>, @@ -177,7 +176,6 @@ impl TestPipeline { goose_mode: &self.goose_mode, prompt_manager: &self.prompt_manager, tool_inspection_manager: &self.tool_inspection_manager, - frontend_instructions: &self.frontend_instructions, context_limit: self.model_config.context_limit(), }; let status_operation = Arc::new(StatusOperation::new( @@ -810,7 +808,6 @@ async fn build_test_pipeline( prompt_manager: TokioMutex::new(PromptManager::new()), tool_inspection_manager, permission_manager, - frontend_instructions: TokioMutex::new(None), hook_manager: HookManager::default(), stop_hook_block_cap: 3, goal: TokioMutex::new(None), @@ -856,7 +853,6 @@ async fn build_test_pipeline( extension, calculator.clone(), calculator.get_info().cloned(), - None, ) .await; } else { diff --git a/crates/goose/src/agents/tool_execution.rs b/crates/goose/src/agents/tool_execution.rs index 4974e00fb..9639d3d76 100644 --- a/crates/goose/src/agents/tool_execution.rs +++ b/crates/goose/src/agents/tool_execution.rs @@ -239,30 +239,4 @@ impl Agent { } }.boxed() } - - pub(crate) fn handle_frontend_tool_request<'a>( - &'a self, - tool_request: &'a ToolRequest, - message_tool_response: &'a mut Message, - ) -> BoxStream<'a, anyhow::Result> { - try_stream! { - if let Ok(tool_call) = tool_request.tool_call.clone() { - if self.is_frontend_tool(&tool_call.name).await { - yield Message::assistant().with_frontend_tool_request( - tool_request.id.clone(), - Ok(tool_call.clone()) - ); - - if let Some((id, result)) = self.tool_result_rx.lock().await.recv().await { - message_tool_response.add_tool_response_with_metadata( - id, - result, - tool_request.metadata.as_ref(), - ); - } - } - } - } - .boxed() - } } diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index be7d97227..fbafe8c99 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -1,12 +1,7 @@ -use crate::mcp_utils::ToolResult; use crate::providers::base::Provider; -use rmcp::model::{CallToolResult, Tool}; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use tokio::sync::{mpsc, Mutex}; - -/// Type alias for the tool result channel receiver -pub type ToolResultReceiver = Arc)>>>; +use tokio::sync::Mutex; // We use double Arc here to allow easy provider swaps while sharing concurrent access pub type SharedProvider = Arc>>>; @@ -72,13 +67,6 @@ pub enum SuccessCheck { }, } -/// A frontend tool that will be executed by the frontend rather than an extension -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FrontendTool { - pub name: String, - pub tool: Tool, -} - /// Session configuration for an agent #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionConfig { diff --git a/crates/goose/src/config/extensions.rs b/crates/goose/src/config/extensions.rs index 2546d200b..b84e91b0d 100644 --- a/crates/goose/src/config/extensions.rs +++ b/crates/goose/src/config/extensions.rs @@ -279,15 +279,20 @@ pub fn get_warnings() -> Vec { let mut warnings = Vec::new(); for (k, v) in raw { - if let (serde_yaml::Value::String(key), Ok(entry)) = - (k, serde_yaml::from_value::(v)) - { - if matches!(entry.config, ExtensionConfig::Sse { .. }) { - warnings.push(format!( - "'{}': SSE is unsupported, migrate to streamable_http", - key - )); - } + let Some(key) = k.as_str() else { + continue; + }; + let Some(extension) = v.as_mapping() else { + continue; + }; + let extension_type = extension + .get(serde_yaml::Value::String("type".to_string())) + .and_then(serde_yaml::Value::as_str); + if extension_type == Some("sse") { + warnings.push(format!( + "'{}': SSE is unsupported, migrate to streamable_http", + key + )); } } warnings diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs index 427199edb..bf27f3aa1 100644 --- a/crates/goose/src/providers/claude_code.rs +++ b/crates/goose/src/providers/claude_code.rs @@ -570,9 +570,6 @@ fn claude_mcp_config_json(extensions: &[ExtensionConfig]) -> Option { } mcp_servers.insert(key, Value::Object(config)); } - ExtensionConfig::Sse { name, .. } => { - tracing::debug!(name, "skipping SSE extension, migrate to streamable_http"); - } _ => {} } } @@ -1244,15 +1241,6 @@ mod tests { None ; "empty_extensions_returns_none" )] - #[test_case( - vec![ExtensionConfig::Sse { - name: "legacy".into(), - description: String::new(), - uri: Some("http://localhost/sse".into()), - }], - None - ; "sse_only_returns_none" - )] #[test_case( vec![ExtensionConfig::Stdio { name: "lookup".into(), diff --git a/crates/goose/src/providers/codex.rs b/crates/goose/src/providers/codex.rs index 6ee9de379..d9c816be1 100644 --- a/crates/goose/src/providers/codex.rs +++ b/crates/goose/src/providers/codex.rs @@ -623,9 +623,6 @@ fn codex_mcp_config_overrides(extensions: &[ExtensionConfig]) -> Result { - tracing::debug!(name, "skipping SSE extension, migrate to streamable_http"); - } _ => {} } } diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs index 1add61af8..084eb9d3a 100644 --- a/crates/goose/src/providers/formats/bedrock.rs +++ b/crates/goose/src/providers/formats/bedrock.rs @@ -250,27 +250,6 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result { - let tool_use_id = tool_req.id.to_string(); - let tool_use = if let Ok(call) = tool_req.tool_call.as_ref() { - bedrock::ToolUseBlock::builder() - .tool_use_id(tool_use_id) - .name(call.name.to_string()) - .input(to_bedrock_json(&args_to_value(call.arguments.clone()))) - .build() - } else { - // Unparseable tool call: emit a placeholder tool_use so the paired - // tool_result isn't orphaned — Bedrock rejects a tool_use with no name - // and a tool_result with no matching tool_use. Mirrors the - // OpenAI/Databricks/Anthropic formatters. - bedrock::ToolUseBlock::builder() - .tool_use_id(tool_use_id) - .name("unparseable_tool_call") - .input(to_bedrock_json(&args_to_value(None))) - .build() - }?; - bedrock::ContentBlock::ToolUse(tool_use) - } MessageContent::ToolResponse(tool_res) => { let content = match &tool_res.tool_result { Ok(result) => Some( diff --git a/crates/goose/src/providers/formats/openrouter.rs b/crates/goose/src/providers/formats/openrouter.rs index aa43b5554..615f96661 100644 --- a/crates/goose/src/providers/formats/openrouter.rs +++ b/crates/goose/src/providers/formats/openrouter.rs @@ -12,7 +12,6 @@ fn has_assistant_content(message: &Message) -> bool { MessageContent::Text(t) => !t.text.is_empty(), MessageContent::Image(_) => true, MessageContent::ToolRequest(req) => req.tool_call.is_ok(), - MessageContent::FrontendToolRequest(req) => req.tool_call.is_ok(), _ => false, }) } diff --git a/crates/goose/src/recipe/mod.rs b/crates/goose/src/recipe/mod.rs index cb6175897..d64d01a30 100644 --- a/crates/goose/src/recipe/mod.rs +++ b/crates/goose/src/recipe/mod.rs @@ -660,53 +660,6 @@ sub_recipes: assert_eq!(author.contact, Some("test@example.com".to_string())); } - #[test] - fn test_inline_python_extension() { - let content = r#"{ - "version": "1.0.0", - "title": "Test Recipe", - "description": "A test recipe", - "instructions": "Test instructions", - "extensions": [ - { - "type": "inline_python", - "name": "test_python", - "code": "print('hello world')", - "timeout": 300, - "description": "Test python extension", - "dependencies": ["numpy", "matplotlib"] - } - ] - }"#; - - let recipe = Recipe::from_content(content).unwrap(); - - assert!(recipe.extensions.is_some()); - let extensions = recipe.extensions.unwrap(); - assert_eq!(extensions.len(), 1); - - match &extensions[0] { - ExtensionConfig::InlinePython { - name, - code, - description, - timeout, - dependencies, - .. - } => { - assert_eq!(name, "test_python"); - assert_eq!(code, "print('hello world')"); - assert_eq!(description, "Test python extension"); - assert_eq!(timeout, &Some(300)); - assert!(dependencies.is_some()); - let deps = dependencies.as_ref().unwrap(); - assert!(deps.contains(&"numpy".to_string())); - assert!(deps.contains(&"matplotlib".to_string())); - } - _ => panic!("Expected InlinePython extension"), - } - } - #[test] fn test_from_content_with_activities() { let content = r#"{ diff --git a/crates/goose/src/recipe/recipe_extension_adapter.rs b/crates/goose/src/recipe/recipe_extension_adapter.rs index eff98ab22..aa4efa1df 100644 --- a/crates/goose/src/recipe/recipe_extension_adapter.rs +++ b/crates/goose/src/recipe/recipe_extension_adapter.rs @@ -1,5 +1,4 @@ use crate::agents::extension::{Envs, ExtensionConfig}; -use rmcp::model::Tool; use serde::de::Deserializer; use serde::Deserialize; use std::collections::HashMap; @@ -76,30 +75,6 @@ enum RecipeExtensionConfigInternal { #[serde(default)] available_tools: Vec, }, - #[serde(rename = "frontend")] - Frontend { - name: String, - #[serde(default)] - description: Option, - tools: Vec, - instructions: Option, - #[serde(default)] - bundled: Option, - #[serde(default)] - available_tools: Vec, - }, - #[serde(rename = "inline_python")] - InlinePython { - name: String, - #[serde(default)] - description: Option, - code: String, - timeout: Option, - #[serde(default)] - dependencies: Option>, - #[serde(default)] - available_tools: Vec, - }, } macro_rules! map_recipe_extensions { @@ -157,18 +132,6 @@ impl From for ExtensionConfig { scopes, bundled, available_tools - }, - Frontend { - tools, - instructions, - bundled, - available_tools - }, - InlinePython { - code, - timeout, - dependencies, - available_tools } ) } diff --git a/crates/goose/tests/agent.rs b/crates/goose/tests/agent.rs index a8265d1a9..b01ea5acc 100644 --- a/crates/goose/tests/agent.rs +++ b/crates/goose/tests/agent.rs @@ -2766,194 +2766,6 @@ mod tests { } } - mod frontend_extension_tests { - use super::*; - use goose::agents::{AgentConfig, ExtensionConfig}; - use goose::config::permission::PermissionManager; - use goose::config::GooseMode; - use goose::session::session_manager::SessionType; - use goose::session::{ - EnabledExtensionsState, ExtensionData, ExtensionState, SessionManager, - }; - use rmcp::model::Tool; - use rmcp::object; - use tempfile::TempDir; - - fn frontend_extension_with_tool(name: &str, tool_name: &str) -> ExtensionConfig { - ExtensionConfig::Frontend { - name: name.to_string(), - description: format!("Frontend test extension {name}"), - tools: vec![Tool::new( - tool_name.to_string(), - format!("Run {tool_name} from the frontend"), - object!({ - "type": "object", - "properties": { - "message": { "type": "string" } - }, - "required": ["message"] - }), - )], - instructions: Some(format!("Use the {tool_name} tool.")), - bundled: None, - available_tools: vec![], - } - } - - fn frontend_extension() -> ExtensionConfig { - frontend_extension_with_tool("frontend-e2e", "frontend__echo") - } - - #[tokio::test] - async fn test_frontend_extensions_are_persisted_listed_and_removed() { - let temp_dir = TempDir::new().unwrap(); - let data_dir = temp_dir.path().to_path_buf(); - let session_manager = Arc::new(SessionManager::new(data_dir.clone())); - let permission_manager = Arc::new(PermissionManager::new(data_dir)); - let agent = Agent::with_config(AgentConfig::new( - session_manager.clone(), - permission_manager, - None, - GooseMode::default(), - false, - GoosePlatform::GooseDesktop, - )); - - let session = session_manager - .create_session( - std::env::current_dir().unwrap(), - "frontend-extension-test".to_string(), - SessionType::Hidden, - GooseMode::default(), - ) - .await - .unwrap(); - - agent - .add_extension(frontend_extension(), &session.id) - .await - .unwrap(); - - let listed_tools = agent.list_tools(&session.id, None).await; - assert!(listed_tools - .iter() - .any(|tool| tool.name == "frontend__echo")); - - let filtered_tools = agent - .list_tools(&session.id, Some("frontend-e2e".to_string())) - .await; - assert_eq!(filtered_tools.len(), 1); - assert_eq!(filtered_tools[0].name, "frontend__echo"); - - let extension_names = agent.list_extensions().await; - assert!(extension_names.iter().any(|name| name == "frontend-e2e")); - - let persisted_session = session_manager - .get_session(&session.id, false) - .await - .unwrap(); - let persisted_extensions = - EnabledExtensionsState::from_extension_data(&persisted_session.extension_data) - .unwrap() - .extensions; - assert!(persisted_extensions - .iter() - .any(|extension| extension.name() == "frontend-e2e")); - - agent - .remove_extension("frontend-e2e", &session.id) - .await - .unwrap(); - - let listed_tools = agent.list_tools(&session.id, None).await; - assert!(!listed_tools - .iter() - .any(|tool| tool.name == "frontend__echo")); - - let persisted_session = session_manager - .get_session(&session.id, false) - .await - .unwrap(); - let persisted_extensions = - EnabledExtensionsState::from_extension_data(&persisted_session.extension_data) - .unwrap() - .extensions; - assert!(persisted_extensions - .iter() - .all(|extension| extension.name() != "frontend-e2e")); - } - - #[tokio::test] - async fn test_concurrent_frontend_session_load_keeps_all_tools() { - let temp_dir = TempDir::new().unwrap(); - let data_dir = temp_dir.path().to_path_buf(); - let session_manager = Arc::new(SessionManager::new(data_dir.clone())); - let permission_manager = Arc::new(PermissionManager::new(data_dir)); - let agent = Arc::new(Agent::with_config(AgentConfig::new( - session_manager.clone(), - permission_manager, - None, - GooseMode::default(), - false, - GoosePlatform::GooseDesktop, - ))); - - let session = session_manager - .create_session( - std::env::current_dir().unwrap(), - "frontend-extension-load-test".to_string(), - SessionType::Hidden, - GooseMode::default(), - ) - .await - .unwrap(); - - let expected_tools = (0..12) - .map(|index| format!("frontend__tool_{index}")) - .collect::>(); - let extensions = expected_tools - .iter() - .enumerate() - .map(|(index, tool_name)| { - frontend_extension_with_tool(&format!("frontend-{index}"), tool_name) - }) - .collect::>(); - - let mut extension_data = ExtensionData::new(); - EnabledExtensionsState::new(extensions) - .to_extension_data(&mut extension_data) - .unwrap(); - session_manager - .update(&session.id) - .extension_data(extension_data) - .apply() - .await - .unwrap(); - - let session = session_manager - .get_session(&session.id, false) - .await - .unwrap(); - let load_results = agent.load_extensions_from_session(&session).await; - assert!( - load_results.iter().all(|result| result.success), - "failed to load frontend extensions: {load_results:?}", - ); - - let listed_tools = agent.list_tools(&session.id, None).await; - for tool_name in expected_tools { - assert!( - listed_tools.iter().any(|tool| tool.name == tool_name), - "expected listed frontend tool {tool_name}", - ); - assert!( - agent.is_frontend_tool(&tool_name).await, - "expected frontend dispatch state for {tool_name}", - ); - } - } - } - mod add_extensions_bulk_tests { use super::*; use goose::agents::extension::Envs; @@ -2964,26 +2776,13 @@ mod tests { use goose::session::{ EnabledExtensionsState, ExtensionData, ExtensionState, SessionManager, }; - use rmcp::model::Tool; - use rmcp::object; use tempfile::TempDir; - fn frontend_extension(name: &str) -> ExtensionConfig { - ExtensionConfig::Frontend { + fn platform_extension(name: &str) -> ExtensionConfig { + ExtensionConfig::Platform { name: name.to_string(), - description: format!("Frontend test extension {name}"), - tools: vec![Tool::new( - format!("{name}__tool"), - format!("Run a tool from {name}"), - object!({ - "type": "object", - "properties": { - "message": { "type": "string" } - }, - "required": ["message"] - }), - )], - instructions: None, + description: format!("Platform test extension {name}"), + display_name: None, bundled: None, available_tools: vec![], } @@ -3059,10 +2858,7 @@ mod tests { let results = agent .add_extensions_bulk( - vec![ - frontend_extension("frontend-a"), - frontend_extension("frontend-b"), - ], + vec![platform_extension("analyze"), platform_extension("todo")], &session_id, ) .await @@ -3071,7 +2867,7 @@ mod tests { assert!(results.iter().all(|result| result.success)); assert_eq!( persisted_extension_names(&session_manager, &session_id).await, - vec!["frontend-a".to_string(), "frontend-b".to_string()] + vec!["analyze".to_string(), "todo".to_string()] ); } @@ -3083,7 +2879,7 @@ mod tests { let results = agent .add_extensions_bulk( vec![ - frontend_extension("frontend-ok"), + platform_extension("todo"), unloadable_stdio_extension("broken"), ], &session_id, @@ -3094,7 +2890,7 @@ mod tests { assert_eq!(results.len(), 2); assert!(results .iter() - .any(|result| result.name == "frontend-ok" && result.success)); + .any(|result| result.name == "todo" && result.success)); let broken = results .iter() .find(|result| result.name == "broken") @@ -3104,7 +2900,7 @@ mod tests { assert_eq!( persisted_extension_names(&session_manager, &session_id).await, - vec!["frontend-ok".to_string()] + vec!["todo".to_string()] ); } diff --git a/documentation/docs/guides/config-files.md b/documentation/docs/guides/config-files.md index ace786009..db4f6e143 100644 --- a/documentation/docs/guides/config-files.md +++ b/documentation/docs/guides/config-files.md @@ -144,7 +144,7 @@ extensions: available_tools: [] # Filter to specific tools (empty = all) ``` -Supported extension types are `builtin`, `platform`, `stdio`, `streamable_http`, `frontend`, and `inline_python`. `sse` may appear in older configs, but is kept only for compatibility. +Supported extension types are `builtin`, `platform`, `stdio`, and `streamable_http`. SSE is not supported; migrate old SSE configurations to `streamable_http`. Common extension shapes: diff --git a/documentation/docs/guides/recipes/recipe-reference.md b/documentation/docs/guides/recipes/recipe-reference.md index 85a202e61..9848f775c 100644 --- a/documentation/docs/guides/recipes/recipe-reference.md +++ b/documentation/docs/guides/recipes/recipe-reference.md @@ -173,8 +173,6 @@ The `extensions` field allows you to specify which Model Context Protocol (MCP) - **`builtin`**: Built-in extension that is part of the bundled goose MCP server - **`platform`**: Platform extensions that run in the agent process - **`streamable_http`**: Streamable HTTP client with URI endpoint -- **`frontend`**: Frontend-provided tools called through the frontend -- **`inline_python`**: Inline Python code executed using uvx. Requires `code` field; optional `dependencies` for packages. :::note Summon Extension and Subagents The `delegate` and `load` tools are provided by the `summon` platform extension. When a recipe specifies an explicit `extensions` block, only the listed extensions are available — default platform extensions like `summon` are not automatically included. If your recipe needs subagent delegation, add `summon` to your extensions list: @@ -222,16 +220,6 @@ extensions: timeout: 60 description: "GitHub MCP extension for repository operations" - - type: inline_python - name: data_processor - code: | - import pandas as pd - print("Processing data...") - dependencies: - - pandas - - numpy - timeout: 120 - description: "Process data using pandas" ``` @@ -265,14 +253,6 @@ extensions: "env_keys": ["GITHUB_PERSONAL_ACCESS_TOKEN"], "timeout": 60, "description": "GitHub MCP extension for repository operations" - }, - { - "type": "inline_python", - "name": "data_processor", - "code": "import pandas as pd\nprint(\"Processing data...\")", - "dependencies": ["pandas", "numpy"], - "timeout": 120, - "description": "Process data using pandas" } ] } diff --git a/examples/frontend_tools.py b/examples/frontend_tools.py deleted file mode 100644 index 98aa98749..000000000 --- a/examples/frontend_tools.py +++ /dev/null @@ -1,292 +0,0 @@ -import asyncio -import json -import os -from typing import List, Dict, Any -import httpx -from datetime import datetime - -# Configuration -GOOSE_HOST = "127.0.0.1" -GOOSE_PORT = "3001" -GOOSE_URL = f"http://{GOOSE_HOST}:{GOOSE_PORT}" -SECRET_KEY = "test" # Default development secret key - -# A simple calculator tool definition -CALCULATOR_TOOL = { - "name": "calculator", - "description": "Perform basic arithmetic calculations", - "inputSchema": { - "type": "object", - "required": ["operation", "numbers"], - "properties": { - "operation": { - "type": "string", - "enum": ["add", "subtract", "multiply", "divide"], - "description": "The arithmetic operation to perform", - }, - "numbers": { - "type": "array", - "items": {"type": "number"}, - "description": "List of numbers to operate on in order", - }, - }, - }, -} - -# Enable Extension tool definition -ENABLE_EXTENSION_TOOL = { - "name": "enable_extension", - "description": "Enable extensions to help complete tasks. Enable an extension by providing the extension name.", - "inputSchema": { - "type": "object", - "required": ["extension_name"], - "properties": { - "extension_name": { - "type": "string", - "description": "The name of the extension to enable", - }, - }, - }, -} - -# Frontend extension configuration -FRONTEND_CONFIG = { - "name": "pythonclient", - "type": "frontend", - "tools": [CALCULATOR_TOOL, ENABLE_EXTENSION_TOOL], - "instructions": "A calculator extension that can perform basic arithmetic operations. Use enable extension tool to add extensions such as fetch, pdf reader, etc.", -} - - -async def setup_agent() -> None: - """Initialize the agent with our frontend tool.""" - async with httpx.AsyncClient() as client: - # First create the agent - response = await client.post( - f"{GOOSE_URL}/agent/update_provider", - json={"provider": "databricks", "model": "goose"}, - headers={"X-Secret-Key": SECRET_KEY}, - ) - response.raise_for_status() - print("Successfully created agent") - - # Then add our frontend extension - response = await client.post( - f"{GOOSE_URL}/extensions/add", - json=FRONTEND_CONFIG, - headers={"X-Secret-Key": SECRET_KEY}, - ) - response.raise_for_status() - print("Successfully added calculator extension") - - -def execute_calculator(args: Dict[str, Any]) -> List[Dict[str, Any]]: - """Execute the calculator tool with the given arguments.""" - operation = args["operation"] - numbers = args["numbers"] - - try: - result = None - if operation == "add": - result = sum(numbers) - elif operation == "subtract": - result = numbers[0] - sum(numbers[1:]) - elif operation == "multiply": - result = 1 - for n in numbers: - result *= n - elif operation == "divide": - result = numbers[0] - for n in numbers[1:]: - result /= n - - # Return properly structured Content::Text variant - return [ - { - "type": "text", - "text": str(result), - "annotations": None, # Required field in Rust struct - } - ] - except Exception as e: - return [ - { - "type": "text", - "text": f"Error: {str(e)}", - "annotations": None, # Required field in Rust struct - } - ] - -def get_tools() -> Dict[str, Any]: - with httpx.Client() as client: - response = client.get( - f"{GOOSE_URL}/agent/tools", - headers={"X-Secret-Key": SECRET_KEY}, - ) - response.raise_for_status() - return response.json() - - -def execute_enable_extension(args: Dict[str, Any]) -> List[Dict[str, Any]]: - """ - Execute the enable_extension tool. - This function fetches available extensions, finds the one with the provided extension_name, - and posts its configuration to the /extensions/add endpoint. - """ - extension = args - extension_name = extension.get("name") - - # Post the extension configuration to enable it - with httpx.Client() as client: - payload = { - "type": extension.get("type"), - "name": extension.get("name"), - "cmd": extension.get("cmd"), - "args": extension.get("args"), - "envs": extension.get("envs", {}), - "timeout": extension.get("timeout"), - "bundled": extension.get("bundled"), - } - add_response = client.post( - f"{GOOSE_URL}/extensions/add", - json=payload, - headers={"Content-Type": "application/json", "X-Secret-Key": SECRET_KEY}, - ) - if add_response.status_code != 200: - error_text = add_response.text - return [{ - "type": "text", - "text": f"Error: Failed to enable extension: {error_text}", - "annotations": None, - }] - - return [{ - "type": "text", - "text": f"Successfully enabled extension: {extension_name}", - "annotations": None, - }] - - -def submit_tool_result(tool_id: str, result: List[Dict[str, Any]]) -> None: - """Submit the tool execution result back to Goose. - - The result should be a list of Content variants (Text, Image, or Resource). - Each Content variant has a type tag and appropriate fields. - """ - payload = { - "id": tool_id, - "result": { - "Ok": result # Result enum variant with single key for success case - }, - } - - with httpx.Client(timeout=2.0) as client: - response = client.post( - f"{GOOSE_URL}/tool_result", - json=payload, - headers={"X-Secret-Key": SECRET_KEY}, - ) - response.raise_for_status() - - -async def chat_loop() -> None: - """Main chat loop that handles the conversation with Goose.""" - session_id = "test-session" - - # Use a client with a longer timeout for streaming - async with httpx.AsyncClient(timeout=60.0) as client: - # Get user input - user_message = input("\nYou: ") - if user_message.lower() in ["exit", "quit"]: - return - - # Create the message object - message = { - "role": "user", - "created": int(datetime.now().timestamp()), - "content": [{"type": "text", "text": user_message}], - } - - # Send to /reply endpoint - payload = { - "messages": [message], - "session_id": session_id, - "session_working_dir": os.getcwd(), - } - - # Process the stream of responses - async with client.stream( - "POST", - f"{GOOSE_URL}/reply", # lock - json=payload, - headers={ - "X-Secret-Key": SECRET_KEY, - "Accept": "text/event-stream", - "Content-Type": "application/json", - }, - ) as stream: - async for line in stream.aiter_lines(): - if not line: - continue - - # Handle SSE format - if line.startswith("data: "): - line = line[6:] # Remove "data: " prefix - - try: - payload = json.loads(line) - except json.JSONDecodeError: - print(f"Failed to parse line: {line}") - continue - - if payload["type"] == "Finish": - break - - message = payload["message"] - # Handle different message types - for content in message.get("content", []): - if content["type"] == "text": - print(f"\nGoose: {content['text']}") - elif content["type"] == "frontendToolRequest": - # Execute the tool and submit results - tool_call = content["toolCall"]["value"] - print(f"\nTool Request: {tool_call}") - - if tool_call['name'] == "calculator": - print(f"Calculator: {tool_call}") - # Execute the tool - result = execute_calculator(tool_call["arguments"]) - elif tool_call['name'] == "enable_extension": - # to trigger this tool, use the instruction "use enable_extension tool with "fetch" extension name" - print(f"Enabling fetch extension") - result = execute_enable_extension(args={ - "type": "stdio", - "name": "fetch", - "cmd": "uvx", - "args": ["mcp-server-fetch"], - "timeout": 300, - "bundled": False - }) - listed_tools = get_tools() - print(f"\nTools after enabling extension: {listed_tools}") - - - # Submit the result - submit_tool_result(content["id"], result) - - -async def main(): - try: - # Initialize the agent with our tool - await setup_agent() - - # Start the chat loop - await chat_loop() - - except Exception as e: - print(f"Error: {e}") - raise # Re-raise to see full traceback - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/ui/desktop/src/acp/extensions.ts b/ui/desktop/src/acp/extensions.ts index 8761d6291..5586e8010 100644 --- a/ui/desktop/src/acp/extensions.ts +++ b/ui/desktop/src/acp/extensions.ts @@ -144,10 +144,6 @@ export function extensionConfigToGooseExtension(config: ExtensionConfig): GooseE bundled: config.bundled, available_tools: availableToolsOrUndefined(config.available_tools), }; - case 'sse': - case 'frontend': - case 'inline_python': - return null; } } diff --git a/ui/desktop/src/components/settings/extensions/modal/ExtensionConfigFields.tsx b/ui/desktop/src/components/settings/extensions/modal/ExtensionConfigFields.tsx index c85e29311..6262fbe3b 100644 --- a/ui/desktop/src/components/settings/extensions/modal/ExtensionConfigFields.tsx +++ b/ui/desktop/src/components/settings/extensions/modal/ExtensionConfigFields.tsx @@ -29,7 +29,7 @@ const i18n = defineMessages({ }); interface ExtensionConfigFieldsProps { - type: 'stdio' | 'sse' | 'streamable_http' | 'builtin'; + type: 'stdio' | 'streamable_http' | 'builtin'; full_cmd: string; endpoint: string; onChange: (key: string, value: string) => void; @@ -51,7 +51,9 @@ export default function ExtensionConfigFields({ return (
- +
{submitAttempted && !isValid && ( -
{intl.formatMessage(i18n.commandRequired)}
+
+ {intl.formatMessage(i18n.commandRequired)} +
)}
@@ -69,7 +73,9 @@ export default function ExtensionConfigFields({ } else { return (
- +
{submitAttempted && !isValid && ( -
{intl.formatMessage(i18n.endpointRequired)}
+
+ {intl.formatMessage(i18n.endpointRequired)} +
)}
diff --git a/ui/desktop/src/components/settings/extensions/modal/ExtensionInfoFields.tsx b/ui/desktop/src/components/settings/extensions/modal/ExtensionInfoFields.tsx index ba178968e..0753a8278 100644 --- a/ui/desktop/src/components/settings/extensions/modal/ExtensionInfoFields.tsx +++ b/ui/desktop/src/components/settings/extensions/modal/ExtensionInfoFields.tsx @@ -27,10 +27,6 @@ const i18n = defineMessages({ id: 'extensionInfoFields.typeHttp', defaultMessage: 'HTTP', }, - typeSseUnsupported: { - id: 'extensionInfoFields.typeSseUnsupported', - defaultMessage: 'SSE (unsupported)', - }, typeStandardIo: { id: 'extensionInfoFields.typeStandardIo', defaultMessage: 'Standard IO (STDIO)', @@ -51,7 +47,7 @@ const i18n = defineMessages({ interface ExtensionInfoFieldsProps { name: string; - type: 'stdio' | 'sse' | 'streamable_http' | 'builtin'; + type: 'stdio' | 'streamable_http' | 'builtin'; description: string; onChange: (key: string, value: string) => void; submitAttempted: boolean; @@ -75,7 +71,9 @@ export default function ExtensionInfoFields({ {/* Top row with Name and Type side by side */}
- +
{submitAttempted && !isNameValid() && ( -
{intl.formatMessage(i18n.nameRequired)}
+
+ {intl.formatMessage(i18n.nameRequired)} +
)}
{/* Type Dropdown */}
- + { return ( (formData.type === 'stdio' && !!formData.cmd && formData.cmd.trim() !== '') || - (formData.type === 'sse' && !!formData.endpoint && formData.endpoint.trim() !== '') || (formData.type === 'streamable_http' && !!formData.endpoint && formData.endpoint.trim() !== '') @@ -373,7 +372,9 @@ export default function ExtensionModal({ }; // Update title based on current state - const modalTitle = showDeleteConfirmation ? intl.formatMessage(i18n.deleteExtensionTitle, { name: formData.name }) : title; + const modalTitle = showDeleteConfirmation + ? intl.formatMessage(i18n.deleteExtensionTitle, { name: formData.name }) + : title; return ( <> @@ -385,17 +386,13 @@ export default function ExtensionModal({ {modalTitle} {showDeleteConfirmation && ( - - {intl.formatMessage(i18n.deleteDescription)} - + {intl.formatMessage(i18n.deleteDescription)} )} {showDeleteConfirmation ? (
-

- {intl.formatMessage(i18n.deleteDescription)} -

+

{intl.formatMessage(i18n.deleteDescription)}

) : (
diff --git a/ui/desktop/src/components/settings/extensions/subcomponents/ExtensionList.tsx b/ui/desktop/src/components/settings/extensions/subcomponents/ExtensionList.tsx index e9e3d92d5..ed0963fa9 100644 --- a/ui/desktop/src/components/settings/extensions/subcomponents/ExtensionList.tsx +++ b/ui/desktop/src/components/settings/extensions/subcomponents/ExtensionList.tsx @@ -152,11 +152,9 @@ export function getSubtitle(config: ExtensionConfig) { command: null, }; } - case 'sse': case 'streamable_http': { - const label = config.type === 'sse' ? 'SSE' : 'HTTP'; return { - description: config.description ? `${label}: ${config.description}` : `${label} extension`, + description: config.description ? `HTTP: ${config.description}` : 'HTTP extension', command: config.uri || null, }; } diff --git a/ui/desktop/src/components/settings/extensions/utils.test.ts b/ui/desktop/src/components/settings/extensions/utils.test.ts index 051b332d3..7b913ada2 100644 --- a/ui/desktop/src/components/settings/extensions/utils.test.ts +++ b/ui/desktop/src/components/settings/extensions/utils.test.ts @@ -402,29 +402,6 @@ describe('Extension Utils', () => { }); }); - it('should create sse extension config', () => { - const formData = { - name: 'test-sse', - description: 'Test SSE extension', - type: 'sse' as const, - cmd: '', - endpoint: 'http://api.example.com/sse', - enabled: true, - timeout: 300, - envVars: [], - headers: [], - }; - - const config = createExtensionConfig(formData); - - expect(config).toEqual({ - type: 'sse', - name: 'test-sse', - description: 'Test SSE extension', - uri: 'http://api.example.com/sse', - }); - }); - it('should preserve available tools metadata', () => { const formData = { name: 'developer', diff --git a/ui/desktop/src/components/settings/extensions/utils.ts b/ui/desktop/src/components/settings/extensions/utils.ts index d3fcd6b89..faab9e012 100644 --- a/ui/desktop/src/components/settings/extensions/utils.ts +++ b/ui/desktop/src/components/settings/extensions/utils.ts @@ -21,7 +21,7 @@ export function nameToKey(name: string): string { export interface ExtensionFormData { name: string; description: string; - type: 'stdio' | 'sse' | 'streamable_http' | 'builtin'; + type: 'stdio' | 'streamable_http' | 'builtin'; cmd?: string; endpoint?: string; enabled: boolean; @@ -109,27 +109,18 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo return { name: extension.name || '', description: extension.description || '', - type: - extension.type === 'frontend' || - extension.type === 'inline_python' || - extension.type === 'platform' - ? 'stdio' - : extension.type, + type: extension.type === 'platform' ? 'stdio' : extension.type, cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args ?? []) : undefined, - endpoint: - extension.type === 'streamable_http' || extension.type === 'sse' - ? (extension.uri ?? undefined) - : undefined, + endpoint: extension.type === 'streamable_http' ? (extension.uri ?? undefined) : undefined, enabled: extension.enabled, timeout: 'timeout' in extension ? (extension.timeout ?? undefined) : undefined, envVars, headers, installation_notes: (extension as Record)['installation_notes'] as - | string - | undefined, + string | undefined, ...(availableTools ? { available_tools: availableTools } : {}), ...(extension.type === 'streamable_http' ? { @@ -197,22 +188,15 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon : {}), ...(formData.scopes?.length ? { scopes: formData.scopes } : {}), }; - } else if (formData.type === 'builtin') { - return { - type: formData.type, - name: formData.name, - description: formData.description, - timeout: formData.timeout, - ...availableToolsConfig(formData.available_tools), - }; - } else { - return { - type: formData.type, - name: formData.name, - description: formData.description, - uri: formData.endpoint || '', - }; } + + return { + type: formData.type, + name: formData.name, + description: formData.description, + timeout: formData.timeout, + ...availableToolsConfig(formData.available_tools), + }; } function isWindowsPlatform(): boolean { diff --git a/ui/desktop/src/i18n/messages/de.json b/ui/desktop/src/i18n/messages/de.json index a2fb58691..37734d3ec 100644 --- a/ui/desktop/src/i18n/messages/de.json +++ b/ui/desktop/src/i18n/messages/de.json @@ -1053,9 +1053,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Typ" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (nicht unterstützt)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/en.json b/ui/desktop/src/i18n/messages/en.json index e781d4a2d..b830fa970 100644 --- a/ui/desktop/src/i18n/messages/en.json +++ b/ui/desktop/src/i18n/messages/en.json @@ -1061,9 +1061,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Type" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (unsupported)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/es.json b/ui/desktop/src/i18n/messages/es.json index d232c9eba..39437de38 100644 --- a/ui/desktop/src/i18n/messages/es.json +++ b/ui/desktop/src/i18n/messages/es.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Tipo" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (no compatible)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/fr.json b/ui/desktop/src/i18n/messages/fr.json index 68075696d..d0651a5af 100644 --- a/ui/desktop/src/i18n/messages/fr.json +++ b/ui/desktop/src/i18n/messages/fr.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Type" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (non pris en charge)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Entrée/sortie standard (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/hi.json b/ui/desktop/src/i18n/messages/hi.json index 5703d29b7..c61c0e26f 100644 --- a/ui/desktop/src/i18n/messages/hi.json +++ b/ui/desktop/src/i18n/messages/hi.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "प्रकार" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (असमर्थित)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "मानक आईओ (एसटीडीआईओ)" }, diff --git a/ui/desktop/src/i18n/messages/id.json b/ui/desktop/src/i18n/messages/id.json index 4da1ae58f..006dd8658 100644 --- a/ui/desktop/src/i18n/messages/id.json +++ b/ui/desktop/src/i18n/messages/id.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Tipe" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (tidak didukung)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/it.json b/ui/desktop/src/i18n/messages/it.json index 1a21e941a..827f43d90 100644 --- a/ui/desktop/src/i18n/messages/it.json +++ b/ui/desktop/src/i18n/messages/it.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Tipo" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (non supportato)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/ja.json b/ui/desktop/src/i18n/messages/ja.json index 00139a7b0..625549955 100644 --- a/ui/desktop/src/i18n/messages/ja.json +++ b/ui/desktop/src/i18n/messages/ja.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "タイプ" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE(未対応)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "標準入出力(STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/ko.json b/ui/desktop/src/i18n/messages/ko.json index 6364949ca..d912eaee9 100644 --- a/ui/desktop/src/i18n/messages/ko.json +++ b/ui/desktop/src/i18n/messages/ko.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "유형" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (미지원)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "표준 IO(STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/ms.json b/ui/desktop/src/i18n/messages/ms.json index 60a895672..c72329955 100644 --- a/ui/desktop/src/i18n/messages/ms.json +++ b/ui/desktop/src/i18n/messages/ms.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Jenis" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (tidak disokong)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/pt.json b/ui/desktop/src/i18n/messages/pt.json index fa5f7df0d..13fb15c72 100644 --- a/ui/desktop/src/i18n/messages/pt.json +++ b/ui/desktop/src/i18n/messages/pt.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Tipo" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (não suportado)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/ru.json b/ui/desktop/src/i18n/messages/ru.json index c8c396136..7a7658840 100644 --- a/ui/desktop/src/i18n/messages/ru.json +++ b/ui/desktop/src/i18n/messages/ru.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Тип" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (не поддерживается)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Стандартный ввод-вывод (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/tr.json b/ui/desktop/src/i18n/messages/tr.json index 1d63852d7..4e35c577b 100644 --- a/ui/desktop/src/i18n/messages/tr.json +++ b/ui/desktop/src/i18n/messages/tr.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Tür" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (desteklenmiyor)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standart IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/vi.json b/ui/desktop/src/i18n/messages/vi.json index d70df271e..549c40a78 100644 --- a/ui/desktop/src/i18n/messages/vi.json +++ b/ui/desktop/src/i18n/messages/vi.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "Loại" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE (không được hỗ trợ)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "Standard IO (STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/zh-CN.json b/ui/desktop/src/i18n/messages/zh-CN.json index 0f1fa4130..dc065c79d 100644 --- a/ui/desktop/src/i18n/messages/zh-CN.json +++ b/ui/desktop/src/i18n/messages/zh-CN.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "类型" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE(不支持)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "标准输入输出(STDIO)" }, diff --git a/ui/desktop/src/i18n/messages/zh-TW.json b/ui/desktop/src/i18n/messages/zh-TW.json index eef3e2526..74e14d264 100644 --- a/ui/desktop/src/i18n/messages/zh-TW.json +++ b/ui/desktop/src/i18n/messages/zh-TW.json @@ -1054,9 +1054,6 @@ "extensionInfoFields.typeLabel": { "defaultMessage": "類型" }, - "extensionInfoFields.typeSseUnsupported": { - "defaultMessage": "SSE(不支援)" - }, "extensionInfoFields.typeStandardIo": { "defaultMessage": "標準 IO (STDIO)" }, diff --git a/ui/desktop/src/types/extensions.ts b/ui/desktop/src/types/extensions.ts index 293b59f72..9207d2260 100644 --- a/ui/desktop/src/types/extensions.ts +++ b/ui/desktop/src/types/extensions.ts @@ -2,50 +2,7 @@ import type { RecipeExtensionDto } from '@aaif/goose-sdk'; export type Envs = Record; -type LegacySseExtensionConfig = { - description?: string | null; - name: string; - type: 'sse'; - uri?: string | null; -}; - -type FrontendTool = { - _meta?: Record; - annotations?: Record; - description?: string; - execution?: Record; - icons?: unknown[]; - inputSchema: Record; - name: string; - outputSchema?: Record; - title?: string; -}; - -type FrontendExtensionConfig = { - available_tools?: string[] | null; - bundled?: boolean | null; - description?: string | null; - instructions?: string | null; - name: string; - tools: FrontendTool[]; - type: 'frontend'; -}; - -type InlinePythonExtensionConfig = { - available_tools?: string[] | null; - code: string; - dependencies?: string[] | null; - description?: string | null; - name: string; - timeout?: number | null; - type: 'inline_python'; -}; - -export type ExtensionConfig = - | RecipeExtensionDto - | LegacySseExtensionConfig - | FrontendExtensionConfig - | InlinePythonExtensionConfig; +export type ExtensionConfig = RecipeExtensionDto; export type ExtensionEntry = ExtensionConfig & { enabled: boolean; diff --git a/ui/desktop/src/types/message.ts b/ui/desktop/src/types/message.ts index f05dc610a..2984e793c 100644 --- a/ui/desktop/src/types/message.ts +++ b/ui/desktop/src/types/message.ts @@ -94,10 +94,7 @@ type ContentIcon = { }; export type SystemNotificationType = - | 'thinkingMessage' - | 'progressMessage' - | 'inlineMessage' - | 'creditsExhausted'; + 'thinkingMessage' | 'progressMessage' | 'inlineMessage' | 'creditsExhausted'; export type SystemNotificationContent = { data?: unknown; @@ -131,11 +128,6 @@ export type ActionRequiredData = user_data: unknown; }; -export type FrontendToolRequest = { - id: string; - toolCall: JsonObject; -}; - export type ThinkingContent = { signature: string; thinking: string; @@ -203,7 +195,6 @@ export type MessageContent = | (ToolResponse & { type: 'toolResponse' }) | (ToolConfirmationRequest & { type: 'toolConfirmationRequest' }) | (ActionRequired & { type: 'actionRequired' }) - | (FrontendToolRequest & { type: 'frontendToolRequest' }) | (ThinkingContent & { type: 'thinking' }) | (RedactedThinkingContent & { type: 'redactedThinking' }) | (SystemNotificationContent & { type: 'systemNotification' });