cursor: parse thinking messages in the JSON logs (#1091)
* cursor: parse thinking messages in the JSON logs * cursor: update log parsing - update editTool and deleteTool - add semSearch tool
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use core::str;
|
||||
use std::{path::Path, process::Stdio, sync::Arc, time::Duration};
|
||||
use std::{collections::HashMap, path::Path, process::Stdio, sync::Arc, time::Duration};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use command_group::AsyncCommandGroup;
|
||||
@@ -155,6 +155,8 @@ impl StandardCodingAgentExecutor for Cursor {
|
||||
|
||||
let mut current_assistant_message_buffer = String::new();
|
||||
let mut current_assistant_message_index: Option<usize> = None;
|
||||
let mut current_thinking_message_buffer = String::new();
|
||||
let mut current_thinking_message_index: Option<usize> = None;
|
||||
|
||||
let worktree_str = current_dir.to_string_lossy().to_string();
|
||||
|
||||
@@ -195,11 +197,16 @@ impl StandardCodingAgentExecutor for Cursor {
|
||||
}
|
||||
|
||||
let is_assistant_message = matches!(cursor_json, CursorJson::Assistant { .. });
|
||||
let is_thinking_message = matches!(cursor_json, CursorJson::Thinking { .. });
|
||||
if !is_assistant_message && current_assistant_message_index.is_some() {
|
||||
// flush
|
||||
current_assistant_message_index = None;
|
||||
current_assistant_message_buffer.clear();
|
||||
}
|
||||
if !is_thinking_message && current_thinking_message_index.is_some() {
|
||||
current_thinking_message_index = None;
|
||||
current_thinking_message_buffer.clear();
|
||||
}
|
||||
|
||||
match &cursor_json {
|
||||
CursorJson::System { model, .. } => {
|
||||
@@ -240,6 +247,27 @@ impl StandardCodingAgentExecutor for Cursor {
|
||||
};
|
||||
}
|
||||
}
|
||||
CursorJson::Thinking { text, .. } => {
|
||||
if let Some(chunk) = text
|
||||
&& !chunk.is_empty()
|
||||
{
|
||||
current_thinking_message_buffer.push_str(chunk);
|
||||
let entry = NormalizedEntry {
|
||||
timestamp: None,
|
||||
entry_type: NormalizedEntryType::Thinking,
|
||||
content: current_thinking_message_buffer.clone(),
|
||||
metadata: None,
|
||||
};
|
||||
if let Some(id) = current_thinking_message_index {
|
||||
msg_store.push_patch(ConversationPatch::replace(id, entry));
|
||||
} else {
|
||||
let id = entry_index_provider.next();
|
||||
current_thinking_message_index = Some(id);
|
||||
msg_store
|
||||
.push_patch(ConversationPatch::add_normalized_entry(id, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CursorJson::ToolCall {
|
||||
subtype,
|
||||
@@ -486,6 +514,15 @@ pub enum CursorJson {
|
||||
#[serde(default)]
|
||||
session_id: Option<String>,
|
||||
},
|
||||
#[serde(rename = "thinking")]
|
||||
Thinking {
|
||||
#[serde(default)]
|
||||
subtype: Option<String>,
|
||||
#[serde(default)]
|
||||
text: Option<String>,
|
||||
#[serde(default)]
|
||||
session_id: Option<String>,
|
||||
},
|
||||
#[serde(rename = "tool_call")]
|
||||
ToolCall {
|
||||
#[serde(default)]
|
||||
@@ -517,6 +554,7 @@ impl CursorJson {
|
||||
CursorJson::System { session_id, .. } => session_id.clone(),
|
||||
CursorJson::User { session_id, .. } => session_id.clone(),
|
||||
CursorJson::Assistant { session_id, .. } => session_id.clone(),
|
||||
CursorJson::Thinking { session_id, .. } => session_id.clone(),
|
||||
CursorJson::ToolCall { session_id, .. } => session_id.clone(),
|
||||
CursorJson::Result { .. } => None,
|
||||
CursorJson::Unknown => None,
|
||||
@@ -577,6 +615,12 @@ pub enum CursorToolCall {
|
||||
#[serde(default)]
|
||||
result: Option<serde_json::Value>,
|
||||
},
|
||||
#[serde(rename = "semSearchToolCall")]
|
||||
SemSearch {
|
||||
args: CursorSemSearchArgs,
|
||||
#[serde(default)]
|
||||
result: Option<serde_json::Value>,
|
||||
},
|
||||
#[serde(rename = "writeToolCall")]
|
||||
Write {
|
||||
args: CursorWriteArgs,
|
||||
@@ -593,7 +637,7 @@ pub enum CursorToolCall {
|
||||
Edit {
|
||||
args: CursorEditArgs,
|
||||
#[serde(default)]
|
||||
result: Option<serde_json::Value>,
|
||||
result: Option<CursorEditResult>,
|
||||
},
|
||||
#[serde(rename = "deleteToolCall")]
|
||||
Delete {
|
||||
@@ -628,6 +672,7 @@ impl CursorToolCall {
|
||||
CursorToolCall::LS { .. } => "ls",
|
||||
CursorToolCall::Glob { .. } => "glob",
|
||||
CursorToolCall::Grep { .. } => "grep",
|
||||
CursorToolCall::SemSearch { .. } => "semsearch",
|
||||
CursorToolCall::Write { .. } => "write",
|
||||
CursorToolCall::Read { .. } => "read",
|
||||
CursorToolCall::Edit { .. } => "edit",
|
||||
@@ -659,7 +704,7 @@ impl CursorToolCall {
|
||||
format!("`{path}`"),
|
||||
)
|
||||
}
|
||||
CursorToolCall::Edit { args, .. } => {
|
||||
CursorToolCall::Edit { args, result, .. } => {
|
||||
let path = make_path_relative(&args.path, worktree_path);
|
||||
let mut changes = vec![];
|
||||
|
||||
@@ -694,6 +739,19 @@ impl CursorToolCall {
|
||||
});
|
||||
}
|
||||
|
||||
if changes.is_empty()
|
||||
&& let Some(CursorEditResult::Success(CursorEditSuccessResult {
|
||||
diff_string: Some(diff_string),
|
||||
..
|
||||
})) = &result
|
||||
{
|
||||
let hunks = extract_unified_diff_hunks(diff_string);
|
||||
changes.push(FileChange::Edit {
|
||||
unified_diff: concatenate_diff_hunks(&path, &hunks),
|
||||
has_line_numbers: false,
|
||||
});
|
||||
}
|
||||
|
||||
(
|
||||
ActionType::FileEdit {
|
||||
path: path.clone(),
|
||||
@@ -707,7 +765,7 @@ impl CursorToolCall {
|
||||
(
|
||||
ActionType::FileEdit {
|
||||
path: path.clone(),
|
||||
changes: vec![],
|
||||
changes: vec![FileChange::Delete],
|
||||
},
|
||||
format!("`{path}`"),
|
||||
)
|
||||
@@ -731,6 +789,15 @@ impl CursorToolCall {
|
||||
format!("`{pattern}`"),
|
||||
)
|
||||
}
|
||||
CursorToolCall::SemSearch { args, .. } => {
|
||||
let query = &args.query;
|
||||
(
|
||||
ActionType::Search {
|
||||
query: query.clone(),
|
||||
},
|
||||
format!("`{query}`"),
|
||||
)
|
||||
}
|
||||
CursorToolCall::Glob { args, .. } => {
|
||||
let pattern = args.glob_pattern.clone().unwrap_or_else(|| "*".to_string());
|
||||
if let Some(path) = args.path.as_ref().or(args.target_directory.as_ref()) {
|
||||
@@ -940,7 +1007,7 @@ pub struct CursorLsArgs {
|
||||
pub struct CursorGlobArgs {
|
||||
#[serde(default, alias = "globPattern", alias = "glob_pattern")]
|
||||
pub glob_pattern: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(default, alias = "targetDirectory")]
|
||||
pub path: Option<String>,
|
||||
#[serde(default, alias = "target_directory")]
|
||||
pub target_directory: Option<String>,
|
||||
@@ -965,6 +1032,15 @@ pub struct CursorGrepArgs {
|
||||
pub r#type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct CursorSemSearchArgs {
|
||||
pub query: String,
|
||||
#[serde(default, alias = "targetDirectories")]
|
||||
pub target_directories: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub explanation: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct CursorWriteArgs {
|
||||
pub path: String,
|
||||
@@ -998,6 +1074,32 @@ pub struct CursorEditArgs {
|
||||
pub multi_str_replace: Option<CursorMultiStrReplace>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum CursorEditResult {
|
||||
Success(CursorEditSuccessResult),
|
||||
#[serde(untagged)]
|
||||
Unknown {
|
||||
#[serde(flatten)]
|
||||
data: HashMap<String, serde_json::Value>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct CursorEditSuccessResult {
|
||||
pub path: String,
|
||||
#[serde(default, rename = "resultForModel")]
|
||||
pub result_for_model: Option<String>,
|
||||
#[serde(default, rename = "linesAdded")]
|
||||
pub lines_added: Option<u64>,
|
||||
#[serde(default, rename = "linesRemoved")]
|
||||
pub lines_removed: Option<u64>,
|
||||
#[serde(default, rename = "diffString")]
|
||||
pub diff_string: Option<String>,
|
||||
#[serde(default, rename = "afterFullFileContent")]
|
||||
pub after_full_file_content: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
||||
pub struct CursorApplyPatch {
|
||||
#[serde(rename = "patchContent")]
|
||||
|
||||
Reference in New Issue
Block a user