Skip to content

Commit 415624a

Browse files
author
kiran-garre
committed
feat: Add basic todo list tool
Current functionality: - Supports interally creating a todo list and completing tasks - Displays list to the user - Can be somewhat reliably invoked by explicitly asking Q for a todo list - Q sometimes automatically generates a todo list on some prompts Future chagnes: - Add context so todo lists can be used across sessions - Allow Q to add and remove tasks
1 parent 264c308 commit 415624a

File tree

5 files changed

+252
-0
lines changed

5 files changed

+252
-0
lines changed

crates/chat-cli/src/cli/chat/tool_manager.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ use crate::cli::chat::tools::fs_write::FsWrite;
8080
use crate::cli::chat::tools::gh_issue::GhIssue;
8181
use crate::cli::chat::tools::knowledge::Knowledge;
8282
use crate::cli::chat::tools::thinking::Thinking;
83+
use crate::cli::chat::tools::todo::TodoInput;
8384
use crate::cli::chat::tools::use_aws::UseAws;
8485
use crate::cli::chat::tools::{
8586
Tool,
@@ -976,6 +977,7 @@ impl ToolManager {
976977
"report_issue" => Tool::GhIssue(serde_json::from_value::<GhIssue>(value.args).map_err(map_err)?),
977978
"thinking" => Tool::Thinking(serde_json::from_value::<Thinking>(value.args).map_err(map_err)?),
978979
"knowledge" => Tool::Knowledge(serde_json::from_value::<Knowledge>(value.args).map_err(map_err)?),
980+
"todoooooo" => Tool::Todo(serde_json::from_value::<TodoInput>(value.args).map_err(map_err)?),
979981
// Note that this name is namespaced with server_name{DELIMITER}tool_name
980982
name => {
981983
// Note: tn_map also has tools that underwent no transformation. In otherwords, if
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#ifndef TOKEN_H
2+
#define TOKEN_H
3+
4+
// Token types for SimpleLang
5+
typedef enum {
6+
// Literals
7+
TOKEN_NUMBER,
8+
TOKEN_IDENTIFIER,
9+
TOKEN_STRING,
10+
11+
// Keywords
12+
TOKEN_IF,
13+
TOKEN_ELSE,
14+
TOKEN_WHILE,
15+
TOKEN_PRINT,
16+
TOKEN_READ,
17+
TOKEN_INT,
18+
TOKEN_STRING_TYPE,
19+
20+
// Operators
21+
TOKEN_PLUS,
22+
TOKEN_MINUS,
23+
TOKEN_MULTIPLY,
24+
TOKEN_DIVIDE,
25+
TOKEN_ASSIGN,
26+
TOKEN_EQUAL,
27+
TOKEN_NOT_EQUAL,
28+
TOKEN_LESS,
29+
TOKEN_GREATER,
30+
TOKEN_LESS_EQUAL,
31+
TOKEN_GREATER_EQUAL,
32+
33+
// Delimiters
34+
TOKEN_SEMICOLON,
35+
TOKEN_COMMA,
36+
TOKEN_LEFT_PAREN,
37+
TOKEN_RIGHT_PAREN,
38+
TOKEN_LEFT_BRACE,
39+
TOKEN_RIGHT_BRACE,
40+
41+
// Special
42+
TOKEN_NEWLINE,
43+
TOKEN_EOF,
44+
TOKEN_ERROR
45+
} TokenType;
46+
47+
typedef struct {
48+
TokenType type;
49+
char* lexeme;
50+
int line;
51+
int column;
52+
union {
53+
int int_value;
54+
char* string_value;
55+
} value;
56+
} Token;
57+
58+
// Function declarations
59+
Token* create_token(TokenType type, char* lexeme, int line, int column);
60+
void free_token(Token* token);
61+
const char* token_type_to_string(TokenType type);
62+
63+
#endif

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod gh_issue;
66
pub mod knowledge;
77
pub mod thinking;
88
pub mod use_aws;
9+
pub mod todo;
910

1011
use std::collections::{
1112
HashMap,
@@ -36,6 +37,7 @@ use serde::{
3637
};
3738
use thinking::Thinking;
3839
use use_aws::UseAws;
40+
use todo::TodoInput;
3941

4042
use super::consts::MAX_TOOL_RESPONSE_SIZE;
4143
use super::util::images::RichImageBlocks;
@@ -53,6 +55,7 @@ pub enum Tool {
5355
GhIssue(GhIssue),
5456
Knowledge(Knowledge),
5557
Thinking(Thinking),
58+
Todo(TodoInput),
5659
}
5760

5861
impl Tool {
@@ -70,6 +73,7 @@ impl Tool {
7073
Tool::GhIssue(_) => "gh_issue",
7174
Tool::Knowledge(_) => "knowledge",
7275
Tool::Thinking(_) => "thinking (prerelease)",
76+
Tool::Todo(_) => "todoooooo"
7377
}
7478
.to_owned()
7579
}
@@ -85,6 +89,7 @@ impl Tool {
8589
Tool::GhIssue(_) => false,
8690
Tool::Knowledge(_) => false,
8791
Tool::Thinking(_) => false,
92+
Tool::Todo(_) => false,
8893
}
8994
}
9095

@@ -99,6 +104,8 @@ impl Tool {
99104
Tool::GhIssue(gh_issue) => gh_issue.invoke(os, stdout).await,
100105
Tool::Knowledge(knowledge) => knowledge.invoke(os, stdout).await,
101106
Tool::Thinking(think) => think.invoke(stdout).await,
107+
Tool::Todo(todo) => todo.clone().invoke(os, stdout).await,
108+
102109
}
103110
}
104111

@@ -113,6 +120,7 @@ impl Tool {
113120
Tool::GhIssue(gh_issue) => gh_issue.queue_description(output),
114121
Tool::Knowledge(knowledge) => knowledge.queue_description(os, output).await,
115122
Tool::Thinking(thinking) => thinking.queue_description(output),
123+
Tool::Todo(todo) => todo.queue_description(os, output),
116124
}
117125
}
118126

@@ -127,6 +135,7 @@ impl Tool {
127135
Tool::GhIssue(gh_issue) => gh_issue.validate(os).await,
128136
Tool::Knowledge(knowledge) => knowledge.validate(os).await,
129137
Tool::Thinking(think) => think.validate(os).await,
138+
Tool::Todo(todo) => todo.validate(os).await,
130139
}
131140
}
132141
}
@@ -238,6 +247,7 @@ impl ToolPermissions {
238247
"report_issue" => "trusted".dark_green().bold(),
239248
"knowledge" => "trusted".dark_green().bold(),
240249
"thinking" => "trusted (prerelease)".dark_green().bold(),
250+
"todoooooo" => "trusted".dark_green().bold(),
241251
_ if self.trust_all => "trusted".dark_grey().bold(),
242252
_ => "not trusted".dark_grey(),
243253
};
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use std::io::Write;
2+
use serde::{
3+
Deserialize,
4+
Serialize
5+
};
6+
7+
use crossterm::{
8+
queue,
9+
style,
10+
};
11+
12+
use eyre::{
13+
Result,
14+
};
15+
16+
use super::{
17+
InvokeOutput,
18+
MAX_TOOL_RESPONSE_SIZE,
19+
OutputKind,
20+
};
21+
22+
use crate::os::Os;
23+
24+
const TODO_STATE_PATH: &str = "todo_state.json";
25+
/*
26+
Prompts that kinda work:
27+
28+
Make a simple todo list for writing a hello world program in C
29+
Execute the steps for me and mark them off on the todo list after you complete each one (as you go).
30+
31+
Design a multi-file compiler for a small language. In each file, include minimal skeleton code for implementing the compiler.
32+
*/
33+
34+
#[derive(Debug, Clone, Deserialize)]
35+
#[serde(tag = "command")]
36+
37+
pub enum TodoInput {
38+
39+
#[serde(rename = "create")]
40+
Create { tasks: Vec<String> },
41+
42+
// #[serde(rename = "add")]
43+
// Add { new_task: String },
44+
45+
#[serde(rename = "complete")]
46+
Complete { completed_indices: Vec<usize> },
47+
}
48+
49+
#[derive(Debug, Default, Serialize, Deserialize)]
50+
pub struct TodoState {
51+
pub tasks: Vec<String>,
52+
pub completed: Vec<bool>,
53+
}
54+
55+
impl TodoState {
56+
pub async fn load(os: &Os) -> Result<Self> {
57+
if os.fs.exists(TODO_STATE_PATH) {
58+
let json_str = os.fs.read_to_string(TODO_STATE_PATH).await?;
59+
match serde_json::from_str::<Self>(&json_str) {
60+
Ok(state_struct) => Ok(state_struct),
61+
Err(_) => Ok(Self::default())
62+
}
63+
} else {
64+
Ok(Self::default())
65+
}
66+
}
67+
68+
pub async fn save(&self, os: &Os) -> Result<()> {
69+
if !os.fs.exists(TODO_STATE_PATH) {
70+
os.fs.create_new(TODO_STATE_PATH).await?;
71+
}
72+
let json_str = serde_json::to_string(self)?;
73+
os.fs.write(TODO_STATE_PATH, json_str).await?;
74+
Ok(())
75+
}
76+
77+
pub fn display_list(&self, output: &mut impl Write) -> Result<()> {
78+
queue!(
79+
output,
80+
style::Print("TODO:\n"),
81+
)?;
82+
for (task, completed) in self.tasks.iter().zip(self.completed.iter()) {
83+
TodoState::queue_next(output, task.clone(), *completed)?;
84+
}
85+
Ok(())
86+
}
87+
88+
fn queue_next(output: &mut impl Write, task: String, completed: bool) -> Result<()> {
89+
if completed {
90+
queue!(
91+
output,
92+
style::SetAttribute(style::Attribute::Italic),
93+
style::SetForegroundColor(style::Color::Green),
94+
style::Print(" ■ "),
95+
style::SetForegroundColor(style::Color::DarkGrey),
96+
// style::SetAttribute(style::Attribute::CrossedOut),
97+
style::Print(format!("{}\n", task)),
98+
// style::SetAttribute(style::Attribute::NotCrossedOut),
99+
style::SetAttribute(style::Attribute::NoItalic),
100+
)?;
101+
} else {
102+
queue!(
103+
output,
104+
style::SetForegroundColor(style::Color::Reset),
105+
style::Print(format!(" ☐ {}\n", task)),
106+
)?;
107+
}
108+
Ok(())
109+
}
110+
}
111+
112+
113+
impl TodoInput {
114+
pub async fn invoke(&self, os: &Os, output: &mut impl Write) -> Result<InvokeOutput> {
115+
let mut state = TodoState::load(os).await?;
116+
match self {
117+
TodoInput::Create { tasks } => {
118+
state.tasks = tasks.clone();
119+
state.completed = vec![false; state.tasks.len()];
120+
},
121+
TodoInput::Complete { completed_indices } => {
122+
completed_indices.iter().for_each(|i| {
123+
if *i > state.completed.len() {
124+
return ();
125+
}
126+
state.completed[*i as usize] = true;
127+
});
128+
}
129+
};
130+
state.display_list(output)?;
131+
output.flush()?;
132+
state.save(os).await?;
133+
Ok(Default::default())
134+
// execute!(
135+
// output,
136+
// style::Print("Q requested to use the TODO tool!"),
137+
// )?;
138+
// panic!("panicked");
139+
}
140+
141+
pub fn queue_description(&self, os: &Os, output: &mut impl Write) -> Result<()> {
142+
Ok(())
143+
}
144+
145+
pub async fn validate(&mut self, os: &Os) -> Result<()> {
146+
Ok(())
147+
}
148+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,5 +232,34 @@
232232
},
233233
"required": ["command"]
234234
}
235+
},
236+
"todo": {
237+
"name": "todoooooo",
238+
"description": "A tool for creating a TODO list and keeping track of tasks. This tool should be used any time the user gives you a task with multiple steps. A TODO list should be made BEFORE executing any steps. Steps should be marked off as you complete them. You should use this tool when appropriate, even if it is not explicitly requested by the user.",
239+
"input_schema": {
240+
"type": "object",
241+
"properties": {
242+
"command": {
243+
"type": "string",
244+
"enum": ["create, complete"],
245+
"description": "The command to run. Allowed options are `add`, `complete`."
246+
},
247+
"tasks": {
248+
"description": "Required paramter of `create` command containing the list of tasks to be added to the TODO list.",
249+
"type": "array",
250+
"items": {
251+
"type": "string"
252+
}
253+
},
254+
"completed_indices": {
255+
"description": "Required parameter of `complete` command containing the 0-INDEXED numbers of EVERY completed task.",
256+
"type": "array",
257+
"items": {
258+
"type": "integer"
259+
}
260+
}
261+
},
262+
"required": ["command"]
263+
}
235264
}
236265
}

0 commit comments

Comments
 (0)