Skip to content

Commit 202b5b7

Browse files
committed
port thinking tool
1 parent 6a117b5 commit 202b5b7

File tree

4 files changed

+105
-29
lines changed

4 files changed

+105
-29
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,11 @@ impl ToolManager {
501501
let tx = self.loading_status_sender.take();
502502
let display_task = self.loading_display_task.take();
503503
let tool_specs = {
504-
let tool_specs = serde_json::from_str::<HashMap<String, ToolSpec>>(include_str!("tools/tool_index.json"))?;
504+
let mut tool_specs =
505+
serde_json::from_str::<HashMap<String, ToolSpec>>(include_str!("tools/tool_index.json"))?;
506+
if !crate::cli::chat::tools::thinking::Thinking::is_enabled() {
507+
tool_specs.remove("q_think_tool");
508+
}
505509
Arc::new(Mutex::new(tool_specs))
506510
};
507511
let conversation_id = self.conversation_id.clone();
@@ -672,6 +676,7 @@ impl ToolManager {
672676
"execute_bash" => Tool::ExecuteBash(serde_json::from_value::<ExecuteBash>(value.args).map_err(map_err)?),
673677
"use_aws" => Tool::UseAws(serde_json::from_value::<UseAws>(value.args).map_err(map_err)?),
674678
"report_issue" => Tool::GhIssue(serde_json::from_value::<GhIssue>(value.args).map_err(map_err)?),
679+
"q_think_tool" => Tool::Thinking(serde_json::from_value::<Think>(value.args).map_err(map_err)?),
675680
// Note that this name is namespaced with server_name{DELIMITER}tool_name
676681
name => {
677682
let name = self.tn_map.get(name).map_or(name, String::as_str);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod execute_bash;
33
pub mod fs_read;
44
pub mod fs_write;
55
pub mod gh_issue;
6+
pub mod thinking;
67
pub mod use_aws;
78

89
use std::collections::HashMap;
@@ -27,6 +28,7 @@ use serde::{
2728
Deserialize,
2829
Serialize,
2930
};
31+
use thinking::Thinking;
3032
use use_aws::UseAws;
3133

3234
use super::consts::MAX_TOOL_RESPONSE_SIZE;
@@ -42,6 +44,7 @@ pub enum Tool {
4244
UseAws(UseAws),
4345
Custom(CustomTool),
4446
GhIssue(GhIssue),
47+
Thinking(Thinking),
4548
}
4649

4750
impl Tool {
@@ -54,6 +57,7 @@ impl Tool {
5457
Tool::UseAws(_) => "use_aws",
5558
Tool::Custom(custom_tool) => &custom_tool.name,
5659
Tool::GhIssue(_) => "gh_issue",
60+
Tool::Thinking(_) => "thinking (prerelease)",
5761
}
5862
.to_owned()
5963
}
@@ -67,6 +71,7 @@ impl Tool {
6771
Tool::UseAws(use_aws) => use_aws.requires_acceptance(),
6872
Tool::Custom(_) => true,
6973
Tool::GhIssue(_) => false,
74+
Tool::Thinking(_) => false,
7075
}
7176
}
7277

@@ -79,6 +84,7 @@ impl Tool {
7984
Tool::UseAws(use_aws) => use_aws.invoke(context, updates).await,
8085
Tool::Custom(custom_tool) => custom_tool.invoke(context, updates).await,
8186
Tool::GhIssue(gh_issue) => gh_issue.invoke(updates).await,
87+
Tool::Thinking(think) => think.invoke(updates).await,
8288
}
8389
}
8490

@@ -91,6 +97,7 @@ impl Tool {
9197
Tool::UseAws(use_aws) => use_aws.queue_description(updates),
9298
Tool::Custom(custom_tool) => custom_tool.queue_description(updates),
9399
Tool::GhIssue(gh_issue) => gh_issue.queue_description(updates),
100+
Tool::Thinking(thinking) => thinking.queue_description(updates),
94101
}
95102
}
96103

@@ -103,6 +110,7 @@ impl Tool {
103110
Tool::UseAws(use_aws) => use_aws.validate(ctx).await,
104111
Tool::Custom(custom_tool) => custom_tool.validate(ctx).await,
105112
Tool::GhIssue(gh_issue) => gh_issue.validate(ctx).await,
113+
Tool::Thinking(think) => think.validate(ctx).await,
106114
}
107115
}
108116
}
@@ -176,6 +184,7 @@ impl ToolPermissions {
176184
"execute_bash" => "trust read-only commands".dark_grey(),
177185
"use_aws" => "trust read-only commands".dark_grey(),
178186
"report_issue" => "trusted".dark_green().bold(),
187+
"thinking" => "trusted (prerelease)".dark_green().bold(),
179188
_ => "not trusted".dark_grey(),
180189
};
181190

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::io::Write;
2+
3+
use crossterm::queue;
4+
use crossterm::style::{
5+
self,
6+
Color,
7+
};
8+
use eyre::Result;
9+
use serde::Deserialize;
10+
11+
use super::{
12+
InvokeOutput,
13+
OutputKind,
14+
};
15+
use crate::fig_settings::settings;
16+
17+
/// The Think tool allows the model to reason through complex problems during response generation.
18+
/// It provides a dedicated space for the model to process information from tool call results,
19+
/// navigate complex decision trees, and improve the quality of responses in multi-step scenarios.
20+
///
21+
/// This is a beta feature that can be enabled/disabled via settings:
22+
/// `q settings chat.enableThinking true`
23+
#[derive(Debug, Clone, Deserialize)]
24+
pub struct Thinking {
25+
/// The thought content that the model wants to process
26+
pub thought: String,
27+
}
28+
29+
impl Thinking {
30+
/// Checks if the thinking feature is enabled in settings
31+
pub fn is_enabled() -> bool {
32+
// Default to enabled if setting doesn't exist or can't be read
33+
settings::get_bool_or("chat.enableThinking", true)
34+
}
35+
36+
/// Queues up a description of the think tool for the user
37+
pub fn queue_description(&self, updates: &mut impl Write) -> Result<()> {
38+
// Only show a description if there's actual thought content
39+
if !self.thought.trim().is_empty() {
40+
// Show a preview of the thought that will be displayed
41+
queue!(
42+
updates,
43+
style::SetForegroundColor(Color::Blue),
44+
style::Print("I'll share my reasoning process: "),
45+
style::SetForegroundColor(Color::Reset),
46+
style::Print(&self.thought),
47+
style::Print("\n")
48+
)?;
49+
}
50+
Ok(())
51+
}
52+
53+
/// Invokes the think tool. This doesn't actually perform any system operations,
54+
/// it's purely for the model's internal reasoning process.
55+
pub async fn invoke(&self, _updates: &mut impl Write) -> Result<InvokeOutput> {
56+
// The think tool always returns an empty output because:
57+
// 1. When enabled with content: We've already shown the thought in queue_description
58+
// 2. When disabled or empty: Nothing should be shown
59+
Ok(InvokeOutput {
60+
output: OutputKind::Text(String::new()),
61+
})
62+
}
63+
64+
/// Validates the thought - accepts empty thoughts
65+
pub async fn validate(&mut self, _ctx: &crate::fig_os_shim::Context) -> Result<()> {
66+
// We accept empty thoughts - they'll just be ignored
67+
// This makes the tool more robust and prevents errors from blocking the model
68+
Ok(())
69+
}
70+
}

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

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
"description": "Bash command to execute"
1111
}
1212
},
13-
"required": [
14-
"command"
15-
]
13+
"required": ["command"]
1614
}
1715
},
1816
"fs_read": {
@@ -27,11 +25,7 @@
2725
},
2826
"mode": {
2927
"type": "string",
30-
"enum": [
31-
"Line",
32-
"Directory",
33-
"Search"
34-
],
28+
"enum": ["Line", "Directory", "Search"],
3529
"description": "The mode to run in: `Line`, `Directory`, `Search`. `Line` and `Search` are only for text files, and `Directory` is only for directories."
3630
},
3731
"start_line": {
@@ -59,10 +53,7 @@
5953
"default": 0
6054
}
6155
},
62-
"required": [
63-
"path",
64-
"mode"
65-
]
56+
"required": ["path", "mode"]
6657
}
6758
},
6859
"fs_write": {
@@ -73,12 +64,7 @@
7364
"properties": {
7465
"command": {
7566
"type": "string",
76-
"enum": [
77-
"create",
78-
"str_replace",
79-
"insert",
80-
"append"
81-
],
67+
"enum": ["create", "str_replace", "insert", "append"],
8268
"description": "The commands to run. Allowed options are: `create`, `str_replace`, `insert`, `append`."
8369
},
8470
"file_text": {
@@ -102,10 +88,7 @@
10288
"type": "string"
10389
}
10490
},
105-
"required": [
106-
"command",
107-
"path"
108-
]
91+
"required": ["command", "path"]
10992
}
11093
},
11194
"use_aws": {
@@ -139,12 +122,7 @@
139122
"description": "Human readable description of the api that is being called."
140123
}
141124
},
142-
"required": [
143-
"region",
144-
"service_name",
145-
"operation_name",
146-
"label"
147-
]
125+
"required": ["region", "service_name", "operation_name", "label"]
148126
}
149127
},
150128
"gh_issue": {
@@ -172,5 +150,19 @@
172150
},
173151
"required": ["title"]
174152
}
153+
},
154+
"thinking": {
155+
"name": "thinking",
156+
"description": "Thinking is an internal reasoning mechanism improving the quality of complex tasks by breaking their atomic actions down; use it specifically for multi-step problems requiring step-by-step dependencies, reasoning through multiple constraints, synthesizing results from previous tool calls, planning intricate sequences of actions, troubleshooting complex errors, or making decisions involving multiple trade-offs. Avoid using it for straightforward tasks, basic information retrieval, summaries, always clearly define the reasoning challenge, structure thoughts explicitly, consider multiple perspectives, and summarize key insights before important decisions or complex tool interactions.",
157+
"input_schema": {
158+
"type": "object",
159+
"properties": {
160+
"thought": {
161+
"type": "string",
162+
"description": "A reflective note or intermediate reasoning step such as \"The user needs to prepare their application for production. I need to complete three major asks including 1: building their code from source, 2: bundling their release artifacts together, and 3: signing the application bundle."
163+
}
164+
},
165+
"required": ["thought"]
166+
}
175167
}
176168
}

0 commit comments

Comments
 (0)