diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index 61917905c719..41a5c19eed4e 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -6,7 +6,7 @@ use goose::config::ExtensionEntry; use goose::conversation::Conversation; use goose::dictation::download_manager::{DownloadProgress, DownloadStatus}; use goose::model::ModelConfig; -use goose::permission::permission_confirmation::PrincipalType; +use goose::permission::permission_confirmation::{Permission, PrincipalType}; use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata, ProviderType}; use goose::session::{Session, SessionInsights, SessionType, SystemInfo}; use rmcp::model::{ @@ -498,6 +498,7 @@ derive_utoipa!(Icon as IconSchema); ToolAnnotationsSchema, ToolInfo, PermissionLevel, + Permission, PrincipalType, ModelInfo, ModelConfig, diff --git a/crates/goose-server/src/routes/action_required.rs b/crates/goose-server/src/routes/action_required.rs index 9fe4166e1881..f925a487a2ae 100644 --- a/crates/goose-server/src/routes/action_required.rs +++ b/crates/goose-server/src/routes/action_required.rs @@ -14,7 +14,7 @@ pub struct ConfirmToolActionRequest { id: String, #[serde(default = "default_principal_type")] principal_type: PrincipalType, - action: String, + action: Permission, session_id: String, } @@ -37,19 +37,13 @@ pub async fn confirm_tool_action( Json(request): Json, ) -> Result, ErrorResponse> { 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, + permission: request.action, }, ) .await; @@ -91,7 +85,7 @@ mod tests { serde_json::to_string(&ConfirmToolActionRequest { id: "test-id".to_string(), principal_type: PrincipalType::Tool, - action: "allow_once".to_string(), + action: Permission::AllowOnce, session_id: "test-session".to_string(), }) .unwrap(), diff --git a/crates/goose/src/permission/permission_confirmation.rs b/crates/goose/src/permission/permission_confirmation.rs index 59e3a8fefcdb..37818cc5f3a1 100644 --- a/crates/goose/src/permission/permission_confirmation.rs +++ b/crates/goose/src/permission/permission_confirmation.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "snake_case")] pub enum Permission { AlwaysAllow, AllowOnce, diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index cda0170459ad..61124bb272fe 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -3555,7 +3555,7 @@ ], "properties": { "action": { - "type": "string" + "$ref": "#/components/schemas/Permission" }, "id": { "type": "string" @@ -5240,6 +5240,16 @@ } } }, + "Permission": { + "type": "string", + "enum": [ + "always_allow", + "allow_once", + "cancel", + "deny_once", + "always_deny" + ] + }, "PermissionLevel": { "type": "string", "description": "Enum representing the possible permission levels for a tool.", diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts index feba78d3e4be..503d75835276 100644 --- a/ui/desktop/src/api/index.ts +++ b/ui/desktop/src/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export { addExtension, agentAddExtension, agentRemoveExtension, backupConfig, callTool, cancelDownload, checkProvider, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteModel, deleteRecipe, deleteSchedule, deleteSession, detectProvider, diagnostics, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getPricing, getPrompt, getPrompts, getProviderModels, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, initConfig, inspectRunningJob, killRunningJob, listApps, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, recoverConfig, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, sendTelemetryEvent, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateSchedule, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, ChatRequest, CheckProviderData, CheckProviderRequest, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderRequest, DetectProviderResponse, DetectProviderResponse2, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadModelData, DownloadModelErrors, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetPricingData, GetPricingResponse, GetPricingResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, Icon, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelConfig, ModelInfo, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, PermissionLevel, PermissionsMetadata, PricingData, PricingQuery, PricingResponse, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionDisplayInfo, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; +export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, ChatRequest, CheckProviderData, CheckProviderRequest, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DetectProviderData, DetectProviderErrors, DetectProviderRequest, DetectProviderResponse, DetectProviderResponse2, DetectProviderResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadModelData, DownloadModelErrors, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetPricingData, GetPricingResponse, GetPricingResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, Icon, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelConfig, ModelInfo, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PricingData, PricingQuery, PricingResponse, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionDisplayInfo, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index eca80ef67000..89566d5e1cdb 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -112,7 +112,7 @@ export type ConfigResponse = { }; export type ConfirmToolActionRequest = { - action: string; + action: Permission; id: string; principalType?: PrincipalType; sessionId: string; @@ -662,6 +662,8 @@ export type ParseRecipeResponse = { recipe: Recipe; }; +export type Permission = 'always_allow' | 'allow_once' | 'cancel' | 'deny_once' | 'always_deny'; + /** * Enum representing the possible permission levels for a tool. */ diff --git a/ui/desktop/src/components/GooseMessage.tsx b/ui/desktop/src/components/GooseMessage.tsx index 16de3de51cb2..3d7164a87bb5 100644 --- a/ui/desktop/src/components/GooseMessage.tsx +++ b/ui/desktop/src/components/GooseMessage.tsx @@ -10,6 +10,8 @@ import { getToolConfirmationContent, getElicitationContent, getPendingToolConfirmationIds, + getAnyToolConfirmationData, + ToolConfirmationData, NotificationEvent, } from '../types/message'; import { Message } from '../api'; @@ -26,7 +28,7 @@ interface GooseMessageProps { metadata?: string[]; toolCallNotifications: Map; append: (value: string) => void; - isStreaming?: boolean; // Whether this message is currently being streamed + isStreaming: boolean; submitElicitationResponse?: ( elicitationId: string, userData: Record @@ -39,7 +41,7 @@ export default function GooseMessage({ messages, toolCallNotifications, append, - isStreaming = false, + isStreaming, submitElicitationResponse, }: GooseMessageProps) { const contentRef = useRef(null); @@ -69,6 +71,18 @@ export default function GooseMessage({ const messageIndex = messages.findIndex((msg) => msg.id === message.id); const toolConfirmationContent = getToolConfirmationContent(message); const elicitationContent = getElicitationContent(message); + + const findConfirmationForToolAcrossMessages = ( + toolRequestId: string + ): ToolConfirmationData | undefined => { + for (const msg of messages) { + const confirmationData = getAnyToolConfirmationData(msg); + if (confirmationData && confirmationData.id === toolRequestId) { + return confirmationData; + } + } + return undefined; + }; const toolCallChains = useMemo(() => identifyConsecutiveToolCalls(messages), [messages]); const hideTimestamp = useMemo( () => shouldHideTimestamp(messageIndex, toolCallChains), @@ -77,6 +91,20 @@ export default function GooseMessage({ const hasToolConfirmation = toolConfirmationContent !== undefined; const hasElicitation = elicitationContent !== undefined; + const toolConfirmationShownInline = useMemo(() => { + if (!toolConfirmationContent) return false; + const confirmationData = getAnyToolConfirmationData(message); + if (!confirmationData) return false; + + for (const msg of messages) { + const requests = getToolRequests(msg); + if (requests.some((req) => req.id === confirmationData.id)) { + return true; + } + } + return false; + }, [toolConfirmationContent, message, messages]); + const toolResponsesMap = useMemo(() => { const responseMap = new Map(); @@ -149,20 +177,28 @@ export default function GooseMessage({
- {toolRequests.map((toolRequest) => ( -
- -
- ))} + {toolRequests.map((toolRequest) => { + const hasResponse = toolResponsesMap.has(toolRequest.id); + const isPending = pendingConfirmationIds.has(toolRequest.id); + const confirmationContent = findConfirmationForToolAcrossMessages(toolRequest.id); + const isApprovalClicked = confirmationContent && !isPending && hasResponse; + return ( +
+ +
+ ); + })}
{!isStreaming && !hideTimestamp && timestamp} @@ -171,10 +207,9 @@ export default function GooseMessage({
)} - {hasToolConfirmation && ( + {hasToolConfirmation && !toolConfirmationShownInline && ( diff --git a/ui/desktop/src/components/ToolApprovalButtons.tsx b/ui/desktop/src/components/ToolApprovalButtons.tsx new file mode 100644 index 000000000000..1b93f7f29a2d --- /dev/null +++ b/ui/desktop/src/components/ToolApprovalButtons.tsx @@ -0,0 +1,99 @@ +import { useState, useEffect } from 'react'; +import { Button } from './ui/button'; +import { confirmToolAction, Permission } from '../api'; + +const globalApprovalState = new Map< + string, + { + decision: Permission | null; + isClicked: boolean; + } +>(); + +export interface ToolApprovalData { + id: string; + toolName: string; + prompt?: string; + sessionId: string; + isClicked?: boolean; +} + +export default function ToolApprovalButtons({ data }: { data: ToolApprovalData }) { + const { id, toolName, prompt, sessionId, isClicked: initialIsClicked } = data; + + const storedState = globalApprovalState.get(id); + const [decision, setDecision] = useState(storedState?.decision ?? null); + const [isClicked, setIsClicked] = useState(storedState?.isClicked ?? initialIsClicked ?? false); + + useEffect(() => { + const currentState = globalApprovalState.get(id); + if (currentState) { + setDecision(currentState.decision); + setIsClicked(currentState.isClicked); + } + }, [id]); + + useEffect(() => { + globalApprovalState.set(id, { decision, isClicked }); + }, [id, decision, isClicked]); + + const handleAction = async (action: Permission) => { + setDecision(action); + setIsClicked(true); + + try { + const response = await confirmToolAction({ + body: { + sessionId, + id, + action, + principalType: 'Tool', + }, + }); + if (response.error) { + console.error('Failed to confirm tool action:', response.error); + } + } catch (err) { + console.error('Error confirming tool action:', err); + } + }; + + if (isClicked && decision) { + const statusMessages: Record = { + allow_once: 'Allowed once', + always_allow: 'Always allowed', + always_deny: 'Denied', + deny_once: 'Denied once', + cancel: 'Cancelled', + }; + return ( +

+ {toolName} - {statusMessages[decision]} +

+ ); + } + + return ( +
+ + {!prompt && ( + + )} + +
+ ); +} diff --git a/ui/desktop/src/components/ToolCallConfirmation.tsx b/ui/desktop/src/components/ToolCallConfirmation.tsx index ee723a62bd4b..5c28b2e490df 100644 --- a/ui/desktop/src/components/ToolCallConfirmation.tsx +++ b/ui/desktop/src/components/ToolCallConfirmation.tsx @@ -1,231 +1,32 @@ -import { useState, useEffect } from 'react'; -import { snakeToTitleCase } from '../utils'; -import PermissionModal from './settings/permission/PermissionModal'; -import { ChevronRight } from 'lucide-react'; -import { confirmToolAction, ActionRequired } from '../api'; -import { Button } from './ui/button'; - -const ALLOW_ONCE = 'allow_once'; -const ALWAYS_ALLOW = 'always_allow'; -const DENY = 'deny'; - -// Global state to track tool confirmation decisions -// This persists across navigation within the same session -const toolConfirmationState = new Map< - string, - { - clicked: boolean; - status: string; - actionDisplay: string; - } ->(); +import { ActionRequired } from '../api'; +import ToolApprovalButtons from './ToolApprovalButtons'; type ToolConfirmationData = Extract; interface ToolConfirmationProps { sessionId: string; - isCancelledMessage: boolean; isClicked: boolean; actionRequiredContent: ActionRequired & { type: 'actionRequired' }; } export default function ToolConfirmation({ sessionId, - isCancelledMessage, isClicked, actionRequiredContent, }: ToolConfirmationProps) { const data = actionRequiredContent.data as ToolConfirmationData; - const { id: toolConfirmationId, toolName, prompt } = data; - - // Check if we have a stored state for this tool confirmation - const storedState = toolConfirmationState.get(toolConfirmationId); - - // Initialize state from stored state if available, otherwise use props/defaults - const [clicked, setClicked] = useState(storedState?.clicked ?? isClicked); - const [status, setStatus] = useState(storedState?.status ?? 'unknown'); - const [actionDisplay, setActionDisplay] = useState(storedState?.actionDisplay ?? ''); - const [isModalOpen, setIsModalOpen] = useState(false); - - // Sync internal state with stored state and props - useEffect(() => { - const currentStoredState = toolConfirmationState.get(toolConfirmationId); - - // If we have stored state, use it - if (currentStoredState) { - setClicked(currentStoredState.clicked); - setStatus(currentStoredState.status); - setActionDisplay(currentStoredState.actionDisplay); - } else if (isClicked && !clicked) { - // Fallback to prop-based logic for historical confirmations - setClicked(isClicked); - if (status === 'unknown') { - setStatus('confirmed'); - setActionDisplay('confirmed'); - - // Store this state for future renders - toolConfirmationState.set(toolConfirmationId, { - clicked: true, - status: 'confirmed', - actionDisplay: 'confirmed', - }); - } - } - }, [isClicked, clicked, status, toolName, toolConfirmationId]); - - const handleButtonClick = async (newStatus: string) => { - let newActionDisplay; - - if (newStatus === ALWAYS_ALLOW) { - newActionDisplay = 'always allowed'; - } else if (newStatus === ALLOW_ONCE) { - newActionDisplay = 'allowed once'; - } else if (newStatus === DENY) { - newActionDisplay = 'denied'; - } else { - newActionDisplay = 'denied'; - } - - // Update local state - setClicked(true); - setStatus(newStatus); - setActionDisplay(newActionDisplay); + const { id, toolName, prompt } = data; - // Store in global state for persistence across navigation - toolConfirmationState.set(toolConfirmationId, { - clicked: true, - status: newStatus, - actionDisplay: newActionDisplay, - }); - - try { - const response = await confirmToolAction({ - body: { - sessionId: sessionId, - id: toolConfirmationId, - action: newStatus, - principalType: 'Tool', - }, - }); - if (response.error) { - console.error('Failed to confirm tool action:', response.error); - } - } catch (err) { - console.error('Error confirming tool action:', err); - } - }; - - const handleModalClose = () => { - setIsModalOpen(false); - }; - - function getExtensionName(toolName: string): string { - const parts = toolName.split('__'); - return parts.length > 1 ? parts[0] : ''; - } - - return isCancelledMessage ? ( -
- Tool call confirmation is cancelled. -
- ) : ( - <> - {/* Display security message if present */} - {prompt && ( -
- {prompt} -
- )} - -
+ return ( +
+
{prompt ? 'Do you allow this tool call?' : 'Goose would like to call the above tool. Allow?'}
- {clicked ? ( -
-
- {(status === 'allow_once' || status === 'always_allow') && ( - - - - )} - {status === 'deny' && ( - - - - )} - {status === 'confirmed' && ( - - - - )} - - {isClicked - ? 'Tool confirmation is not available' - : `${snakeToTitleCase(toolName.substring(toolName.lastIndexOf('__') + 2))} is ${actionDisplay}`} - -
- -
setIsModalOpen(true)}> - Change - -
-
- ) : ( -
- - {/* Only show "Always Allow" if there's no security message (no security finding) */} - {!prompt && ( - - )} - -
- )} - - {/* Modal for updating tool permission */} - {isModalOpen && ( - - )} - + +
); } diff --git a/ui/desktop/src/components/ToolCallWithResponse.tsx b/ui/desktop/src/components/ToolCallWithResponse.tsx index 2fcca040bb90..b0ad29b8a6bb 100644 --- a/ui/desktop/src/components/ToolCallWithResponse.tsx +++ b/ui/desktop/src/components/ToolCallWithResponse.tsx @@ -9,6 +9,7 @@ import { ToolRequestMessageContent, ToolResponseMessageContent, NotificationEvent, + ToolConfirmationData, } from '../types/message'; import { cn, snakeToTitleCase } from '../utils'; import { LoadingStatus } from './ui/Dot'; @@ -18,6 +19,7 @@ import MCPUIResourceRenderer from './MCPUIResourceRenderer'; import { isUIResource } from '@mcp-ui/client'; import { CallToolResponse, Content, EmbeddedResource } from '../api'; import McpAppRenderer from './McpApps/McpAppRenderer'; +import ToolApprovalButtons from './ToolApprovalButtons'; interface ToolGraphNode { tool: string; @@ -58,6 +60,8 @@ interface ToolCallWithResponseProps { isStreamingMessage?: boolean; isPendingApproval: boolean; append?: (value: string) => void; + confirmationContent?: ToolConfirmationData; + isApprovalClicked?: boolean; } function getToolResultContent(toolResult: Record): Content[] { @@ -155,6 +159,8 @@ export default function ToolCallWithResponse({ isStreamingMessage, isPendingApproval, append, + confirmationContent, + isApprovalClicked, }: ToolCallWithResponseProps) { // Handle both the wrapped ToolResult format and the unwrapped format // The server serializes ToolResult as { status: "success", value: T } or { status: "error", error: string } @@ -176,11 +182,14 @@ export default function ToolCallWithResponse({ const shouldShowMcpContent = !isPendingApproval; + const showInlineApproval = isPendingApproval && confirmationContent && sessionId; + return ( <>
+ {/* Inline approval UI */} + {showInlineApproval && ( +
+ {confirmationContent.prompt && ( +
+ {confirmationContent.prompt} +
+ )} +
+ +
+
+ )}
{/* MCP UI — Inline */} {shouldShowMcpContent && diff --git a/ui/desktop/src/types/message.ts b/ui/desktop/src/types/message.ts index 43423578ede3..845096ace67c 100644 --- a/ui/desktop/src/types/message.ts +++ b/ui/desktop/src/types/message.ts @@ -1,7 +1,17 @@ -import { Message, MessageEvent, ActionRequired, ToolRequest, ToolResponse } from '../api'; +import { + Message, + MessageEvent, + ActionRequired, + ToolRequest, + ToolResponse, + ToolConfirmationRequest, +} from '../api'; export type ToolRequestMessageContent = ToolRequest & { type: 'toolRequest' }; export type ToolResponseMessageContent = ToolResponse & { type: 'toolResponse' }; +export type ToolConfirmationRequestContent = ToolConfirmationRequest & { + type: 'toolConfirmationRequest'; +}; export type NotificationEvent = Extract; // Compaction response message - must match backend constant @@ -108,6 +118,46 @@ export function getToolConfirmationContent( ); } +export function getToolConfirmationRequestContent( + message: Message +): ToolConfirmationRequestContent | undefined { + return message.content.find( + (content): content is ToolConfirmationRequestContent => + content.type === 'toolConfirmationRequest' + ); +} + +export interface ToolConfirmationData { + id: string; + toolName: string; + arguments: Record; + prompt?: string | null; +} + +export function getAnyToolConfirmationData(message: Message): ToolConfirmationData | undefined { + const confirmationRequest = getToolConfirmationRequestContent(message); + if (confirmationRequest) { + return { + id: confirmationRequest.id, + toolName: confirmationRequest.toolName, + arguments: confirmationRequest.arguments, + prompt: confirmationRequest.prompt, + }; + } + + const actionRequired = getToolConfirmationContent(message); + if (actionRequired && actionRequired.data.actionType === 'toolConfirmation') { + return { + id: actionRequired.data.id, + toolName: actionRequired.data.toolName, + arguments: actionRequired.data.arguments, + prompt: actionRequired.data.prompt, + }; + } + + return undefined; +} + export function getToolConfirmationId( content: ActionRequired & { type: 'actionRequired' } ): string | undefined { @@ -129,12 +179,9 @@ export function getPendingToolConfirmationIds(messages: Message[]): Set } for (const message of messages) { - const confirmation = getToolConfirmationContent(message); - if (confirmation) { - const confirmationId = getToolConfirmationId(confirmation); - if (confirmationId && !respondedIds.has(confirmationId)) { - pendingIds.add(confirmationId); - } + const confirmationData = getAnyToolConfirmationData(message); + if (confirmationData && !respondedIds.has(confirmationData.id)) { + pendingIds.add(confirmationData.id); } }