feat: use the same permission flow for enable extensions (#2302)
This commit is contained in:
@@ -3,8 +3,7 @@ use console::style;
|
|||||||
use goose::agents::extension::ToolInfo;
|
use goose::agents::extension::ToolInfo;
|
||||||
use goose::agents::extension_manager::get_parameter_names;
|
use goose::agents::extension_manager::get_parameter_names;
|
||||||
use goose::agents::platform_tools::{
|
use goose::agents::platform_tools::{
|
||||||
PLATFORM_ENABLE_EXTENSION_TOOL_NAME, PLATFORM_LIST_RESOURCES_TOOL_NAME,
|
PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_READ_RESOURCE_TOOL_NAME,
|
||||||
PLATFORM_READ_RESOURCE_TOOL_NAME,
|
|
||||||
};
|
};
|
||||||
use goose::agents::Agent;
|
use goose::agents::Agent;
|
||||||
use goose::agents::{extension::Envs, ExtensionConfig};
|
use goose::agents::{extension::Envs, ExtensionConfig};
|
||||||
@@ -1015,8 +1014,7 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box<dyn Error>> {
|
|||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|tool| {
|
.filter(|tool| {
|
||||||
tool.name != PLATFORM_ENABLE_EXTENSION_TOOL_NAME
|
tool.name != PLATFORM_LIST_RESOURCES_TOOL_NAME
|
||||||
&& tool.name != PLATFORM_LIST_RESOURCES_TOOL_NAME
|
|
||||||
&& tool.name != PLATFORM_READ_RESOURCE_TOOL_NAME
|
&& tool.name != PLATFORM_READ_RESOURCE_TOOL_NAME
|
||||||
})
|
})
|
||||||
.map(|tool| {
|
.map(|tool| {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use completion::GooseCompleter;
|
|||||||
use etcetera::choose_app_strategy;
|
use etcetera::choose_app_strategy;
|
||||||
use etcetera::AppStrategy;
|
use etcetera::AppStrategy;
|
||||||
use goose::agents::extension::{Envs, ExtensionConfig};
|
use goose::agents::extension::{Envs, ExtensionConfig};
|
||||||
use goose::agents::platform_tools::PLATFORM_ENABLE_EXTENSION_TOOL_NAME;
|
|
||||||
use goose::agents::{Agent, SessionConfig};
|
use goose::agents::{Agent, SessionConfig};
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
use goose::message::{Message, MessageContent};
|
use goose::message::{Message, MessageContent};
|
||||||
@@ -623,29 +622,6 @@ impl Session {
|
|||||||
principal_type: PrincipalType::Tool,
|
principal_type: PrincipalType::Tool,
|
||||||
permission,
|
permission,
|
||||||
},).await;
|
},).await;
|
||||||
} else if let Some(MessageContent::ExtensionRequest(enable_extension_request)) = message.content.first() {
|
|
||||||
output::hide_thinking();
|
|
||||||
|
|
||||||
let extension_action = if enable_extension_request.tool_name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME {
|
|
||||||
"enable"
|
|
||||||
} else {
|
|
||||||
"disable"
|
|
||||||
};
|
|
||||||
|
|
||||||
let prompt = format!("Goose would like to {} the following extension, do you approve?", extension_action);
|
|
||||||
let confirmed = cliclack::select(prompt)
|
|
||||||
.item(true, "Yes, for this session", format!("{} the extension for this session", extension_action))
|
|
||||||
.item(false, "No", format!("Do not {} the extension", extension_action))
|
|
||||||
.interact()?;
|
|
||||||
let permission = if confirmed {
|
|
||||||
Permission::AllowOnce
|
|
||||||
} else {
|
|
||||||
Permission::DenyOnce
|
|
||||||
};
|
|
||||||
self.agent.handle_confirmation(enable_extension_request.id.clone(), PermissionConfirmation {
|
|
||||||
principal_type: PrincipalType::Extension,
|
|
||||||
permission,
|
|
||||||
},).await;
|
|
||||||
}
|
}
|
||||||
// otherwise we have a model/tool to render
|
// otherwise we have a model/tool to render
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ use tracing::{debug, error, instrument, warn};
|
|||||||
use crate::agents::extension::{ExtensionConfig, ExtensionResult, ToolInfo};
|
use crate::agents::extension::{ExtensionConfig, ExtensionResult, ToolInfo};
|
||||||
use crate::agents::extension_manager::{get_parameter_names, ExtensionManager};
|
use crate::agents::extension_manager::{get_parameter_names, ExtensionManager};
|
||||||
use crate::agents::platform_tools::{
|
use crate::agents::platform_tools::{
|
||||||
PLATFORM_LIST_RESOURCES_TOOL_NAME, PLATFORM_READ_RESOURCE_TOOL_NAME,
|
PLATFORM_ENABLE_EXTENSION_TOOL_NAME, PLATFORM_LIST_RESOURCES_TOOL_NAME,
|
||||||
PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
PLATFORM_READ_RESOURCE_TOOL_NAME, PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
|
||||||
};
|
};
|
||||||
use crate::agents::prompt_manager::PromptManager;
|
use crate::agents::prompt_manager::PromptManager;
|
||||||
use crate::agents::types::SessionConfig;
|
use crate::agents::types::SessionConfig;
|
||||||
@@ -33,9 +33,7 @@ use mcp_core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::platform_tools;
|
use super::platform_tools;
|
||||||
use super::tool_execution::{
|
use super::tool_execution::{ToolFuture, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE};
|
||||||
ExtensionInstallResult, ToolFuture, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_TRUNCATION_ATTEMPTS: usize = 3;
|
const MAX_TRUNCATION_ATTEMPTS: usize = 3;
|
||||||
const ESTIMATE_FACTOR_DECAY: f32 = 0.9;
|
const ESTIMATE_FACTOR_DECAY: f32 = 0.9;
|
||||||
@@ -114,6 +112,16 @@ impl Agent {
|
|||||||
tool_call: mcp_core::tool::ToolCall,
|
tool_call: mcp_core::tool::ToolCall,
|
||||||
request_id: String,
|
request_id: String,
|
||||||
) -> (String, Result<Vec<Content>, ToolError>) {
|
) -> (String, Result<Vec<Content>, ToolError>) {
|
||||||
|
if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME {
|
||||||
|
let extension_name = tool_call
|
||||||
|
.arguments
|
||||||
|
.get("extension_name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
return self.enable_extension(extension_name, request_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
let extension_manager = self.extension_manager.lock().await;
|
let extension_manager = self.extension_manager.lock().await;
|
||||||
let result = if tool_call.name == PLATFORM_READ_RESOURCE_TOOL_NAME {
|
let result = if tool_call.name == PLATFORM_READ_RESOURCE_TOOL_NAME {
|
||||||
// Check if the tool is read_resource and handle it separately
|
// Check if the tool is read_resource and handle it separately
|
||||||
@@ -418,26 +426,26 @@ impl Agent {
|
|||||||
// What remains is handling the remaining tool requests (enable extension,
|
// What remains is handling the remaining tool requests (enable extension,
|
||||||
// regular tool calls) in goose_mode == ["auto", "approve" or "smart_approve"]
|
// regular tool calls) in goose_mode == ["auto", "approve" or "smart_approve"]
|
||||||
let mut permission_manager = PermissionManager::default();
|
let mut permission_manager = PermissionManager::default();
|
||||||
let permission_check_result = check_tool_permissions(&remaining_requests,
|
let (permission_check_result, enable_extension_request_ids) = check_tool_permissions(
|
||||||
&mode,
|
&remaining_requests,
|
||||||
tools_with_readonly_annotation.clone(),
|
&mode,
|
||||||
tools_without_annotation.clone(),
|
tools_with_readonly_annotation.clone(),
|
||||||
&mut permission_manager,
|
tools_without_annotation.clone(),
|
||||||
self.provider()).await;
|
&mut permission_manager,
|
||||||
|
self.provider(),
|
||||||
|
).await;
|
||||||
|
|
||||||
// Handle pre-approved and read-only tools in parallel
|
// Handle pre-approved and read-only tools in parallel
|
||||||
let mut tool_futures: Vec<ToolFuture> = Vec::new();
|
let mut tool_futures: Vec<ToolFuture> = Vec::new();
|
||||||
let mut install_results: Vec<ExtensionInstallResult> = Vec::new();
|
|
||||||
let install_results_arc = Arc::new(Mutex::new(install_results));
|
|
||||||
|
|
||||||
// Skip the confirmation for approved tools
|
// Skip the confirmation for approved tools
|
||||||
for request in &permission_check_result.approved {
|
for request in &permission_check_result.approved {
|
||||||
if let Ok(tool_call) = request.tool_call.clone() {
|
if let Ok(tool_call) = request.tool_call.clone() {
|
||||||
let tool_future = self.dispatch_tool_call(tool_call, request.id.clone());
|
let tool_future = self.dispatch_tool_call(tool_call, request.id.clone());
|
||||||
tool_futures.push(Box::pin(tool_future));
|
tool_futures.push(Box::pin(tool_future));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for request in &permission_check_result.denied {
|
for request in &permission_check_result.denied {
|
||||||
let mut response = message_tool_response.lock().await;
|
let mut response = message_tool_response.lock().await;
|
||||||
*response = response.clone().with_tool_response(
|
*response = response.clone().with_tool_response(
|
||||||
@@ -446,51 +454,42 @@ impl Agent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need interior mutability in handle_approval_tool_requests
|
// We need interior mutability in handle_approval_tool_requests
|
||||||
let tool_futures_arc = Arc::new(Mutex::new(tool_futures));
|
let tool_futures_arc = Arc::new(Mutex::new(tool_futures));
|
||||||
|
|
||||||
// Process tools requiring approval (enable extension, regular tool calls)
|
// Process tools requiring approval (enable extension, regular tool calls)
|
||||||
let mut tool_approval_stream = self.handle_approval_tool_requests(
|
let mut tool_approval_stream = self.handle_approval_tool_requests(
|
||||||
&permission_check_result.needs_approval,
|
&permission_check_result.needs_approval,
|
||||||
install_results_arc.clone(),
|
|
||||||
tool_futures_arc.clone(),
|
tool_futures_arc.clone(),
|
||||||
&mut permission_manager,
|
&mut permission_manager,
|
||||||
message_tool_response.clone()
|
message_tool_response.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// we have a stream of tool_approval_requests to handle
|
// We have a stream of tool_approval_requests to handle
|
||||||
// execution is yeield back to this reply loop, and is of the same Message
|
// Execution is yielded back to this reply loop, and is of the same Message
|
||||||
// type, so we can yield the Message back up to be handled and grab and
|
// type, so we can yield the Message back up to be handled and grab any
|
||||||
// confirmations or denials
|
// confirmations or denials
|
||||||
while let Some(msg) = tool_approval_stream.try_next().await? {
|
while let Some(msg) = tool_approval_stream.try_next().await? {
|
||||||
yield msg;
|
yield msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_futures = {
|
tool_futures = {
|
||||||
// Lock the mutex asynchronously.
|
// Lock the mutex asynchronously
|
||||||
let mut futures_lock = tool_futures_arc.lock().await;
|
let mut futures_lock = tool_futures_arc.lock().await;
|
||||||
// Drain the vector and collect into a new Vec.
|
// Drain the vector and collect into a new Vec
|
||||||
futures_lock.drain(..).collect::<Vec<_>>()
|
futures_lock.drain(..).collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
install_results = {
|
|
||||||
// Lock the mutex asynchronously.
|
|
||||||
let mut results_lock = install_results_arc.lock().await;
|
|
||||||
// Drain the vector and collect into a new Vec.
|
|
||||||
results_lock.drain(..).collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Wait for all tool calls to complete
|
// Wait for all tool calls to complete
|
||||||
let results = futures::future::join_all(tool_futures).await;
|
let results = futures::future::join_all(tool_futures).await;
|
||||||
|
let mut all_install_successful = true;
|
||||||
|
|
||||||
// Check if any install results had errors before processing them
|
for (request_id, output) in results.into_iter() {
|
||||||
let all_install_successful = !install_results.iter().any(|(_, result)| result.is_err());
|
if enable_extension_request_ids.contains(&request_id) && output.is_err(){
|
||||||
for (request_id, output) in results.into_iter().chain(install_results.into_iter()) {
|
all_install_successful = false;
|
||||||
|
}
|
||||||
let mut response = message_tool_response.lock().await;
|
let mut response = message_tool_response.lock().await;
|
||||||
*response = response.clone().with_tool_response(
|
*response = response.clone().with_tool_response(request_id, output);
|
||||||
request_id,
|
|
||||||
output
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update system prompt and tools if installations were successful
|
// Update system prompt and tools if installations were successful
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ use tokio::sync::Mutex;
|
|||||||
use crate::config::permission::PermissionLevel;
|
use crate::config::permission::PermissionLevel;
|
||||||
use crate::config::PermissionManager;
|
use crate::config::PermissionManager;
|
||||||
use crate::message::{Message, ToolRequest};
|
use crate::message::{Message, ToolRequest};
|
||||||
use crate::permission::permission_confirmation::PrincipalType;
|
|
||||||
use crate::permission::permission_judge::get_confirmation_message;
|
|
||||||
use crate::permission::Permission;
|
use crate::permission::Permission;
|
||||||
use mcp_core::{Content, ToolError};
|
use mcp_core::{Content, ToolError};
|
||||||
|
|
||||||
@@ -19,9 +17,6 @@ use mcp_core::{Content, ToolError};
|
|||||||
pub(crate) type ToolFuture<'a> =
|
pub(crate) type ToolFuture<'a> =
|
||||||
Pin<Box<dyn Future<Output = (String, Result<Vec<Content>, ToolError>)> + Send + 'a>>;
|
Pin<Box<dyn Future<Output = (String, Result<Vec<Content>, ToolError>)> + Send + 'a>>;
|
||||||
pub(crate) type ToolFuturesVec<'a> = Arc<Mutex<Vec<ToolFuture<'a>>>>;
|
pub(crate) type ToolFuturesVec<'a> = Arc<Mutex<Vec<ToolFuture<'a>>>>;
|
||||||
// Type alias for extension installation results
|
|
||||||
pub(crate) type ExtensionInstallResult = (String, Result<Vec<Content>, ToolError>);
|
|
||||||
pub(crate) type ExtensionInstallResults = Arc<Mutex<Vec<ExtensionInstallResult>>>;
|
|
||||||
|
|
||||||
use crate::agents::Agent;
|
use crate::agents::Agent;
|
||||||
|
|
||||||
@@ -42,7 +37,6 @@ impl Agent {
|
|||||||
pub(crate) fn handle_approval_tool_requests<'a>(
|
pub(crate) fn handle_approval_tool_requests<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
tool_requests: &'a [ToolRequest],
|
tool_requests: &'a [ToolRequest],
|
||||||
install_results: ExtensionInstallResults,
|
|
||||||
tool_futures: ToolFuturesVec<'a>,
|
tool_futures: ToolFuturesVec<'a>,
|
||||||
permission_manager: &'a mut PermissionManager,
|
permission_manager: &'a mut PermissionManager,
|
||||||
message_tool_response: Arc<Mutex<Message>>,
|
message_tool_response: Arc<Mutex<Message>>,
|
||||||
@@ -50,31 +44,24 @@ impl Agent {
|
|||||||
try_stream! {
|
try_stream! {
|
||||||
for request in tool_requests {
|
for request in tool_requests {
|
||||||
if let Ok(tool_call) = request.tool_call.clone() {
|
if let Ok(tool_call) = request.tool_call.clone() {
|
||||||
let (principal_type, confirmation) = get_confirmation_message(&request.id.clone(), tool_call.clone());
|
let confirmation = Message::user().with_tool_confirmation_request(
|
||||||
|
request.id.clone(),
|
||||||
|
tool_call.name.clone(),
|
||||||
|
tool_call.arguments.clone(),
|
||||||
|
Some("Goose would like to call the above tool. Allow? (y/n):".to_string()),
|
||||||
|
);
|
||||||
yield confirmation;
|
yield confirmation;
|
||||||
|
|
||||||
let mut rx = self.confirmation_rx.lock().await;
|
let mut rx = self.confirmation_rx.lock().await;
|
||||||
while let Some((req_id, confirmation)) = rx.recv().await {
|
while let Some((req_id, confirmation)) = rx.recv().await {
|
||||||
if req_id == request.id {
|
if req_id == request.id {
|
||||||
if confirmation.permission == Permission::AllowOnce || confirmation.permission == Permission::AlwaysAllow {
|
if confirmation.permission == Permission::AllowOnce || confirmation.permission == Permission::AlwaysAllow {
|
||||||
if principal_type == PrincipalType::Extension {
|
let tool_future = self.dispatch_tool_call(tool_call.clone(), request.id.clone());
|
||||||
let extension_name = tool_call.arguments.get("extension_name")
|
let mut futures = tool_futures.lock().await;
|
||||||
.and_then(|v| v.as_str())
|
futures.push(Box::pin(tool_future));
|
||||||
.unwrap_or("")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let mut results = install_results.lock().await;
|
if confirmation.permission == Permission::AlwaysAllow {
|
||||||
let install_result = self.enable_extension(extension_name, request.id.clone()).await;
|
permission_manager.update_user_permission(&tool_call.name, PermissionLevel::AlwaysAllow);
|
||||||
results.push(install_result);
|
|
||||||
} else {
|
|
||||||
// Add this tool call to the futures collection
|
|
||||||
let tool_future = self.dispatch_tool_call(tool_call.clone(), request.id.clone());
|
|
||||||
let mut futures = tool_futures.lock().await;
|
|
||||||
futures.push(Box::pin(tool_future));
|
|
||||||
|
|
||||||
if confirmation.permission == Permission::AlwaysAllow {
|
|
||||||
permission_manager.update_user_permission(&tool_call.name, PermissionLevel::AlwaysAllow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// User declined - add declined response
|
// User declined - add declined response
|
||||||
|
|||||||
@@ -59,14 +59,6 @@ pub struct ToolConfirmationRequest {
|
|||||||
pub prompt: Option<String>,
|
pub prompt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ExtensionRequest {
|
|
||||||
pub id: String,
|
|
||||||
pub extension_name: String,
|
|
||||||
pub tool_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ThinkingContent {
|
pub struct ThinkingContent {
|
||||||
pub thinking: String,
|
pub thinking: String,
|
||||||
@@ -95,7 +87,6 @@ pub enum MessageContent {
|
|||||||
ToolRequest(ToolRequest),
|
ToolRequest(ToolRequest),
|
||||||
ToolResponse(ToolResponse),
|
ToolResponse(ToolResponse),
|
||||||
ToolConfirmationRequest(ToolConfirmationRequest),
|
ToolConfirmationRequest(ToolConfirmationRequest),
|
||||||
ExtensionRequest(ExtensionRequest),
|
|
||||||
FrontendToolRequest(FrontendToolRequest),
|
FrontendToolRequest(FrontendToolRequest),
|
||||||
Thinking(ThinkingContent),
|
Thinking(ThinkingContent),
|
||||||
RedactedThinking(RedactedThinkingContent),
|
RedactedThinking(RedactedThinkingContent),
|
||||||
@@ -145,18 +136,6 @@ impl MessageContent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extension_request<S: Into<String>>(
|
|
||||||
id: S,
|
|
||||||
extension_name: String,
|
|
||||||
tool_name: String,
|
|
||||||
) -> Self {
|
|
||||||
MessageContent::ExtensionRequest(ExtensionRequest {
|
|
||||||
id: id.into(),
|
|
||||||
extension_name,
|
|
||||||
tool_name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn thinking<S1: Into<String>, S2: Into<String>>(thinking: S1, signature: S2) -> Self {
|
pub fn thinking<S1: Into<String>, S2: Into<String>>(thinking: S1, signature: S2) -> Self {
|
||||||
MessageContent::Thinking(ThinkingContent {
|
MessageContent::Thinking(ThinkingContent {
|
||||||
thinking: thinking.into(),
|
thinking: thinking.into(),
|
||||||
@@ -198,14 +177,6 @@ impl MessageContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_extension_request(&self) -> Option<&ExtensionRequest> {
|
|
||||||
if let MessageContent::ExtensionRequest(ref extension_request) = self {
|
|
||||||
Some(extension_request)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_tool_response_text(&self) -> Option<String> {
|
pub fn as_tool_response_text(&self) -> Option<String> {
|
||||||
if let Some(tool_response) = self.as_tool_response() {
|
if let Some(tool_response) = self.as_tool_response() {
|
||||||
if let Ok(contents) = &tool_response.tool_result {
|
if let Ok(contents) = &tool_response.tool_result {
|
||||||
@@ -365,19 +336,6 @@ impl Message {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_extension_request<S: Into<String>>(
|
|
||||||
self,
|
|
||||||
id: S,
|
|
||||||
extension_name: String,
|
|
||||||
tool_name: String,
|
|
||||||
) -> Self {
|
|
||||||
self.with_content(MessageContent::extension_request(
|
|
||||||
id,
|
|
||||||
extension_name,
|
|
||||||
tool_name,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_frontend_tool_request<S: Into<String>>(
|
pub fn with_frontend_tool_request<S: Into<String>>(
|
||||||
self,
|
self,
|
||||||
id: S,
|
id: S,
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ use crate::providers::base::Provider;
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use mcp_core::tool::ToolAnnotations;
|
use mcp_core::tool::ToolAnnotations;
|
||||||
use mcp_core::ToolCall;
|
|
||||||
use mcp_core::{tool::Tool, TextContent};
|
use mcp_core::{tool::Tool, TextContent};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::permission_confirmation::PrincipalType;
|
|
||||||
|
|
||||||
/// Creates the tool definition for checking read-only permissions.
|
/// Creates the tool definition for checking read-only permissions.
|
||||||
fn create_read_only_tool() -> Tool {
|
fn create_read_only_tool() -> Tool {
|
||||||
Tool::new(
|
Tool::new(
|
||||||
@@ -154,36 +151,6 @@ pub async fn detect_read_only_tools(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the boolean value whether the message is enable extension related and
|
|
||||||
/// the cconfirmation message based on the tool call
|
|
||||||
pub fn get_confirmation_message(request_id: &str, tool_call: ToolCall) -> (PrincipalType, Message) {
|
|
||||||
if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME {
|
|
||||||
(
|
|
||||||
PrincipalType::Extension,
|
|
||||||
Message::user().with_extension_request(
|
|
||||||
request_id,
|
|
||||||
tool_call
|
|
||||||
.arguments
|
|
||||||
.get("extension_name")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string(),
|
|
||||||
tool_call.name.clone(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
PrincipalType::Tool,
|
|
||||||
Message::user().with_tool_confirmation_request(
|
|
||||||
request_id,
|
|
||||||
tool_call.name.clone(),
|
|
||||||
tool_call.arguments.clone(),
|
|
||||||
Some("Goose would like to call the above tool. Allow? (y/n):".to_string()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define return structure
|
// Define return structure
|
||||||
pub struct PermissionCheckResult {
|
pub struct PermissionCheckResult {
|
||||||
pub approved: Vec<ToolRequest>,
|
pub approved: Vec<ToolRequest>,
|
||||||
@@ -198,26 +165,24 @@ pub async fn check_tool_permissions(
|
|||||||
tools_without_annotation: HashSet<String>,
|
tools_without_annotation: HashSet<String>,
|
||||||
permission_manager: &mut PermissionManager,
|
permission_manager: &mut PermissionManager,
|
||||||
provider: Arc<dyn Provider>,
|
provider: Arc<dyn Provider>,
|
||||||
) -> PermissionCheckResult {
|
) -> (PermissionCheckResult, Vec<String>) {
|
||||||
let mut approved = vec![];
|
let mut approved = vec![];
|
||||||
let mut needs_approval = vec![];
|
let mut needs_approval = vec![];
|
||||||
let mut denied = vec![];
|
let mut denied = vec![];
|
||||||
let mut llm_detect_candidates = vec![];
|
let mut llm_detect_candidates = vec![];
|
||||||
|
let mut enable_extension_request_ids = vec![];
|
||||||
|
|
||||||
for request in candidate_requests {
|
for request in candidate_requests {
|
||||||
if let Ok(tool_call) = request.tool_call.clone() {
|
if let Ok(tool_call) = request.tool_call.clone() {
|
||||||
// Always ask approval for enable extension tool.
|
|
||||||
if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME {
|
|
||||||
// Insert at the front of the list so that enable extension can be run before other tools.
|
|
||||||
needs_approval.insert(0, request.clone());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode == "chat" {
|
if mode == "chat" {
|
||||||
continue;
|
continue;
|
||||||
} else if mode == "auto" {
|
} else if mode == "auto" {
|
||||||
approved.push(request.clone());
|
approved.push(request.clone());
|
||||||
} else {
|
} else {
|
||||||
|
if tool_call.name == PLATFORM_ENABLE_EXTENSION_TOOL_NAME {
|
||||||
|
enable_extension_request_ids.push(request.id.clone());
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Check user-defined permission
|
// 1. Check user-defined permission
|
||||||
if let Some(level) = permission_manager.get_user_permission(&tool_call.name) {
|
if let Some(level) = permission_manager.get_user_permission(&tool_call.name) {
|
||||||
match level {
|
match level {
|
||||||
@@ -284,11 +249,14 @@ pub async fn check_tool_permissions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PermissionCheckResult {
|
(
|
||||||
approved,
|
PermissionCheckResult {
|
||||||
needs_approval,
|
approved,
|
||||||
denied,
|
needs_approval,
|
||||||
}
|
denied,
|
||||||
|
},
|
||||||
|
enable_extension_request_ids,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -471,7 +439,7 @@ mod tests {
|
|||||||
vec![tool_request_1, tool_request_2, enable_extension];
|
vec![tool_request_1, tool_request_2, enable_extension];
|
||||||
|
|
||||||
// Call the function under test
|
// Call the function under test
|
||||||
let result = check_tool_permissions(
|
let (result, enable_extension_request_ids) = check_tool_permissions(
|
||||||
&candidate_requests,
|
&candidate_requests,
|
||||||
"smart_approve",
|
"smart_approve",
|
||||||
tools_with_readonly_annotation,
|
tools_with_readonly_annotation,
|
||||||
@@ -485,21 +453,13 @@ mod tests {
|
|||||||
assert_eq!(result.approved.len(), 1); // file_reader should be approved
|
assert_eq!(result.approved.len(), 1); // file_reader should be approved
|
||||||
assert_eq!(result.needs_approval.len(), 2); // data_fetcher should need approval
|
assert_eq!(result.needs_approval.len(), 2); // data_fetcher should need approval
|
||||||
assert_eq!(result.denied.len(), 0); // No tool should be denied in this test
|
assert_eq!(result.denied.len(), 0); // No tool should be denied in this test
|
||||||
|
assert_eq!(enable_extension_request_ids.len(), 1);
|
||||||
|
|
||||||
// Ensure the right tools are in the approved and needs_approval lists
|
// Ensure the right tools are in the approved and needs_approval lists
|
||||||
assert!(result.approved.iter().any(|req| req.id == "tool_1"));
|
assert!(result.approved.iter().any(|req| req.id == "tool_1"));
|
||||||
assert!(result.needs_approval.iter().any(|req| req.id == "tool_2"));
|
assert!(result.needs_approval.iter().any(|req| req.id == "tool_2"));
|
||||||
|
assert!(result.needs_approval.iter().any(|req| req.id == "tool_3"));
|
||||||
let tool_0 = result.needs_approval.get(0);
|
assert!(enable_extension_request_ids.iter().any(|id| id == "tool_3"));
|
||||||
assert!(
|
|
||||||
tool_0.is_some(),
|
|
||||||
"Expected at least one tool in needs_approval"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
tool_0.unwrap().id,
|
|
||||||
"tool_3",
|
|
||||||
"PLATFORM_ENABLE_EXTENSION_TOOL_NAME should be the first in needs_approval"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -538,7 +498,7 @@ mod tests {
|
|||||||
let candidate_requests: Vec<ToolRequest> = vec![tool_request_1, tool_request_2];
|
let candidate_requests: Vec<ToolRequest> = vec![tool_request_1, tool_request_2];
|
||||||
|
|
||||||
// Call the function under test
|
// Call the function under test
|
||||||
let result = check_tool_permissions(
|
let (result, _) = check_tool_permissions(
|
||||||
&candidate_requests,
|
&candidate_requests,
|
||||||
"auto",
|
"auto",
|
||||||
tools_with_readonly_annotation,
|
tools_with_readonly_annotation,
|
||||||
|
|||||||
@@ -60,9 +60,6 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
|||||||
MessageContent::ToolConfirmationRequest(_tool_confirmation_request) => {
|
MessageContent::ToolConfirmationRequest(_tool_confirmation_request) => {
|
||||||
// Skip tool confirmation requests
|
// Skip tool confirmation requests
|
||||||
}
|
}
|
||||||
MessageContent::ExtensionRequest(_extension_request) => {
|
|
||||||
// Skip extension requests
|
|
||||||
}
|
|
||||||
MessageContent::Thinking(thinking) => {
|
MessageContent::Thinking(thinking) => {
|
||||||
content.push(json!({
|
content.push(json!({
|
||||||
"type": "thinking",
|
"type": "thinking",
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result<bedrock::C
|
|||||||
MessageContent::ToolConfirmationRequest(_tool_confirmation_request) => {
|
MessageContent::ToolConfirmationRequest(_tool_confirmation_request) => {
|
||||||
bedrock::ContentBlock::Text("".to_string())
|
bedrock::ContentBlock::Text("".to_string())
|
||||||
}
|
}
|
||||||
MessageContent::ExtensionRequest(_extension_request) => {
|
|
||||||
bedrock::ContentBlock::Text("".to_string())
|
|
||||||
}
|
|
||||||
MessageContent::Image(_) => {
|
MessageContent::Image(_) => {
|
||||||
bail!("Image content is not supported by Bedrock provider yet")
|
bail!("Image content is not supported by Bedrock provider yet")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,9 +179,6 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
|||||||
MessageContent::ToolConfirmationRequest(_) => {
|
MessageContent::ToolConfirmationRequest(_) => {
|
||||||
// Skip tool confirmation requests
|
// Skip tool confirmation requests
|
||||||
}
|
}
|
||||||
MessageContent::ExtensionRequest(_) => {
|
|
||||||
// Skip enable extension requests
|
|
||||||
}
|
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
// Handle direct image content
|
// Handle direct image content
|
||||||
content_array.push(json!({
|
content_array.push(json!({
|
||||||
|
|||||||
@@ -147,9 +147,6 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
|||||||
MessageContent::ToolConfirmationRequest(_) => {
|
MessageContent::ToolConfirmationRequest(_) => {
|
||||||
// Skip tool confirmation requests
|
// Skip tool confirmation requests
|
||||||
}
|
}
|
||||||
MessageContent::ExtensionRequest(_) => {
|
|
||||||
// Skip enable extension requests
|
|
||||||
}
|
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
// Handle direct image content
|
// Handle direct image content
|
||||||
converted["content"] = json!([convert_image(image, image_format)]);
|
converted["content"] = json!([convert_image(image, image_format)]);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
ToolRequestMessageContent,
|
ToolRequestMessageContent,
|
||||||
ToolResponseMessageContent,
|
ToolResponseMessageContent,
|
||||||
ToolConfirmationRequestMessageContent,
|
ToolConfirmationRequestMessageContent,
|
||||||
ExtensionRequestMessageContent,
|
|
||||||
} from '../types/message';
|
} from '../types/message';
|
||||||
|
|
||||||
export interface ChatType {
|
export interface ChatType {
|
||||||
@@ -48,9 +47,6 @@ const isUserMessage = (message: Message): boolean => {
|
|||||||
if (message.content.every((c) => c.type === 'toolConfirmationRequest')) {
|
if (message.content.every((c) => c.type === 'toolConfirmationRequest')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (message.content.every((c) => c.type === 'extensionRequest')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -260,14 +256,6 @@ export default function ChatView({
|
|||||||
return [content.id, toolCall];
|
return [content.id, toolCall];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const extensionRequests = lastMessage.content
|
|
||||||
.filter(
|
|
||||||
(content): content is ExtensionRequestMessageContent =>
|
|
||||||
content.type === 'extensionRequest'
|
|
||||||
)
|
|
||||||
.map((content) => {
|
|
||||||
return [content.id, content.extensionCall];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (toolRequests.length !== 0) {
|
if (toolRequests.length !== 0) {
|
||||||
// This means we were interrupted during a tool request
|
// This means we were interrupted during a tool request
|
||||||
@@ -297,30 +285,6 @@ export default function ChatView({
|
|||||||
// Use an immutable update to add the response message to the messages array
|
// Use an immutable update to add the response message to the messages array
|
||||||
setMessages([...messages, responseMessage]);
|
setMessages([...messages, responseMessage]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the same for enable extension requests
|
|
||||||
// leverages toolResponse to send the error notification
|
|
||||||
if (extensionRequests.length !== 0) {
|
|
||||||
let responseMessage: Message = {
|
|
||||||
role: 'user',
|
|
||||||
created: Date.now(),
|
|
||||||
content: [],
|
|
||||||
};
|
|
||||||
const notification = 'Interrupted by the user to make a correction';
|
|
||||||
// generate a response saying it was interrupted for each extension request
|
|
||||||
for (const [reqId, _] of extensionRequests) {
|
|
||||||
const toolResponse: ToolResponseMessageContent = {
|
|
||||||
type: 'toolResponse',
|
|
||||||
id: reqId,
|
|
||||||
toolResult: {
|
|
||||||
status: 'error',
|
|
||||||
error: notification,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
responseMessage.content.push(toolResponse);
|
|
||||||
}
|
|
||||||
setMessages([...messages, responseMessage]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -338,9 +302,8 @@ export default function ChatView({
|
|||||||
(c) => c.type === 'toolConfirmationRequest'
|
(c) => c.type === 'toolConfirmationRequest'
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasExtensionRequest = message.content.every((c) => c.type === 'extensionRequest');
|
|
||||||
// Keep the message if it has text content or tool confirmation or is not just tool responses
|
// Keep the message if it has text content or tool confirmation or is not just tool responses
|
||||||
return hasTextContent || !hasOnlyToolResponses || hasToolConfirmation || hasExtensionRequest;
|
return hasTextContent || !hasOnlyToolResponses || hasToolConfirmation;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { snakeToTitleCase } from '../utils';
|
|
||||||
import { confirmPermission } from '../api';
|
|
||||||
|
|
||||||
interface ExtensionConfirmationProps {
|
|
||||||
isCancelledMessage: boolean;
|
|
||||||
isClicked: boolean;
|
|
||||||
extensionConfirmationId: string;
|
|
||||||
extensionName: string;
|
|
||||||
toolName: string;
|
|
||||||
}
|
|
||||||
export default function ExtensionConfirmation({
|
|
||||||
isCancelledMessage,
|
|
||||||
isClicked,
|
|
||||||
extensionConfirmationId,
|
|
||||||
extensionName,
|
|
||||||
toolName,
|
|
||||||
}: ExtensionConfirmationProps) {
|
|
||||||
const [clicked, setClicked] = useState(isClicked);
|
|
||||||
const [status, setStatus] = useState('unknown');
|
|
||||||
|
|
||||||
const extensionAction = toolName.toLowerCase().includes('enable') ? 'enable' : 'disable';
|
|
||||||
|
|
||||||
const handleButtonClick = async (confirmed: boolean) => {
|
|
||||||
setClicked(true);
|
|
||||||
setStatus(confirmed ? 'approved' : 'denied');
|
|
||||||
try {
|
|
||||||
const response = await confirmPermission({
|
|
||||||
body: {
|
|
||||||
id: extensionConfirmationId,
|
|
||||||
action: confirmed ? 'allow_once' : 'deny',
|
|
||||||
principal_type: 'Extension',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (response.error) {
|
|
||||||
console.error('Failed to confirm permission: ', response.error);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error fetching tools:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return isCancelledMessage ? (
|
|
||||||
<div className="goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 text-textStandard">
|
|
||||||
Extension {extensionAction} is cancelled.
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 rounded-b-none text-textStandard">
|
|
||||||
Goose would like to {extensionAction} the following extension. Allow?
|
|
||||||
</div>
|
|
||||||
{clicked ? (
|
|
||||||
<div className="goose-message-tool bg-bgApp border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 flex gap-4 mt-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
{status === 'approved' && (
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-gray-500"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={2}
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
{status === 'denied' && (
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-gray-500"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={2}
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
<span className="ml-2 text-textStandard">
|
|
||||||
{isClicked
|
|
||||||
? 'Extension enablement is not available'
|
|
||||||
: `${snakeToTitleCase(extensionName.includes('__') ? extensionName.split('__').pop() || extensionName : extensionName)} is ${status}`}{' '}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="goose-message-tool bg-bgApp border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 flex gap-4 mt-1">
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
'bg-black text-white dark:bg-white dark:text-black rounded-full px-6 py-2 transition'
|
|
||||||
}
|
|
||||||
onClick={() => handleButtonClick(true)}
|
|
||||||
>
|
|
||||||
{extensionAction.charAt(0).toUpperCase() + extensionAction.slice(1).toLowerCase()}{' '}
|
|
||||||
extension
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
'bg-white text-black dark:bg-black dark:text-white border border-gray-300 dark:border-gray-700 rounded-full px-6 py-2 transition'
|
|
||||||
}
|
|
||||||
onClick={() => handleButtonClick(false)}
|
|
||||||
>
|
|
||||||
Deny
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -12,11 +12,9 @@ import {
|
|||||||
getToolResponses,
|
getToolResponses,
|
||||||
getToolConfirmationContent,
|
getToolConfirmationContent,
|
||||||
createToolErrorResponseMessage,
|
createToolErrorResponseMessage,
|
||||||
getExtensionContent,
|
|
||||||
} from '../types/message';
|
} from '../types/message';
|
||||||
import ToolCallConfirmation from './ToolCallConfirmation';
|
import ToolCallConfirmation from './ToolCallConfirmation';
|
||||||
import MessageCopyLink from './MessageCopyLink';
|
import MessageCopyLink from './MessageCopyLink';
|
||||||
import ExtensionConfirmation from './ExtensionConfirmation';
|
|
||||||
|
|
||||||
interface GooseMessageProps {
|
interface GooseMessageProps {
|
||||||
messageHistoryIndex: number;
|
messageHistoryIndex: number;
|
||||||
@@ -58,9 +56,6 @@ export default function GooseMessage({
|
|||||||
const toolConfirmationContent = getToolConfirmationContent(message);
|
const toolConfirmationContent = getToolConfirmationContent(message);
|
||||||
const hasToolConfirmation = toolConfirmationContent !== undefined;
|
const hasToolConfirmation = toolConfirmationContent !== undefined;
|
||||||
|
|
||||||
const extensionContent = getExtensionContent(message);
|
|
||||||
const hasExtensionRequest = extensionContent !== undefined;
|
|
||||||
|
|
||||||
// Find tool responses that correspond to the tool requests in this message
|
// Find tool responses that correspond to the tool requests in this message
|
||||||
const toolResponsesMap = useMemo(() => {
|
const toolResponsesMap = useMemo(() => {
|
||||||
const responseMap = new Map();
|
const responseMap = new Map();
|
||||||
@@ -95,23 +90,12 @@ export default function GooseMessage({
|
|||||||
createToolErrorResponseMessage(toolConfirmationContent.id, 'The tool call is cancelled.')
|
createToolErrorResponseMessage(toolConfirmationContent.id, 'The tool call is cancelled.')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (messageIndex == messageHistoryIndex - 1 && hasExtensionRequest) {
|
|
||||||
appendMessage(
|
|
||||||
createToolErrorResponseMessage(
|
|
||||||
extensionContent.id,
|
|
||||||
'The extension enablement is cancelled.'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [
|
}, [
|
||||||
messageIndex,
|
messageIndex,
|
||||||
messageHistoryIndex,
|
messageHistoryIndex,
|
||||||
hasToolConfirmation,
|
hasToolConfirmation,
|
||||||
toolConfirmationContent,
|
toolConfirmationContent,
|
||||||
appendMessage,
|
appendMessage,
|
||||||
hasExtensionRequest,
|
|
||||||
// Only include enableExtensionContent if it exists
|
|
||||||
extensionContent?.id,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -172,16 +156,6 @@ export default function GooseMessage({
|
|||||||
toolName={toolConfirmationContent.toolName}
|
toolName={toolConfirmationContent.toolName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasExtensionRequest && (
|
|
||||||
<ExtensionConfirmation
|
|
||||||
isCancelledMessage={messageIndex == messageHistoryIndex - 1}
|
|
||||||
isClicked={messageIndex < messageHistoryIndex - 1}
|
|
||||||
extensionConfirmationId={extensionContent.id}
|
|
||||||
extensionName={extensionContent.extensionName}
|
|
||||||
toolName={extensionContent.toolName}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* TODO(alexhancock): Re-enable link previews once styled well again */}
|
{/* TODO(alexhancock): Re-enable link previews once styled well again */}
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo
|
|||||||
} else {
|
} else {
|
||||||
const filteredTools = (response.data || []).filter(
|
const filteredTools = (response.data || []).filter(
|
||||||
(tool) =>
|
(tool) =>
|
||||||
tool.name !== 'platform__enable_extension' &&
|
tool.name !== 'platform__read_resource' && tool.name !== 'platform__list_resources'
|
||||||
tool.name !== 'platform__read_resource' &&
|
|
||||||
tool.name !== 'platform__list_resources'
|
|
||||||
);
|
);
|
||||||
setTools(filteredTools);
|
setTools(filteredTools);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ export default function PermissionSettingsView({ onClose }: { onClose: () => voi
|
|||||||
const extensionsList = await getExtensions(true); // Force refresh
|
const extensionsList = await getExtensions(true); // Force refresh
|
||||||
// Filter out disabled extensions
|
// Filter out disabled extensions
|
||||||
const enabledExtensions = extensionsList.filter((extension) => extension.enabled);
|
const enabledExtensions = extensionsList.filter((extension) => extension.enabled);
|
||||||
|
enabledExtensions.push({
|
||||||
|
name: 'platform',
|
||||||
|
type: 'builtin',
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
// Sort extensions by name to maintain consistent order
|
// Sort extensions by name to maintain consistent order
|
||||||
const sortedExtensions = [...enabledExtensions].sort((a, b) => {
|
const sortedExtensions = [...enabledExtensions].sort((a, b) => {
|
||||||
// First sort by builtin
|
// First sort by builtin
|
||||||
|
|||||||
@@ -41,13 +41,6 @@ export interface ToolResponse {
|
|||||||
toolResult: ToolCallResult<Content[]>;
|
toolResult: ToolCallResult<Content[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolConfirmationRequest {
|
|
||||||
id: string;
|
|
||||||
toolName: string;
|
|
||||||
arguments: Record<string, unknown>;
|
|
||||||
prompt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolRequestMessageContent {
|
export interface ToolRequestMessageContent {
|
||||||
type: 'toolRequest';
|
type: 'toolRequest';
|
||||||
id: string;
|
id: string;
|
||||||
@@ -80,33 +73,12 @@ export interface ExtensionCallResult<T> {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionRequest {
|
|
||||||
id: string;
|
|
||||||
extensionCall: ExtensionCallResult<ExtensionCall>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExtensionConfirmationRequest {
|
|
||||||
id: string;
|
|
||||||
extensionName: string;
|
|
||||||
arguments: Record<string, unknown>;
|
|
||||||
prompt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExtensionRequestMessageContent {
|
|
||||||
type: 'extensionRequest';
|
|
||||||
id: string;
|
|
||||||
extensionCall: ExtensionCallResult<ExtensionCall>;
|
|
||||||
extensionName: string;
|
|
||||||
toolName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MessageContent =
|
export type MessageContent =
|
||||||
| TextContent
|
| TextContent
|
||||||
| ImageContent
|
| ImageContent
|
||||||
| ToolRequestMessageContent
|
| ToolRequestMessageContent
|
||||||
| ToolResponseMessageContent
|
| ToolResponseMessageContent
|
||||||
| ToolConfirmationRequestMessageContent
|
| ToolConfirmationRequestMessageContent;
|
||||||
| ExtensionRequestMessageContent;
|
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -220,12 +192,6 @@ export function getToolResponses(message: Message): ToolResponseMessageContent[]
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtensionRequests(message: Message): ExtensionRequestMessageContent[] {
|
|
||||||
return message.content.filter(
|
|
||||||
(content): content is ExtensionRequestMessageContent => content.type === 'extensionRequest'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getToolConfirmationContent(
|
export function getToolConfirmationContent(
|
||||||
message: Message
|
message: Message
|
||||||
): ToolConfirmationRequestMessageContent {
|
): ToolConfirmationRequestMessageContent {
|
||||||
@@ -235,12 +201,6 @@ export function getToolConfirmationContent(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtensionContent(message: Message): ExtensionRequestMessageContent {
|
|
||||||
return message.content.find(
|
|
||||||
(content): content is ExtensionRequestMessageContent => content.type === 'extensionRequest'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasCompletedToolCalls(message: Message): boolean {
|
export function hasCompletedToolCalls(message: Message): boolean {
|
||||||
const toolRequests = getToolRequests(message);
|
const toolRequests = getToolRequests(message);
|
||||||
if (toolRequests.length === 0) return false;
|
if (toolRequests.length === 0) return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user