feat: add permission field to the list tools response (#2080)
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
use utoipa::OpenApi;
|
||||
|
||||
use goose::agents::extension::Envs;
|
||||
use goose::agents::extension::ToolInfo;
|
||||
use goose::agents::ExtensionConfig;
|
||||
use goose::config::permission::PermissionLevel;
|
||||
use goose::config::ExtensionEntry;
|
||||
use goose::providers::base::ConfigKey;
|
||||
use goose::providers::base::ProviderMetadata;
|
||||
use mcp_core::tool::{Tool, ToolAnnotations};
|
||||
use utoipa::OpenApi;
|
||||
|
||||
#[allow(dead_code)] // Used by utoipa for OpenAPI generation
|
||||
#[derive(OpenApi)]
|
||||
@@ -36,6 +37,8 @@ use mcp_core::tool::{Tool, ToolAnnotations};
|
||||
Envs,
|
||||
Tool,
|
||||
ToolAnnotations,
|
||||
ToolInfo,
|
||||
PermissionLevel,
|
||||
))
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
use crate::state::AppState;
|
||||
use axum::{
|
||||
extract::State,
|
||||
extract::{Query, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use goose::config::Config;
|
||||
use goose::{agents::AgentFactory, model::ModelConfig, providers};
|
||||
use mcp_core::Tool;
|
||||
use goose::{agents::AgentFactory, config::PermissionManager, model::ModelConfig, providers};
|
||||
use goose::{
|
||||
agents::{capabilities::get_parameter_names, extension::ToolInfo},
|
||||
config::Config,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
@@ -62,6 +64,11 @@ struct ProviderList {
|
||||
details: ProviderDetails,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetToolsQuery {
|
||||
extension_name: Option<String>,
|
||||
}
|
||||
|
||||
async fn get_versions() -> Json<VersionsResponse> {
|
||||
let versions = AgentFactory::available_versions();
|
||||
let default_version = AgentFactory::default_version().to_string();
|
||||
@@ -167,6 +174,9 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/agent/tools",
|
||||
params(
|
||||
("extension_name" = Option<String>, Query, description = "Optional extension name to filter tools")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Tools retrieved successfully", body = Vec<Tool>),
|
||||
(status = 401, description = "Unauthorized - invalid secret key"),
|
||||
@@ -177,7 +187,8 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
|
||||
async fn get_tools(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<Vec<Tool>>, StatusCode> {
|
||||
Query(query): Query<GetToolsQuery>,
|
||||
) -> Result<Json<Vec<ToolInfo>>, StatusCode> {
|
||||
let secret_key = headers
|
||||
.get("X-Secret-Key")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
@@ -189,11 +200,30 @@ async fn get_tools(
|
||||
|
||||
let mut agent = state.agent.write().await;
|
||||
let agent = agent.as_mut().ok_or(StatusCode::PRECONDITION_REQUIRED)?;
|
||||
let permission_manager = PermissionManager::default();
|
||||
|
||||
// Since list_tools() now returns Vec<Tool> directly, not a Result
|
||||
let tools = agent.list_tools().await;
|
||||
let tools = agent
|
||||
.list_tools()
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|tool| {
|
||||
// Apply the filter only if the extension name is present in the query
|
||||
if let Some(extension_name) = &query.extension_name {
|
||||
tool.name.starts_with(extension_name)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.map(|tool| {
|
||||
ToolInfo::new(
|
||||
&tool.name,
|
||||
&tool.description,
|
||||
get_parameter_names(&tool),
|
||||
permission_manager.get_user_permission(&tool.name),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Return the tools directly
|
||||
Ok(Json(tools))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use utoipa::ToSchema;
|
||||
|
||||
use crate::config;
|
||||
use crate::config::extensions::name_to_key;
|
||||
use crate::config::permission::PermissionLevel;
|
||||
|
||||
/// Errors from Extension operation
|
||||
#[derive(Error, Debug)]
|
||||
@@ -277,19 +278,26 @@ impl ExtensionInfo {
|
||||
}
|
||||
|
||||
/// Information about the tool used for building prompts
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, ToSchema)]
|
||||
pub struct ToolInfo {
|
||||
name: String,
|
||||
description: String,
|
||||
parameters: Vec<String>,
|
||||
permission: Option<PermissionLevel>,
|
||||
}
|
||||
|
||||
impl ToolInfo {
|
||||
pub fn new(name: &str, description: &str, parameters: Vec<String>) -> Self {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
description: &str,
|
||||
parameters: Vec<String>,
|
||||
permission: Option<PermissionLevel>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
description: description.to_string(),
|
||||
parameters,
|
||||
permission,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,14 @@ impl Agent for ReferenceAgent {
|
||||
let tools = capabilities.get_prefixed_tools().await?;
|
||||
let tools_info = tools
|
||||
.into_iter()
|
||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
||||
.map(|tool| {
|
||||
ToolInfo::new(
|
||||
&tool.name,
|
||||
&tool.description,
|
||||
get_parameter_names(&tool),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||
|
||||
@@ -489,7 +489,14 @@ impl Agent for SummarizeAgent {
|
||||
let tools = capabilities.get_prefixed_tools().await?;
|
||||
let tools_info = tools
|
||||
.into_iter()
|
||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
||||
.map(|tool| {
|
||||
ToolInfo::new(
|
||||
&tool.name,
|
||||
&tool.description,
|
||||
get_parameter_names(&tool),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||
|
||||
@@ -774,7 +774,14 @@ impl Agent for TruncateAgent {
|
||||
let tools = capabilities.get_prefixed_tools().await?;
|
||||
let tools_info = tools
|
||||
.into_iter()
|
||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
||||
.map(|tool| {
|
||||
ToolInfo::new(
|
||||
&tool.name,
|
||||
&tool.description,
|
||||
get_parameter_names(&tool),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||
|
||||
@@ -5,9 +5,10 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Enum representing the possible permission levels for a tool.
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PermissionLevel {
|
||||
AlwaysAllow, // Tool can always be used without prompt
|
||||
|
||||
@@ -19,6 +19,18 @@
|
||||
"super::routes::agent"
|
||||
],
|
||||
"operationId": "get_tools",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "extension_name",
|
||||
"in": "query",
|
||||
"description": "Optional extension name to filter tools",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Tools retrieved successfully",
|
||||
@@ -547,6 +559,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"PermissionLevel": {
|
||||
"type": "string",
|
||||
"description": "Enum representing the possible permission levels for a tool.",
|
||||
"enum": [
|
||||
"always_allow",
|
||||
"ask_before",
|
||||
"never_allow"
|
||||
]
|
||||
},
|
||||
"ProviderDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -688,6 +709,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ToolInfo": {
|
||||
"type": "object",
|
||||
"description": "Information about the tool used for building prompts",
|
||||
"required": [
|
||||
"name",
|
||||
"description",
|
||||
"parameters"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PermissionLevel"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpsertConfigQuery": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -84,6 +84,11 @@ export type ExtensionResponse = {
|
||||
extensions: Array<ExtensionEntry>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum representing the possible permission levels for a tool.
|
||||
*/
|
||||
export type PermissionLevel = 'always_allow' | 'ask_before' | 'never_allow';
|
||||
|
||||
export type ProviderDetails = {
|
||||
/**
|
||||
* Indicates whether the provider is fully configured
|
||||
@@ -204,6 +209,16 @@ export type ToolAnnotations = {
|
||||
title?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Information about the tool used for building prompts
|
||||
*/
|
||||
export type ToolInfo = {
|
||||
description: string;
|
||||
name: string;
|
||||
parameters: Array<string>;
|
||||
permission?: PermissionLevel | null;
|
||||
};
|
||||
|
||||
export type UpsertConfigQuery = {
|
||||
is_secret: boolean;
|
||||
key: string;
|
||||
@@ -213,7 +228,12 @@ export type UpsertConfigQuery = {
|
||||
export type GetToolsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
query?: {
|
||||
/**
|
||||
* Optional extension name to filter tools
|
||||
*/
|
||||
extension_name?: string | null;
|
||||
};
|
||||
url: '/agent/tools';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user