Skip to main content

hanzo_protocol/
approvals.rs

1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use crate::mcp::RequestId;
5use crate::parse_command::ParsedCommand;
6use crate::protocol::FileChange;
7use schemars::JsonSchema;
8use serde::Deserialize;
9use serde::Serialize;
10use ts_rs::TS;
11
12/// Proposed execpolicy change to allow commands starting with this prefix.
13///
14/// The `command` tokens form the prefix that would be added as an execpolicy
15/// `prefix_rule(..., decision="allow")`, letting the agent bypass approval for
16/// commands that start with this token sequence.
17#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
18#[serde(transparent)]
19#[ts(type = "Array<string>")]
20pub struct ExecPolicyAmendment {
21    pub command: Vec<String>,
22}
23
24impl ExecPolicyAmendment {
25    pub fn new(command: Vec<String>) -> Self {
26        Self { command }
27    }
28
29    pub fn command(&self) -> &[String] {
30        &self.command
31    }
32}
33
34impl From<Vec<String>> for ExecPolicyAmendment {
35    fn from(command: Vec<String>) -> Self {
36        Self { command }
37    }
38}
39
40#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
41#[serde(rename_all = "snake_case")]
42pub enum NetworkApprovalProtocol {
43    // TODO(viyatb): Add websocket protocol variants when managed proxy policy
44    // decisions expose websocket traffic as a distinct approval context.
45    Http,
46    #[serde(alias = "https_connect", alias = "http-connect")]
47    Https,
48    Socks5Tcp,
49    Socks5Udp,
50}
51
52#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
53pub struct NetworkApprovalContext {
54    pub host: String,
55    pub protocol: NetworkApprovalProtocol,
56}
57
58#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
59pub struct ExecApprovalRequestEvent {
60    /// Identifier for the associated command execution item.
61    pub call_id: String,
62    /// Identifier for this specific approval callback.
63    ///
64    /// When absent, the approval is for the command item itself (`call_id`).
65    /// This is present for subcommand approvals (via execve intercept).
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    #[ts(optional)]
68    pub approval_id: Option<String>,
69    /// Turn ID that this command belongs to.
70    /// Uses `#[serde(default)]` for backwards compatibility.
71    #[serde(default)]
72    pub turn_id: String,
73    /// The command to be executed.
74    pub command: Vec<String>,
75    /// The command's working directory.
76    pub cwd: PathBuf,
77    /// Optional human-readable reason for the approval (e.g. retry without sandbox).
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub reason: Option<String>,
80    /// Optional network context for a blocked request that can be approved.
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    #[ts(optional)]
83    pub network_approval_context: Option<NetworkApprovalContext>,
84    /// Proposed execpolicy amendment that can be applied to allow future runs.
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    #[ts(optional)]
87    pub proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
88    pub parsed_cmd: Vec<ParsedCommand>,
89}
90
91impl ExecApprovalRequestEvent {
92    pub fn effective_approval_id(&self) -> String {
93        self.approval_id
94            .clone()
95            .unwrap_or_else(|| self.call_id.clone())
96    }
97}
98
99#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
100pub struct ElicitationRequestEvent {
101    pub server_name: String,
102    #[ts(type = "string | number")]
103    pub id: RequestId,
104    pub message: String,
105    // TODO: MCP servers can request we fill out a schema for the elicitation. We don't support
106    // this yet.
107    // pub requested_schema: ElicitRequestParamsRequestedSchema,
108}
109
110#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
111#[serde(rename_all = "lowercase")]
112pub enum ElicitationAction {
113    Accept,
114    Decline,
115    Cancel,
116}
117
118#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
119pub struct ApplyPatchApprovalRequestEvent {
120    /// Responses API call id for the associated patch apply call, if available.
121    pub call_id: String,
122    /// Turn ID that this patch belongs to.
123    /// Uses `#[serde(default)]` for backwards compatibility with older senders.
124    #[serde(default)]
125    pub turn_id: String,
126    pub changes: HashMap<PathBuf, FileChange>,
127    /// Optional explanatory reason (e.g. request for extra write access).
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub reason: Option<String>,
130    /// When set, the agent is asking the user to allow writes under this root for the remainder of the session.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub grant_root: Option<PathBuf>,
133}