feat: ActionRequired (#5897)
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
use goose::conversation::message::{Message, MessageContent, ToolRequest, ToolResponse};
|
use goose::conversation::message::{
|
||||||
|
ActionRequiredData, Message, MessageContent, ToolRequest, ToolResponse,
|
||||||
|
};
|
||||||
use goose::utils::safe_truncate;
|
use goose::utils::safe_truncate;
|
||||||
use rmcp::model::{RawContent, ResourceContents, Role};
|
use rmcp::model::{RawContent, ResourceContents, Role};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -340,6 +342,14 @@ pub fn message_to_markdown(message: &Message, export_all_content: bool) -> Strin
|
|||||||
let mut md = String::new();
|
let mut md = String::new();
|
||||||
for content in &message.content {
|
for content in &message.content {
|
||||||
match content {
|
match content {
|
||||||
|
MessageContent::ActionRequired(action) => match &action.data {
|
||||||
|
ActionRequiredData::ToolConfirmation { tool_name, .. } => {
|
||||||
|
md.push_str(&format!(
|
||||||
|
"**Action Required** (tool_confirmation): {}\n\n",
|
||||||
|
tool_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
MessageContent::Text(text) => {
|
MessageContent::Text(text) => {
|
||||||
md.push_str(&text.text);
|
md.push_str(&text.text);
|
||||||
md.push_str("\n\n");
|
md.push_str("\n\n");
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ use rmcp::model::ServerNotification;
|
|||||||
use rmcp::model::{ErrorCode, ErrorData};
|
use rmcp::model::{ErrorCode, ErrorData};
|
||||||
|
|
||||||
use goose::config::paths::Paths;
|
use goose::config::paths::Paths;
|
||||||
use goose::conversation::message::{Message, MessageContent};
|
use goose::conversation::message::{ActionRequiredData, Message, MessageContent};
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
use rustyline::EditMode;
|
use rustyline::EditMode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -852,12 +852,24 @@ impl CliSession {
|
|||||||
result = stream.next() => {
|
result = stream.next() => {
|
||||||
match result {
|
match result {
|
||||||
Some(Ok(AgentEvent::Message(message))) => {
|
Some(Ok(AgentEvent::Message(message))) => {
|
||||||
// If it's a confirmation request, get approval but otherwise do not render/persist
|
let tool_call_confirmation = message.content.iter().find_map(|content| {
|
||||||
if let Some(MessageContent::ToolConfirmationRequest(confirmation)) = message.content.first() {
|
if let MessageContent::ActionRequired(action) = content {
|
||||||
|
#[allow(irrefutable_let_patterns)] // this is a one variant enum right now but it will have more
|
||||||
|
if let ActionRequiredData::ToolConfirmation { id, tool_name, arguments, prompt } = &action.data {
|
||||||
|
Some((id.clone(), tool_name.clone(), arguments.clone(), prompt.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((id, _tool_name, _arguments, security_prompt)) = tool_call_confirmation {
|
||||||
output::hide_thinking();
|
output::hide_thinking();
|
||||||
|
|
||||||
// Format the confirmation prompt - use security message if present, otherwise use generic message
|
// Format the confirmation prompt - use security message if present, otherwise use generic message
|
||||||
let prompt = if let Some(security_message) = &confirmation.prompt {
|
let prompt = if let Some(security_message) = &security_prompt {
|
||||||
println!("\n{}", security_message);
|
println!("\n{}", security_message);
|
||||||
"Do you allow this tool call?".to_string()
|
"Do you allow this tool call?".to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -865,7 +877,7 @@ impl CliSession {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get confirmation from user
|
// Get confirmation from user
|
||||||
let permission_result = if confirmation.prompt.is_none() {
|
let permission_result = if security_prompt.is_none() {
|
||||||
// No security message - show all options including "Always Allow"
|
// No security message - show all options including "Always Allow"
|
||||||
cliclack::select(prompt)
|
cliclack::select(prompt)
|
||||||
.item(Permission::AllowOnce, "Allow", "Allow the tool call once")
|
.item(Permission::AllowOnce, "Allow", "Allow the tool call once")
|
||||||
@@ -883,13 +895,12 @@ impl CliSession {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let permission = match permission_result {
|
let permission = match permission_result {
|
||||||
Ok(p) => p, // If Ok, use the selected permission
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Check if the error is an interruption (Ctrl+C/Cmd+C, Escape)
|
|
||||||
if e.kind() == std::io::ErrorKind::Interrupted {
|
if e.kind() == std::io::ErrorKind::Interrupted {
|
||||||
Permission::Cancel // If interrupted, set permission to Cancel
|
Permission::Cancel
|
||||||
} else {
|
} else {
|
||||||
return Err(e.into()); // Otherwise, convert and propagate the original error
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -899,7 +910,7 @@ impl CliSession {
|
|||||||
|
|
||||||
let mut response_message = Message::user();
|
let mut response_message = Message::user();
|
||||||
response_message.content.push(MessageContent::tool_response(
|
response_message.content.push(MessageContent::tool_response(
|
||||||
confirmation.id.clone(),
|
id.clone(),
|
||||||
Err(ErrorData { code: ErrorCode::INVALID_REQUEST, message: std::borrow::Cow::from("Tool call cancelled by user".to_string()), data: None })
|
Err(ErrorData { code: ErrorCode::INVALID_REQUEST, message: std::borrow::Cow::from("Tool call cancelled by user".to_string()), data: None })
|
||||||
));
|
));
|
||||||
self.messages.push(response_message);
|
self.messages.push(response_message);
|
||||||
@@ -907,10 +918,10 @@ impl CliSession {
|
|||||||
drop(stream);
|
drop(stream);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
self.agent.handle_confirmation(confirmation.id.clone(), PermissionConfirmation {
|
self.agent.handle_confirmation(id.clone(), PermissionConfirmation {
|
||||||
principal_type: PrincipalType::Tool,
|
principal_type: PrincipalType::Tool,
|
||||||
permission,
|
permission,
|
||||||
},).await;
|
}).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ use anstream::println;
|
|||||||
use bat::WrappingMode;
|
use bat::WrappingMode;
|
||||||
use console::{measure_text_width, style, Color, Term};
|
use console::{measure_text_width, style, Color, Term};
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
use goose::conversation::message::{Message, MessageContent, ToolRequest, ToolResponse};
|
use goose::conversation::message::{
|
||||||
|
ActionRequiredData, Message, MessageContent, ToolRequest, ToolResponse,
|
||||||
|
};
|
||||||
use goose::providers::pricing::get_model_pricing;
|
use goose::providers::pricing::get_model_pricing;
|
||||||
use goose::providers::pricing::parse_model_id;
|
use goose::providers::pricing::parse_model_id;
|
||||||
use goose::utils::safe_truncate;
|
use goose::utils::safe_truncate;
|
||||||
@@ -166,6 +168,11 @@ pub fn render_message(message: &Message, debug: bool) {
|
|||||||
|
|
||||||
for content in &message.content {
|
for content in &message.content {
|
||||||
match content {
|
match content {
|
||||||
|
MessageContent::ActionRequired(action) => match &action.data {
|
||||||
|
ActionRequiredData::ToolConfirmation { tool_name, .. } => {
|
||||||
|
println!("action_required(tool_confirmation): {}", tool_name)
|
||||||
|
}
|
||||||
|
},
|
||||||
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
||||||
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
|
MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug),
|
||||||
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
|
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug),
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ use goose::config::declarative_providers::{
|
|||||||
DeclarativeProviderConfig, LoadedProvider, ProviderEngine,
|
DeclarativeProviderConfig, LoadedProvider, ProviderEngine,
|
||||||
};
|
};
|
||||||
use goose::conversation::message::{
|
use goose::conversation::message::{
|
||||||
FrontendToolRequest, Message, MessageContent, MessageMetadata, RedactedThinkingContent,
|
ActionRequired, ActionRequiredData, FrontendToolRequest, Message, MessageContent,
|
||||||
SystemNotificationContent, SystemNotificationType, ThinkingContent, TokenState,
|
MessageMetadata, RedactedThinkingContent, SystemNotificationContent, SystemNotificationType,
|
||||||
ToolConfirmationRequest, ToolRequest, ToolResponse,
|
ThinkingContent, TokenState, ToolConfirmationRequest, ToolRequest, ToolResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::routes::recipe_utils::RecipeManifest;
|
use crate::routes::recipe_utils::RecipeManifest;
|
||||||
@@ -358,7 +358,7 @@ derive_utoipa!(Icon as IconSchema);
|
|||||||
super::routes::agent::agent_remove_extension,
|
super::routes::agent::agent_remove_extension,
|
||||||
super::routes::agent::update_agent_provider,
|
super::routes::agent::update_agent_provider,
|
||||||
super::routes::agent::update_router_tool_selector,
|
super::routes::agent::update_router_tool_selector,
|
||||||
super::routes::reply::confirm_permission,
|
super::routes::action_required::confirm_tool_action,
|
||||||
super::routes::reply::reply,
|
super::routes::reply::reply,
|
||||||
super::routes::session::list_sessions,
|
super::routes::session::list_sessions,
|
||||||
super::routes::session::get_session,
|
super::routes::session::get_session,
|
||||||
@@ -411,7 +411,7 @@ derive_utoipa!(Icon as IconSchema);
|
|||||||
super::routes::config_management::UpdateCustomProviderRequest,
|
super::routes::config_management::UpdateCustomProviderRequest,
|
||||||
super::routes::config_management::CheckProviderRequest,
|
super::routes::config_management::CheckProviderRequest,
|
||||||
super::routes::config_management::SetProviderRequest,
|
super::routes::config_management::SetProviderRequest,
|
||||||
super::routes::reply::PermissionConfirmationRequest,
|
super::routes::action_required::ConfirmToolActionRequest,
|
||||||
super::routes::reply::ChatRequest,
|
super::routes::reply::ChatRequest,
|
||||||
super::routes::session::ImportSessionRequest,
|
super::routes::session::ImportSessionRequest,
|
||||||
super::routes::session::SessionListResponse,
|
super::routes::session::SessionListResponse,
|
||||||
@@ -438,6 +438,8 @@ derive_utoipa!(Icon as IconSchema);
|
|||||||
ToolResponse,
|
ToolResponse,
|
||||||
ToolRequest,
|
ToolRequest,
|
||||||
ToolConfirmationRequest,
|
ToolConfirmationRequest,
|
||||||
|
ActionRequired,
|
||||||
|
ActionRequiredData,
|
||||||
ThinkingContent,
|
ThinkingContent,
|
||||||
RedactedThinkingContent,
|
RedactedThinkingContent,
|
||||||
FrontendToolRequest,
|
FrontendToolRequest,
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
use crate::state::AppState;
|
||||||
|
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
|
||||||
|
use goose::permission::permission_confirmation::PrincipalType;
|
||||||
|
use goose::permission::{Permission, PermissionConfirmation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, ToSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ConfirmToolActionRequest {
|
||||||
|
id: String,
|
||||||
|
#[serde(default = "default_principal_type")]
|
||||||
|
principal_type: PrincipalType,
|
||||||
|
action: String,
|
||||||
|
session_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_principal_type() -> PrincipalType {
|
||||||
|
PrincipalType::Tool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/action-required/tool-confirmation",
|
||||||
|
request_body = ConfirmToolActionRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Tool confirmation action is confirmed", body = Value),
|
||||||
|
(status = 401, description = "Unauthorized - invalid secret key"),
|
||||||
|
(status = 500, description = "Internal server error")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn confirm_tool_action(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Json(request): Json<ConfirmToolActionRequest>,
|
||||||
|
) -> Result<Json<Value>, StatusCode> {
|
||||||
|
let agent = state.get_agent_for_route(request.session_id).await?;
|
||||||
|
let permission = match request.action.as_str() {
|
||||||
|
"always_allow" => Permission::AlwaysAllow,
|
||||||
|
"allow_once" => Permission::AllowOnce,
|
||||||
|
"deny" => Permission::DenyOnce,
|
||||||
|
_ => Permission::DenyOnce,
|
||||||
|
};
|
||||||
|
|
||||||
|
agent
|
||||||
|
.handle_confirmation(
|
||||||
|
request.id.clone(),
|
||||||
|
PermissionConfirmation {
|
||||||
|
principal_type: request.principal_type,
|
||||||
|
permission,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(Json(Value::Object(serde_json::Map::new())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes(state: Arc<AppState>) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route(
|
||||||
|
"/action-required/tool-confirmation",
|
||||||
|
post(confirm_tool_action),
|
||||||
|
)
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod integration_tests {
|
||||||
|
use super::*;
|
||||||
|
use axum::{body::Body, http::Request};
|
||||||
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_tool_confirmation_endpoint() {
|
||||||
|
let state = AppState::new().await.unwrap();
|
||||||
|
|
||||||
|
let app = routes(state);
|
||||||
|
|
||||||
|
let request = Request::builder()
|
||||||
|
.uri("/action-required/tool-confirmation")
|
||||||
|
.method("POST")
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.header("x-secret-key", "test-secret")
|
||||||
|
.body(Body::from(
|
||||||
|
serde_json::to_string(&ConfirmToolActionRequest {
|
||||||
|
id: "test-id".to_string(),
|
||||||
|
principal_type: PrincipalType::Tool,
|
||||||
|
action: "allow_once".to_string(),
|
||||||
|
session_id: "test-session".to_string(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = app.oneshot(request).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod action_required;
|
||||||
pub mod agent;
|
pub mod agent;
|
||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod config_management;
|
pub mod config_management;
|
||||||
@@ -22,6 +23,7 @@ pub fn configure(state: Arc<crate::state::AppState>, secret_key: String) -> Rout
|
|||||||
Router::new()
|
Router::new()
|
||||||
.merge(status::routes())
|
.merge(status::routes())
|
||||||
.merge(reply::routes(state.clone()))
|
.merge(reply::routes(state.clone()))
|
||||||
|
.merge(action_required::routes(state.clone()))
|
||||||
.merge(agent::routes(state.clone()))
|
.merge(agent::routes(state.clone()))
|
||||||
.merge(audio::routes(state.clone()))
|
.merge(audio::routes(state.clone()))
|
||||||
.merge(config_management::routes(state.clone()))
|
.merge(config_management::routes(state.clone()))
|
||||||
|
|||||||
@@ -8,17 +8,12 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::{stream::StreamExt, Stream};
|
use futures::{stream::StreamExt, Stream};
|
||||||
|
use goose::agents::{AgentEvent, SessionConfig};
|
||||||
use goose::conversation::message::{Message, MessageContent, TokenState};
|
use goose::conversation::message::{Message, MessageContent, TokenState};
|
||||||
use goose::conversation::Conversation;
|
use goose::conversation::Conversation;
|
||||||
use goose::permission::{Permission, PermissionConfirmation};
|
|
||||||
use goose::session::SessionManager;
|
use goose::session::SessionManager;
|
||||||
use goose::{
|
|
||||||
agents::{AgentEvent, SessionConfig},
|
|
||||||
permission::permission_confirmation::PrincipalType,
|
|
||||||
};
|
|
||||||
use rmcp::model::ServerNotification;
|
use rmcp::model::ServerNotification;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
@@ -30,7 +25,6 @@ use tokio::sync::mpsc;
|
|||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use utoipa::ToSchema;
|
|
||||||
|
|
||||||
fn track_tool_telemetry(content: &MessageContent, all_messages: &[Message]) {
|
fn track_tool_telemetry(content: &MessageContent, all_messages: &[Message]) {
|
||||||
match content {
|
match content {
|
||||||
@@ -452,60 +446,12 @@ pub async fn reply(
|
|||||||
Ok(SseResponse::new(stream))
|
Ok(SseResponse::new(stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, ToSchema)]
|
|
||||||
pub struct PermissionConfirmationRequest {
|
|
||||||
id: String,
|
|
||||||
#[serde(default = "default_principal_type")]
|
|
||||||
principal_type: PrincipalType,
|
|
||||||
action: String,
|
|
||||||
session_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_principal_type() -> PrincipalType {
|
|
||||||
PrincipalType::Tool
|
|
||||||
}
|
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
post,
|
|
||||||
path = "/confirm",
|
|
||||||
request_body = PermissionConfirmationRequest,
|
|
||||||
responses(
|
|
||||||
(status = 200, description = "Permission action is confirmed", body = Value),
|
|
||||||
(status = 401, description = "Unauthorized - invalid secret key"),
|
|
||||||
(status = 500, description = "Internal server error")
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
pub async fn confirm_permission(
|
|
||||||
State(state): State<Arc<AppState>>,
|
|
||||||
Json(request): Json<PermissionConfirmationRequest>,
|
|
||||||
) -> Result<Json<Value>, StatusCode> {
|
|
||||||
let agent = state.get_agent_for_route(request.session_id).await?;
|
|
||||||
let permission = match request.action.as_str() {
|
|
||||||
"always_allow" => Permission::AlwaysAllow,
|
|
||||||
"allow_once" => Permission::AllowOnce,
|
|
||||||
"deny" => Permission::DenyOnce,
|
|
||||||
_ => Permission::DenyOnce,
|
|
||||||
};
|
|
||||||
|
|
||||||
agent
|
|
||||||
.handle_confirmation(
|
|
||||||
request.id.clone(),
|
|
||||||
PermissionConfirmation {
|
|
||||||
principal_type: request.principal_type,
|
|
||||||
permission,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
Ok(Json(Value::Object(serde_json::Map::new())))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn routes(state: Arc<AppState>) -> Router {
|
pub fn routes(state: Arc<AppState>) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route(
|
.route(
|
||||||
"/reply",
|
"/reply",
|
||||||
post(reply).layer(DefaultBodyLimit::max(50 * 1024 * 1024)),
|
post(reply).layer(DefaultBodyLimit::max(50 * 1024 * 1024)),
|
||||||
)
|
)
|
||||||
.route("/confirm", post(confirm_permission))
|
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ impl Agent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let confirmation = Message::assistant()
|
let confirmation = Message::assistant()
|
||||||
.with_tool_confirmation_request(
|
.with_action_required(
|
||||||
request.id.clone(),
|
request.id.clone(),
|
||||||
tool_call.name.to_string().clone(),
|
tool_call.name.to_string().clone(),
|
||||||
tool_call.arguments.clone().unwrap_or_default(),
|
tool_call.arguments.clone().unwrap_or_default(),
|
||||||
|
|||||||
@@ -375,6 +375,14 @@ fn format_message_for_compacting(msg: &Message) -> String {
|
|||||||
MessageContent::ToolConfirmationRequest(req) => {
|
MessageContent::ToolConfirmationRequest(req) => {
|
||||||
format!("tool_confirmation_request: {}", req.tool_name)
|
format!("tool_confirmation_request: {}", req.tool_name)
|
||||||
}
|
}
|
||||||
|
MessageContent::ActionRequired(action) => match &action.data {
|
||||||
|
crate::conversation::message::ActionRequiredData::ToolConfirmation {
|
||||||
|
tool_name,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
format!("action_required(tool_confirmation): {}", tool_name)
|
||||||
|
}
|
||||||
|
},
|
||||||
MessageContent::FrontendToolRequest(req) => {
|
MessageContent::FrontendToolRequest(req) => {
|
||||||
if let Ok(call) = &req.tool_call {
|
if let Ok(call) = &req.tool_call {
|
||||||
format!("frontend_tool_request: {}", call.name)
|
format!("frontend_tool_request: {}", call.name)
|
||||||
|
|||||||
@@ -101,6 +101,24 @@ pub struct ToolConfirmationRequest {
|
|||||||
pub prompt: Option<String>,
|
pub prompt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
||||||
|
#[serde(tag = "actionType", rename_all = "camelCase")]
|
||||||
|
pub enum ActionRequiredData {
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
ToolConfirmation {
|
||||||
|
id: String,
|
||||||
|
tool_name: String,
|
||||||
|
arguments: JsonObject,
|
||||||
|
prompt: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ActionRequired {
|
||||||
|
pub data: ActionRequiredData,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct ThinkingContent {
|
pub struct ThinkingContent {
|
||||||
pub thinking: String,
|
pub thinking: String,
|
||||||
@@ -144,6 +162,7 @@ pub enum MessageContent {
|
|||||||
ToolRequest(ToolRequest),
|
ToolRequest(ToolRequest),
|
||||||
ToolResponse(ToolResponse),
|
ToolResponse(ToolResponse),
|
||||||
ToolConfirmationRequest(ToolConfirmationRequest),
|
ToolConfirmationRequest(ToolConfirmationRequest),
|
||||||
|
ActionRequired(ActionRequired),
|
||||||
FrontendToolRequest(FrontendToolRequest),
|
FrontendToolRequest(FrontendToolRequest),
|
||||||
Thinking(ThinkingContent),
|
Thinking(ThinkingContent),
|
||||||
RedactedThinking(RedactedThinkingContent),
|
RedactedThinking(RedactedThinkingContent),
|
||||||
@@ -169,6 +188,11 @@ impl fmt::Display for MessageContent {
|
|||||||
MessageContent::ToolConfirmationRequest(r) => {
|
MessageContent::ToolConfirmationRequest(r) => {
|
||||||
write!(f, "[ToolConfirmationRequest: {}]", r.tool_name)
|
write!(f, "[ToolConfirmationRequest: {}]", r.tool_name)
|
||||||
}
|
}
|
||||||
|
MessageContent::ActionRequired(a) => match &a.data {
|
||||||
|
ActionRequiredData::ToolConfirmation { tool_name, .. } => {
|
||||||
|
write!(f, "[ActionRequired: ToolConfirmation for {}]", tool_name)
|
||||||
|
}
|
||||||
|
},
|
||||||
MessageContent::FrontendToolRequest(r) => match &r.tool_call {
|
MessageContent::FrontendToolRequest(r) => match &r.tool_call {
|
||||||
Ok(tool_call) => write!(f, "[FrontendToolRequest: {}]", tool_call.name),
|
Ok(tool_call) => write!(f, "[FrontendToolRequest: {}]", tool_call.name),
|
||||||
Err(e) => write!(f, "[FrontendToolRequest: Error: {}]", e),
|
Err(e) => write!(f, "[FrontendToolRequest: Error: {}]", e),
|
||||||
@@ -234,17 +258,19 @@ impl MessageContent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tool_confirmation_request<S: Into<String>>(
|
pub fn action_required<S: Into<String>>(
|
||||||
id: S,
|
id: S,
|
||||||
tool_name: String,
|
tool_name: String,
|
||||||
arguments: JsonObject,
|
arguments: JsonObject,
|
||||||
prompt: Option<String>,
|
prompt: Option<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
MessageContent::ToolConfirmationRequest(ToolConfirmationRequest {
|
MessageContent::ActionRequired(ActionRequired {
|
||||||
id: id.into(),
|
data: ActionRequiredData::ToolConfirmation {
|
||||||
tool_name,
|
id: id.into(),
|
||||||
arguments,
|
tool_name,
|
||||||
prompt,
|
arguments,
|
||||||
|
prompt,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,9 +329,9 @@ impl MessageContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_tool_confirmation_request(&self) -> Option<&ToolConfirmationRequest> {
|
pub fn as_action_required(&self) -> Option<&ActionRequired> {
|
||||||
if let MessageContent::ToolConfirmationRequest(ref tool_confirmation_request) = self {
|
if let MessageContent::ActionRequired(ref action_required) = self {
|
||||||
Some(tool_confirmation_request)
|
Some(action_required)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -582,15 +608,15 @@ impl Message {
|
|||||||
self.with_content(MessageContent::tool_response(id, result))
|
self.with_content(MessageContent::tool_response(id, result))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a tool confirmation request to the message
|
/// Add an action required message for tool confirmation
|
||||||
pub fn with_tool_confirmation_request<S: Into<String>>(
|
pub fn with_action_required<S: Into<String>>(
|
||||||
self,
|
self,
|
||||||
id: S,
|
id: S,
|
||||||
tool_name: String,
|
tool_name: String,
|
||||||
arguments: JsonObject,
|
arguments: JsonObject,
|
||||||
prompt: Option<String>,
|
prompt: Option<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.with_content(MessageContent::tool_confirmation_request(
|
self.with_content(MessageContent::action_required(
|
||||||
id, tool_name, arguments, prompt,
|
id, tool_name, arguments, prompt,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ 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::ActionRequired(_action_required) => {
|
||||||
|
// Skip action required messages - they're for UI only
|
||||||
|
}
|
||||||
MessageContent::SystemNotification(_) => {
|
MessageContent::SystemNotification(_) => {
|
||||||
// Skip
|
// Skip
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ 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::ActionRequired(_action_required) => {
|
||||||
|
bedrock::ContentBlock::Text("".to_string())
|
||||||
|
}
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
bedrock::ContentBlock::Image(to_bedrock_image(&image.data, &image.mime_type)?)
|
bedrock::ContentBlock::Image(to_bedrock_image(&image.data, &image.mime_type)?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,9 +208,8 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageContent::ToolConfirmationRequest(_) => {
|
MessageContent::ToolConfirmationRequest(_) => {}
|
||||||
// Skip tool confirmation requests
|
MessageContent::ActionRequired(_) => {}
|
||||||
}
|
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
content_array.push(convert_image(image, image_format));
|
content_array.push(convert_image(image, image_format));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.is_agent_visible())
|
.filter(|m| m.is_agent_visible())
|
||||||
.filter(|message| {
|
.filter(|message| {
|
||||||
message
|
message.content.iter().any(|content| {
|
||||||
.content
|
!matches!(
|
||||||
.iter()
|
content,
|
||||||
.any(|content| !matches!(content, MessageContent::ToolConfirmationRequest(_)))
|
MessageContent::ToolConfirmationRequest(_) | MessageContent::ActionRequired(_)
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.map(|message| {
|
.map(|message| {
|
||||||
let role = if message.role == Role::User {
|
let role = if message.role == Role::User {
|
||||||
@@ -408,11 +410,11 @@ mod tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_up_tool_confirmation_message(id: &str, tool_call: CallToolRequestParam) -> Message {
|
fn set_up_action_required_message(id: &str, tool_call: CallToolRequestParam) -> Message {
|
||||||
Message::new(
|
Message::new(
|
||||||
Role::User,
|
Role::User,
|
||||||
0,
|
0,
|
||||||
vec![MessageContent::tool_confirmation_request(
|
vec![MessageContent::action_required(
|
||||||
id.to_string(),
|
id.to_string(),
|
||||||
tool_call.name.to_string().clone(),
|
tool_call.name.to_string().clone(),
|
||||||
tool_call.arguments.unwrap_or_default().clone(),
|
tool_call.arguments.unwrap_or_default().clone(),
|
||||||
@@ -474,7 +476,7 @@ mod tests {
|
|||||||
arguments: Some(object(arguments.clone())),
|
arguments: Some(object(arguments.clone())),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
set_up_tool_confirmation_message(
|
set_up_action_required_message(
|
||||||
"id2",
|
"id2",
|
||||||
CallToolRequestParam {
|
CallToolRequestParam {
|
||||||
name: "tool_name_2".into(),
|
name: "tool_name_2".into(),
|
||||||
|
|||||||
@@ -198,9 +198,8 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageContent::ToolConfirmationRequest(_) => {
|
MessageContent::ToolConfirmationRequest(_) => {}
|
||||||
// Skip tool confirmation requests
|
MessageContent::ActionRequired(_) => {}
|
||||||
}
|
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
content_array.push(convert_image(image, image_format));
|
content_array.push(convert_image(image, image_format));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,8 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageContent::ToolConfirmationRequest(_) => {
|
MessageContent::ToolConfirmationRequest(_) => {}
|
||||||
// Skip tool confirmation requests
|
MessageContent::ActionRequired(_) => {}
|
||||||
}
|
|
||||||
MessageContent::SystemNotification(_) => {
|
MessageContent::SystemNotification(_) => {
|
||||||
// Skip
|
// Skip
|
||||||
}
|
}
|
||||||
|
|||||||
+125
-56
@@ -13,6 +13,40 @@
|
|||||||
"version": "1.15.0"
|
"version": "1.15.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/action-required/tool-confirmation": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"super::routes::action_required"
|
||||||
|
],
|
||||||
|
"operationId": "confirm_tool_action",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ConfirmToolActionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Tool confirmation action is confirmed",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized - invalid secret key"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/agent/add_extension": {
|
"/agent/add_extension": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -954,40 +988,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/confirm": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"super::routes::reply"
|
|
||||||
],
|
|
||||||
"operationId": "confirm_permission",
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/PermissionConfirmationRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Permission action is confirmed",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Unauthorized - invalid secret key"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/diagnostics/{session_id}": {
|
"/diagnostics/{session_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -2353,6 +2353,54 @@
|
|||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
"ActionRequired": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/components/schemas/ActionRequiredData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ActionRequiredData": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"toolName",
|
||||||
|
"arguments",
|
||||||
|
"actionType"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"actionType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"toolConfirmation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
"$ref": "#/components/schemas/JsonObject"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"toolName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"discriminator": {
|
||||||
|
"propertyName": "actionType"
|
||||||
|
}
|
||||||
|
},
|
||||||
"AddExtensionRequest": {
|
"AddExtensionRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2516,6 +2564,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ConfirmToolActionRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"action",
|
||||||
|
"sessionId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"principalType": {
|
||||||
|
"$ref": "#/components/schemas/PrincipalType"
|
||||||
|
},
|
||||||
|
"sessionId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Content": {
|
"Content": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
@@ -3518,6 +3588,27 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/ActionRequired"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"actionRequired"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@@ -3872,28 +3963,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PermissionConfirmationRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"action",
|
|
||||||
"session_id"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"action": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"principal_type": {
|
|
||||||
"$ref": "#/components/schemas/PrincipalType"
|
|
||||||
},
|
|
||||||
"session_id": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"PermissionLevel": {
|
"PermissionLevel": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Enum representing the possible permission levels for a tool.",
|
"description": "Enum representing the possible permission levels for a tool.",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { Client, Options as Options2, TDataShape } from './client';
|
import type { Client, Options as Options2, TDataShape } from './client';
|
||||||
import { client } from './client.gen';
|
import { client } from './client.gen';
|
||||||
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmPermissionData, ConfirmPermissionErrors, ConfirmPermissionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
|
import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CheckProviderData, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, EditMessageData, EditMessageErrors, EditMessageResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateRouterToolSelectorData, UpdateRouterToolSelectorErrors, UpdateRouterToolSelectorResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen';
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
||||||
/**
|
/**
|
||||||
@@ -18,6 +18,17 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
|
|||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const confirmToolAction = <ThrowOnError extends boolean = false>(options: Options<ConfirmToolActionData, ThrowOnError>) => {
|
||||||
|
return (options.client ?? client).post<ConfirmToolActionResponses, ConfirmToolActionErrors, ThrowOnError>({
|
||||||
|
url: '/action-required/tool-confirmation',
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const agentAddExtension = <ThrowOnError extends boolean = false>(options: Options<AgentAddExtensionData, ThrowOnError>) => {
|
export const agentAddExtension = <ThrowOnError extends boolean = false>(options: Options<AgentAddExtensionData, ThrowOnError>) => {
|
||||||
return (options.client ?? client).post<AgentAddExtensionResponses, AgentAddExtensionErrors, ThrowOnError>({
|
return (options.client ?? client).post<AgentAddExtensionResponses, AgentAddExtensionErrors, ThrowOnError>({
|
||||||
url: '/agent/add_extension',
|
url: '/agent/add_extension',
|
||||||
@@ -285,17 +296,6 @@ export const validateConfig = <ThrowOnError extends boolean = false>(options?: O
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const confirmPermission = <ThrowOnError extends boolean = false>(options: Options<ConfirmPermissionData, ThrowOnError>) => {
|
|
||||||
return (options.client ?? client).post<ConfirmPermissionResponses, ConfirmPermissionErrors, ThrowOnError>({
|
|
||||||
url: '/confirm',
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...options.headers
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const diagnostics = <ThrowOnError extends boolean = false>(options: Options<DiagnosticsData, ThrowOnError>) => {
|
export const diagnostics = <ThrowOnError extends boolean = false>(options: Options<DiagnosticsData, ThrowOnError>) => {
|
||||||
return (options.client ?? client).get<DiagnosticsResponses, DiagnosticsErrors, ThrowOnError>({
|
return (options.client ?? client).get<DiagnosticsResponses, DiagnosticsErrors, ThrowOnError>({
|
||||||
url: '/diagnostics/{session_id}',
|
url: '/diagnostics/{session_id}',
|
||||||
|
|||||||
@@ -4,6 +4,18 @@ export type ClientOptions = {
|
|||||||
baseUrl: `${string}://${string}` | (string & {});
|
baseUrl: `${string}://${string}` | (string & {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ActionRequired = {
|
||||||
|
data: ActionRequiredData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ActionRequiredData = {
|
||||||
|
actionType: 'toolConfirmation';
|
||||||
|
arguments: JsonObject;
|
||||||
|
id: string;
|
||||||
|
prompt?: string | null;
|
||||||
|
toolName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AddExtensionRequest = {
|
export type AddExtensionRequest = {
|
||||||
config: ExtensionConfig;
|
config: ExtensionConfig;
|
||||||
session_id: string;
|
session_id: string;
|
||||||
@@ -76,6 +88,13 @@ export type ConfigResponse = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ConfirmToolActionRequest = {
|
||||||
|
action: string;
|
||||||
|
id: string;
|
||||||
|
principalType?: PrincipalType;
|
||||||
|
sessionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Content = RawTextContent | RawImageContent | RawEmbeddedResource | RawAudioContent | RawResource;
|
export type Content = RawTextContent | RawImageContent | RawEmbeddedResource | RawAudioContent | RawResource;
|
||||||
|
|
||||||
export type Conversation = Array<Message>;
|
export type Conversation = Array<Message>;
|
||||||
@@ -371,6 +390,8 @@ export type MessageContent = (TextContent & {
|
|||||||
type: 'toolResponse';
|
type: 'toolResponse';
|
||||||
}) | (ToolConfirmationRequest & {
|
}) | (ToolConfirmationRequest & {
|
||||||
type: 'toolConfirmationRequest';
|
type: 'toolConfirmationRequest';
|
||||||
|
}) | (ActionRequired & {
|
||||||
|
type: 'actionRequired';
|
||||||
}) | (FrontendToolRequest & {
|
}) | (FrontendToolRequest & {
|
||||||
type: 'frontendToolRequest';
|
type: 'frontendToolRequest';
|
||||||
}) | (ThinkingContent & {
|
}) | (ThinkingContent & {
|
||||||
@@ -471,13 +492,6 @@ export type ParseRecipeResponse = {
|
|||||||
recipe: Recipe;
|
recipe: Recipe;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PermissionConfirmationRequest = {
|
|
||||||
action: string;
|
|
||||||
id: string;
|
|
||||||
principal_type?: PrincipalType;
|
|
||||||
session_id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum representing the possible permission levels for a tool.
|
* Enum representing the possible permission levels for a tool.
|
||||||
*/
|
*/
|
||||||
@@ -990,6 +1004,31 @@ export type UpsertPermissionsQuery = {
|
|||||||
tool_permissions: Array<ToolPermission>;
|
tool_permissions: Array<ToolPermission>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ConfirmToolActionData = {
|
||||||
|
body: ConfirmToolActionRequest;
|
||||||
|
path?: never;
|
||||||
|
query?: never;
|
||||||
|
url: '/action-required/tool-confirmation';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfirmToolActionErrors = {
|
||||||
|
/**
|
||||||
|
* Unauthorized - invalid secret key
|
||||||
|
*/
|
||||||
|
401: unknown;
|
||||||
|
/**
|
||||||
|
* Internal server error
|
||||||
|
*/
|
||||||
|
500: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ConfirmToolActionResponses = {
|
||||||
|
/**
|
||||||
|
* Tool confirmation action is confirmed
|
||||||
|
*/
|
||||||
|
200: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
export type AgentAddExtensionData = {
|
export type AgentAddExtensionData = {
|
||||||
body: AddExtensionRequest;
|
body: AddExtensionRequest;
|
||||||
path?: never;
|
path?: never;
|
||||||
@@ -1726,31 +1765,6 @@ export type ValidateConfigResponses = {
|
|||||||
|
|
||||||
export type ValidateConfigResponse = ValidateConfigResponses[keyof ValidateConfigResponses];
|
export type ValidateConfigResponse = ValidateConfigResponses[keyof ValidateConfigResponses];
|
||||||
|
|
||||||
export type ConfirmPermissionData = {
|
|
||||||
body: PermissionConfirmationRequest;
|
|
||||||
path?: never;
|
|
||||||
query?: never;
|
|
||||||
url: '/confirm';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ConfirmPermissionErrors = {
|
|
||||||
/**
|
|
||||||
* Unauthorized - invalid secret key
|
|
||||||
*/
|
|
||||||
401: unknown;
|
|
||||||
/**
|
|
||||||
* Internal server error
|
|
||||||
*/
|
|
||||||
500: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ConfirmPermissionResponses = {
|
|
||||||
/**
|
|
||||||
* Permission action is confirmed
|
|
||||||
*/
|
|
||||||
200: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DiagnosticsData = {
|
export type DiagnosticsData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path: {
|
path: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
getToolConfirmationContent,
|
getToolConfirmationContent,
|
||||||
NotificationEvent,
|
NotificationEvent,
|
||||||
} from '../types/message';
|
} from '../types/message';
|
||||||
import { Message, confirmPermission } from '../api';
|
import { Message, confirmToolAction } from '../api';
|
||||||
import ToolCallConfirmation from './ToolCallConfirmation';
|
import ToolCallConfirmation from './ToolCallConfirmation';
|
||||||
import MessageCopyLink from './MessageCopyLink';
|
import MessageCopyLink from './MessageCopyLink';
|
||||||
import { cn } from '../utils';
|
import { cn } from '../utils';
|
||||||
@@ -100,22 +100,22 @@ export default function GooseMessage({
|
|||||||
messageIndex === messageHistoryIndex - 1 &&
|
messageIndex === messageHistoryIndex - 1 &&
|
||||||
hasToolConfirmation &&
|
hasToolConfirmation &&
|
||||||
toolConfirmationContent &&
|
toolConfirmationContent &&
|
||||||
!handledToolConfirmations.current.has(toolConfirmationContent.id)
|
!handledToolConfirmations.current.has(toolConfirmationContent.data.id)
|
||||||
) {
|
) {
|
||||||
const hasExistingResponse = messages.some((msg) =>
|
const hasExistingResponse = messages.some((msg) =>
|
||||||
getToolResponses(msg).some((response) => response.id === toolConfirmationContent.id)
|
getToolResponses(msg).some((response) => response.id === toolConfirmationContent.data.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasExistingResponse) {
|
if (!hasExistingResponse) {
|
||||||
handledToolConfirmations.current.add(toolConfirmationContent.id);
|
handledToolConfirmations.current.add(toolConfirmationContent.data.id);
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
await confirmPermission({
|
await confirmToolAction({
|
||||||
body: {
|
body: {
|
||||||
session_id: sessionId,
|
sessionId,
|
||||||
id: toolConfirmationContent.id,
|
|
||||||
action: 'deny',
|
action: 'deny',
|
||||||
|
id: toolConfirmationContent.data.id,
|
||||||
},
|
},
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
});
|
});
|
||||||
@@ -216,7 +216,7 @@ export default function GooseMessage({
|
|||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
isCancelledMessage={messageIndex == messageHistoryIndex - 1}
|
isCancelledMessage={messageIndex == messageHistoryIndex - 1}
|
||||||
isClicked={messageIndex < messageHistoryIndex}
|
isClicked={messageIndex < messageHistoryIndex}
|
||||||
toolConfirmationContent={toolConfirmationContent}
|
actionRequiredContent={toolConfirmationContent}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { snakeToTitleCase } from '../utils';
|
import { snakeToTitleCase } from '../utils';
|
||||||
import PermissionModal from './settings/permission/PermissionModal';
|
import PermissionModal from './settings/permission/PermissionModal';
|
||||||
import { ChevronRight } from 'lucide-react';
|
import { ChevronRight } from 'lucide-react';
|
||||||
import { confirmPermission, ToolConfirmationRequest } from '../api';
|
import { confirmToolAction, ActionRequired } from '../api';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
|
|
||||||
const ALLOW_ONCE = 'allow_once';
|
const ALLOW_ONCE = 'allow_once';
|
||||||
@@ -24,16 +24,16 @@ interface ToolConfirmationProps {
|
|||||||
sessionId: string;
|
sessionId: string;
|
||||||
isCancelledMessage: boolean;
|
isCancelledMessage: boolean;
|
||||||
isClicked: boolean;
|
isClicked: boolean;
|
||||||
toolConfirmationContent: ToolConfirmationRequest & { type: 'toolConfirmationRequest' };
|
actionRequiredContent: ActionRequired & { type: 'actionRequired' };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToolConfirmation({
|
export default function ToolConfirmation({
|
||||||
sessionId,
|
sessionId,
|
||||||
isCancelledMessage,
|
isCancelledMessage,
|
||||||
isClicked,
|
isClicked,
|
||||||
toolConfirmationContent,
|
actionRequiredContent,
|
||||||
}: ToolConfirmationProps) {
|
}: ToolConfirmationProps) {
|
||||||
const { id: toolConfirmationId, toolName, prompt } = toolConfirmationContent;
|
const { id: toolConfirmationId, toolName, prompt } = actionRequiredContent.data;
|
||||||
|
|
||||||
// Check if we have a stored state for this tool confirmation
|
// Check if we have a stored state for this tool confirmation
|
||||||
const storedState = toolConfirmationState.get(toolConfirmationId);
|
const storedState = toolConfirmationState.get(toolConfirmationId);
|
||||||
@@ -96,19 +96,19 @@ export default function ToolConfirmation({
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await confirmPermission({
|
const response = await confirmToolAction({
|
||||||
body: {
|
body: {
|
||||||
session_id: sessionId,
|
sessionId: sessionId,
|
||||||
id: toolConfirmationId,
|
id: toolConfirmationId,
|
||||||
action: newStatus,
|
action: newStatus,
|
||||||
principal_type: 'Tool',
|
principalType: 'Tool',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.error('Failed to confirm permission:', response.error);
|
console.error('Failed to confirm tool action:', response.error);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error confirming permission:', err);
|
console.error('Error confirming tool action:', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Message, MessageEvent, ToolConfirmationRequest, ToolRequest, ToolResponse } from '../api';
|
import { Message, MessageEvent, ActionRequired, ToolRequest, ToolResponse } from '../api';
|
||||||
|
|
||||||
export type ToolRequestMessageContent = ToolRequest & { type: 'toolRequest' };
|
export type ToolRequestMessageContent = ToolRequest & { type: 'toolRequest' };
|
||||||
export type ToolResponseMessageContent = ToolResponse & { type: 'toolResponse' };
|
export type ToolResponseMessageContent = ToolResponse & { type: 'toolResponse' };
|
||||||
@@ -44,10 +44,10 @@ export function getToolResponses(message: Message): (ToolResponse & { type: 'too
|
|||||||
|
|
||||||
export function getToolConfirmationContent(
|
export function getToolConfirmationContent(
|
||||||
message: Message
|
message: Message
|
||||||
): (ToolConfirmationRequest & { type: 'toolConfirmationRequest' }) | undefined {
|
): (ActionRequired & { type: 'actionRequired' }) | undefined {
|
||||||
return message.content.find(
|
return message.content.find(
|
||||||
(content): content is ToolConfirmationRequest & { type: 'toolConfirmationRequest' } =>
|
(content): content is ActionRequired & { type: 'actionRequired' } =>
|
||||||
content.type === 'toolConfirmationRequest'
|
content.type === 'actionRequired' && content.data.actionType === 'toolConfirmation'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user