mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-06 21:52:51 +08:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfd1e740b2 | ||
|
|
65edad6a21 |
Generated
+1
-1
@@ -1128,7 +1128,7 @@ checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-byok-desktop"
|
name = "cursor-byok-desktop"
|
||||||
version = "0.1.0-beta.8"
|
version = "0.1.0-beta.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"cursor-server",
|
"cursor-server",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cursor-byok-desktop",
|
"name": "cursor-byok-desktop",
|
||||||
"version": "0.1.0-beta.8",
|
"version": "0.1.0-beta.10",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cursor-byok-desktop",
|
"name": "cursor-byok-desktop",
|
||||||
"version": "0.1.0-beta.8",
|
"version": "0.1.0-beta.10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/dom": "^1.8.0",
|
"@floating-ui/dom": "^1.8.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cursor-byok-desktop",
|
"name": "cursor-byok-desktop",
|
||||||
"version": "0.1.0-beta.8",
|
"version": "0.1.0-beta.10",
|
||||||
"description": "Cursor BYOK desktop management application",
|
"description": "Cursor BYOK desktop management application",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cursor-byok-desktop"
|
name = "cursor-byok-desktop"
|
||||||
version = "0.1.0-beta.8"
|
version = "0.1.0-beta.10"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Cursor BYOK",
|
"productName": "Cursor BYOK",
|
||||||
"version": "0.1.0-beta.8",
|
"version": "0.1.0-beta.10",
|
||||||
"identifier": "dev.cursorbyok.desktop",
|
"identifier": "dev.cursorbyok.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@@ -274,20 +274,7 @@ pub(crate) async fn prepare(
|
|||||||
};
|
};
|
||||||
RunAction::Resume { pending_tool_round }
|
RunAction::Resume { pending_tool_round }
|
||||||
};
|
};
|
||||||
let kind = match (request.subagent_type_name.as_deref(), parent) {
|
let kind = run_kind(request.subagent_type_name.as_deref(), parent)?;
|
||||||
(None, _) => RunKind::Root,
|
|
||||||
(Some(name), Some((parent_run_id, parent_tool_call_id))) => RunKind::Subagent {
|
|
||||||
parent_run_id,
|
|
||||||
parent_tool_call_id,
|
|
||||||
kind: model::subagent_kind(name),
|
|
||||||
background: false,
|
|
||||||
},
|
|
||||||
(Some(_), None) => {
|
|
||||||
return Err(Error::Protocol(
|
|
||||||
"subagent Run is missing its parent Run and tool call".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let exec = exec_context(
|
let exec = exec_context(
|
||||||
request,
|
request,
|
||||||
&request_context,
|
&request_context,
|
||||||
@@ -322,6 +309,21 @@ pub(crate) async fn prepare(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_kind(subagent_type_name: Option<&str>, parent: Option<(RunId, String)>) -> Result<RunKind> {
|
||||||
|
match (subagent_type_name, parent) {
|
||||||
|
(None | Some("side-chat"), _) => Ok(RunKind::Root),
|
||||||
|
(Some(name), Some((parent_run_id, parent_tool_call_id))) => Ok(RunKind::Subagent {
|
||||||
|
parent_run_id,
|
||||||
|
parent_tool_call_id,
|
||||||
|
kind: model::subagent_kind(name),
|
||||||
|
background: false,
|
||||||
|
}),
|
||||||
|
(Some(_), None) => Err(Error::Protocol(
|
||||||
|
"subagent Run is missing its parent Run and tool call".into(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_prompt_root(messages: &[CanonicalMessage]) -> Result<()> {
|
fn validate_prompt_root(messages: &[CanonicalMessage]) -> Result<()> {
|
||||||
let prompts = messages
|
let prompts = messages
|
||||||
.iter()
|
.iter()
|
||||||
@@ -579,6 +581,23 @@ mod tests {
|
|||||||
assert!(mode_from_proto(99).is_err());
|
assert!(mode_from_proto(99).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn side_chat_without_task_parent_is_an_independent_root_run() {
|
||||||
|
assert!(matches!(
|
||||||
|
run_kind(Some("side-chat"), None).unwrap(),
|
||||||
|
RunKind::Root
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn task_subagent_without_parent_is_still_rejected() {
|
||||||
|
assert!(matches!(
|
||||||
|
run_kind(Some("explore"), None),
|
||||||
|
Err(Error::Protocol(message))
|
||||||
|
if message == "subagent Run is missing its parent Run and tool call"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_user_message_consumes_the_mode_instead_of_history_mode() {
|
fn current_user_message_consumes_the_mode_instead_of_history_mode() {
|
||||||
let request = pb::AgentRunRequest {
|
let request = pb::AgentRunRequest {
|
||||||
|
|||||||
@@ -119,17 +119,98 @@ fn delete(value: &pb::DeleteResult) -> Result<(String, bool)> {
|
|||||||
fn grep(value: &pb::GrepResult) -> Result<(String, bool)> {
|
fn grep(value: &pb::GrepResult) -> Result<(String, bool)> {
|
||||||
use pb::grep_result::Result as R;
|
use pb::grep_result::Result as R;
|
||||||
match value.result.as_ref().ok_or_else(|| missing("grep"))? {
|
match value.result.as_ref().ok_or_else(|| missing("grep"))? {
|
||||||
R::Success(value) => Ok((
|
R::Success(value) => Ok((grep_success(value), false)),
|
||||||
format!(
|
|
||||||
"grep success pattern={} mode={}",
|
|
||||||
value.pattern, value.output_mode
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
R::Error(value) => Ok((value.error.clone(), true)),
|
R::Error(value) => Ok((value.error.clone(), true)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn grep_success(value: &pb::GrepSuccess) -> String {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
if let Some(result) = &value.active_editor_result {
|
||||||
|
grep_union(result, &mut lines);
|
||||||
|
}
|
||||||
|
let mut workspaces = value.workspace_results.iter().collect::<Vec<_>>();
|
||||||
|
workspaces.sort_unstable_by_key(|(name, _)| *name);
|
||||||
|
for (_, result) in workspaces {
|
||||||
|
grep_union(result, &mut lines);
|
||||||
|
}
|
||||||
|
if lines.is_empty() {
|
||||||
|
format!(
|
||||||
|
"No matches found for pattern `{}` in {}",
|
||||||
|
value.pattern, value.path
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lines.join("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grep_union(value: &pb::GrepUnionResult, lines: &mut Vec<String>) {
|
||||||
|
use pb::grep_union_result::Result as R;
|
||||||
|
match value.result.as_ref() {
|
||||||
|
Some(R::Files(value)) => {
|
||||||
|
lines.extend(value.files.iter().cloned());
|
||||||
|
grep_truncation(
|
||||||
|
value.client_truncated,
|
||||||
|
value.ripgrep_truncated,
|
||||||
|
value.total_files,
|
||||||
|
"files",
|
||||||
|
lines,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(R::Count(value)) => {
|
||||||
|
lines.extend(
|
||||||
|
value
|
||||||
|
.counts
|
||||||
|
.iter()
|
||||||
|
.map(|count| format!("{}:{}", count.file, count.count)),
|
||||||
|
);
|
||||||
|
grep_truncation(
|
||||||
|
value.client_truncated,
|
||||||
|
value.ripgrep_truncated,
|
||||||
|
value.total_matches,
|
||||||
|
"matches",
|
||||||
|
lines,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(R::Content(value)) => {
|
||||||
|
for file in &value.matches {
|
||||||
|
lines.extend(file.matches.iter().map(|matched| {
|
||||||
|
let separator = if matched.is_context_line { '-' } else { ':' };
|
||||||
|
let truncated = if matched.content_truncated {
|
||||||
|
" [line truncated]"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
format!(
|
||||||
|
"{}{separator}{}{separator}{}{truncated}",
|
||||||
|
file.file, matched.line_number, matched.content
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
grep_truncation(
|
||||||
|
value.client_truncated,
|
||||||
|
value.ripgrep_truncated,
|
||||||
|
value.total_matched_lines,
|
||||||
|
"matched lines",
|
||||||
|
lines,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grep_truncation(
|
||||||
|
client_truncated: bool,
|
||||||
|
ripgrep_truncated: bool,
|
||||||
|
total: i32,
|
||||||
|
unit: &str,
|
||||||
|
lines: &mut Vec<String>,
|
||||||
|
) {
|
||||||
|
if client_truncated || ripgrep_truncated {
|
||||||
|
lines.push(format!("[Results truncated; {total} total {unit}]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn diagnostics(value: &pb::DiagnosticsResult) -> Result<(String, bool)> {
|
fn diagnostics(value: &pb::DiagnosticsResult) -> Result<(String, bool)> {
|
||||||
use pb::diagnostics_result::Result as R;
|
use pb::diagnostics_result::Result as R;
|
||||||
match value
|
match value
|
||||||
@@ -137,13 +218,7 @@ fn diagnostics(value: &pb::DiagnosticsResult) -> Result<(String, bool)> {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| missing("diagnostics"))?
|
.ok_or_else(|| missing("diagnostics"))?
|
||||||
{
|
{
|
||||||
R::Success(value) => Ok((
|
R::Success(value) => Ok((diagnostics_success(value), false)),
|
||||||
format!(
|
|
||||||
"diagnostics path={} count={}",
|
|
||||||
value.path, value.total_diagnostics
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
R::Error(value) => Ok((value.error.clone(), true)),
|
R::Error(value) => Ok((value.error.clone(), true)),
|
||||||
R::Rejected(value) => Ok((value.reason.clone(), true)),
|
R::Rejected(value) => Ok((value.reason.clone(), true)),
|
||||||
R::FileNotFound(value) => Ok((format!("file not found: {}", value.path), true)),
|
R::FileNotFound(value) => Ok((format!("file not found: {}", value.path), true)),
|
||||||
@@ -151,6 +226,76 @@ fn diagnostics(value: &pb::DiagnosticsResult) -> Result<(String, bool)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diagnostics_success(value: &pb::DiagnosticsSuccess) -> String {
|
||||||
|
if value.diagnostics.is_empty() {
|
||||||
|
return format!("No diagnostics found in {}", value.path);
|
||||||
|
}
|
||||||
|
let mut lines = value
|
||||||
|
.diagnostics
|
||||||
|
.iter()
|
||||||
|
.map(|diagnostic| {
|
||||||
|
let location = diagnostic_location(&value.path, diagnostic.range.as_ref());
|
||||||
|
let mut labels = vec![diagnostic_severity(diagnostic.severity)];
|
||||||
|
if !diagnostic.source.is_empty() {
|
||||||
|
labels.push(diagnostic.source.as_str());
|
||||||
|
}
|
||||||
|
if !diagnostic.code.is_empty() {
|
||||||
|
labels.push(diagnostic.code.as_str());
|
||||||
|
}
|
||||||
|
if diagnostic.is_stale {
|
||||||
|
labels.push("stale");
|
||||||
|
}
|
||||||
|
format!(
|
||||||
|
"{}: [{}] {}",
|
||||||
|
location,
|
||||||
|
labels.join(" "),
|
||||||
|
diagnostic.message
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if value.total_diagnostics != value.diagnostics.len() as i32 {
|
||||||
|
lines.push(format!(
|
||||||
|
"[Reported {} diagnostics; received {} details]",
|
||||||
|
value.total_diagnostics,
|
||||||
|
value.diagnostics.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
lines.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagnostic_location(path: &str, range: Option<&pb::Range>) -> String {
|
||||||
|
let Some(range) = range else {
|
||||||
|
return path.into();
|
||||||
|
};
|
||||||
|
let Some(start) = &range.start else {
|
||||||
|
return path.into();
|
||||||
|
};
|
||||||
|
let mut location = format!(
|
||||||
|
"{}:{}:{}",
|
||||||
|
path,
|
||||||
|
start.line.saturating_add(1),
|
||||||
|
start.column.saturating_add(1)
|
||||||
|
);
|
||||||
|
if let Some(end) = &range.end {
|
||||||
|
location.push_str(&format!(
|
||||||
|
"-{}:{}",
|
||||||
|
end.line.saturating_add(1),
|
||||||
|
end.column.saturating_add(1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
location
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagnostic_severity(value: i32) -> &'static str {
|
||||||
|
match pb::DiagnosticSeverity::try_from(value) {
|
||||||
|
Ok(pb::DiagnosticSeverity::Error) => "error",
|
||||||
|
Ok(pb::DiagnosticSeverity::Warning) => "warning",
|
||||||
|
Ok(pb::DiagnosticSeverity::Information) => "information",
|
||||||
|
Ok(pb::DiagnosticSeverity::Hint) => "hint",
|
||||||
|
Ok(pb::DiagnosticSeverity::Unspecified) | Err(_) => "diagnostic",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn mcp(value: &pb::McpResult) -> Result<(String, bool)> {
|
fn mcp(value: &pb::McpResult) -> Result<(String, bool)> {
|
||||||
use pb::mcp_result::Result as R;
|
use pb::mcp_result::Result as R;
|
||||||
match value.result.as_ref().ok_or_else(|| missing("mcp"))? {
|
match value.result.as_ref().ok_or_else(|| missing("mcp"))? {
|
||||||
@@ -264,6 +409,106 @@ fn creates_subagent(call: &ToolCall) -> bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn grep_output_contains_file_and_match_details() {
|
||||||
|
let value = pb::GrepResult {
|
||||||
|
result: Some(pb::grep_result::Result::Success(pb::GrepSuccess {
|
||||||
|
pattern: "Cursor".into(),
|
||||||
|
path: "/workspace".into(),
|
||||||
|
output_mode: "content".into(),
|
||||||
|
workspace_results: HashMap::from([
|
||||||
|
(
|
||||||
|
"workspace-b".into(),
|
||||||
|
pb::GrepUnionResult {
|
||||||
|
result: Some(pb::grep_union_result::Result::Files(
|
||||||
|
pb::GrepFilesResult {
|
||||||
|
files: vec!["/workspace/Cargo.toml".into()],
|
||||||
|
total_files: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"workspace-a".into(),
|
||||||
|
pb::GrepUnionResult {
|
||||||
|
result: Some(pb::grep_union_result::Result::Content(
|
||||||
|
pb::GrepContentResult {
|
||||||
|
matches: vec![pb::GrepFileMatch {
|
||||||
|
file: "/workspace/README.md".into(),
|
||||||
|
matches: vec![pb::GrepContentMatch {
|
||||||
|
line_number: 7,
|
||||||
|
content: "Cursor BYOK".into(),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
total_lines: 1,
|
||||||
|
total_matched_lines: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
active_editor_result: None,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (content, is_error) = grep(&value).unwrap();
|
||||||
|
|
||||||
|
assert!(!is_error);
|
||||||
|
assert!(content.contains("/workspace/README.md:7:Cursor BYOK"));
|
||||||
|
assert!(content.contains("/workspace/Cargo.toml"));
|
||||||
|
assert!(
|
||||||
|
content.find("/workspace/README.md").unwrap()
|
||||||
|
< content.find("/workspace/Cargo.toml").unwrap(),
|
||||||
|
"workspace map output must be deterministic"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diagnostics_output_contains_each_diagnostic_detail() {
|
||||||
|
let value = pb::DiagnosticsResult {
|
||||||
|
result: Some(pb::diagnostics_result::Result::Success(
|
||||||
|
pb::DiagnosticsSuccess {
|
||||||
|
path: "/workspace/src/main.rs".into(),
|
||||||
|
diagnostics: vec![pb::Diagnostic {
|
||||||
|
severity: pb::DiagnosticSeverity::Error as i32,
|
||||||
|
range: Some(pb::Range {
|
||||||
|
start: Some(pb::Position { line: 4, column: 8 }),
|
||||||
|
end: Some(pb::Position {
|
||||||
|
line: 4,
|
||||||
|
column: 12,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
message: "cannot find value `name`".into(),
|
||||||
|
source: "rustc".into(),
|
||||||
|
code: "E0425".into(),
|
||||||
|
is_stale: false,
|
||||||
|
}],
|
||||||
|
total_diagnostics: 1,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (content, is_error) = diagnostics(&value).unwrap();
|
||||||
|
|
||||||
|
assert!(!is_error);
|
||||||
|
assert!(content.contains("/workspace/src/main.rs:5:9"));
|
||||||
|
assert!(content.contains("-5:13"));
|
||||||
|
assert!(content.contains("error"));
|
||||||
|
assert!(content.contains("rustc"));
|
||||||
|
assert!(content.contains("E0425"));
|
||||||
|
assert!(content.contains("cannot find value `name`"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn missing(name: &str) -> Error {
|
fn missing(name: &str) -> Error {
|
||||||
Error::Protocol(format!("{name} returned no result"))
|
Error::Protocol(format!("{name} returned no result"))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user