Skip to content

Commit cf8889e

Browse files
feat: allow mutative actions with use_aws (#686)
1 parent 2219a94 commit cf8889e

File tree

3 files changed

+40
-26
lines changed

3 files changed

+40
-26
lines changed

crates/q_cli/src/cli/chat/tools/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl Tool {
6363
Tool::FsRead(_) => "Read from filesystem",
6464
Tool::FsWrite(_) => "Write to filesystem",
6565
Tool::ExecuteBash(_) => "Execute shell command",
66-
Tool::UseAws(_) => "Read AWS resources",
66+
Tool::UseAws(_) => "Use AWS CLI",
6767
}
6868
}
6969

@@ -73,7 +73,7 @@ impl Tool {
7373
Tool::FsRead(_) => false,
7474
Tool::FsWrite(_) => true,
7575
Tool::ExecuteBash(_) => true,
76-
Tool::UseAws(_) => false,
76+
Tool::UseAws(use_aws) => use_aws.requires_consent(),
7777
}
7878
}
7979

crates/q_cli/src/cli/chat/tools/tool_index.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
},
7676
{
7777
"name": "use_aws",
78-
"description": "Make an AWS CLI api call with the specified service, operation, and parameters. The arguments MUST conform to the AWS CLI specification. You may not create resources or perform any write or mutating actions. You may only use this tool to call read operations with names that start with: get, describe, list, search, batch_get.",
78+
"description": "Make an AWS CLI api call with the specified service, operation, and parameters. The arguments MUST conform to the AWS CLI specification.",
7979
"input_schema": {
8080
"type": "object",
8181
"properties": {

crates/q_cli/src/cli/chat/tools/use_aws.rs

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,6 @@ use super::{
2121

2222
const ALLOWED_OPS: [&str; 6] = ["get", "describe", "list", "ls", "search", "batch_get"];
2323

24-
#[derive(Debug, thiserror::Error)]
25-
enum AwsToolError {
26-
ForbiddenOperation(String),
27-
}
28-
29-
impl std::fmt::Display for AwsToolError {
30-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31-
match self {
32-
AwsToolError::ForbiddenOperation(op) => Ok(writeln!(f, "Forbidden operation encountered: {}", op)?),
33-
}
34-
}
35-
}
36-
3724
// TODO: we should perhaps composite this struct with an interface that we can use to mock the
3825
// actual cli with. That will allow us to more thoroughly test it.
3926
#[derive(Debug, Deserialize)]
@@ -47,14 +34,8 @@ pub struct UseAws {
4734
}
4835

4936
impl UseAws {
50-
fn validate_operation(&self) -> Result<(), AwsToolError> {
51-
let operation_name = &self.operation_name;
52-
for op in ALLOWED_OPS {
53-
if self.operation_name.starts_with(op) {
54-
return Ok(());
55-
}
56-
}
57-
Err(AwsToolError::ForbiddenOperation(operation_name.clone()))
37+
pub fn requires_consent(&self) -> bool {
38+
!ALLOWED_OPS.iter().any(|op| self.operation_name.starts_with(op))
5839
}
5940

6041
pub async fn invoke(&self, _ctx: &Context, _updates: impl Write) -> Result<InvokeOutput> {
@@ -129,15 +110,48 @@ impl UseAws {
129110
}
130111

131112
pub async fn validate(&mut self, _ctx: &Context) -> Result<()> {
132-
self.validate_operation()
133-
.wrap_err_with(|| format!("Unable to spawn command '{:?}'", &self))
113+
Ok(())
134114
}
135115
}
136116

137117
#[cfg(test)]
138118
mod tests {
139119
use super::*;
140120

121+
macro_rules! use_aws {
122+
($value:tt) => {
123+
serde_json::from_value::<UseAws>(serde_json::json!($value)).unwrap()
124+
};
125+
}
126+
127+
#[test]
128+
fn test_requires_consent() {
129+
let cmd = use_aws! {{
130+
"service_name": "ecs",
131+
"operation_name": "list-task-definitions",
132+
"region": "us-west-2",
133+
"profile_name": "default",
134+
"label": ""
135+
}};
136+
assert!(!cmd.requires_consent());
137+
let cmd = use_aws! {{
138+
"service_name": "lambda",
139+
"operation_name": "list-functions",
140+
"region": "us-west-2",
141+
"profile_name": "default",
142+
"label": ""
143+
}};
144+
assert!(!cmd.requires_consent());
145+
let cmd = use_aws! {{
146+
"service_name": "s3",
147+
"operation_name": "put-object",
148+
"region": "us-west-2",
149+
"profile_name": "default",
150+
"label": ""
151+
}};
152+
assert!(cmd.requires_consent());
153+
}
154+
141155
#[tokio::test]
142156
#[ignore = "not in ci"]
143157
async fn test_aws_read_only() {

0 commit comments

Comments
 (0)