mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-06 13:44:21 +08:00
- Introduced a new documentation site for Cursor BYOK using Next.js and Fumadocs. - Added a product demo page with a corresponding Vite configuration. - Implemented a demo API to simulate LLM calls and responses. - Enhanced the Makefile to include new build and development commands for the documentation. - Updated package.json scripts for building and running the documentation site. - Created various components and layouts for the documentation structure, including blog and user documentation sections. - Added styling for the new components and layouts to ensure a cohesive design. - Included a README and other necessary files for local development and deployment.
355 lines
12 KiB
Rust
355 lines
12 KiB
Rust
use crate::{
|
|
cursor::{
|
|
interaction,
|
|
proto::agent::v1 as pb,
|
|
tools::{
|
|
edit,
|
|
result::{self, ToolCompletion},
|
|
runtime::{CursorToolRuntime, ExecStage, PendingExec},
|
|
},
|
|
},
|
|
model::ToolCall,
|
|
Error, Result,
|
|
};
|
|
|
|
use super::request::edit_write_request;
|
|
|
|
pub enum ClientExecEvent {
|
|
Delta(Box<pb::AgentServerMessage>),
|
|
Message(Box<pb::AgentServerMessage>),
|
|
Completed(Box<ToolCompletion>),
|
|
Pending,
|
|
}
|
|
|
|
pub async fn client_event(
|
|
message: &pb::ExecClientMessage,
|
|
pending: &CursorToolRuntime,
|
|
) -> Result<ClientExecEvent> {
|
|
if pending.is_interrupted(message.id).await {
|
|
if message.message.as_ref().is_some_and(is_terminal) {
|
|
pending.discard_exec(message.id).await;
|
|
}
|
|
return Ok(ClientExecEvent::Pending);
|
|
}
|
|
let call = match pending.exec_call(message.id).await {
|
|
Some(call) => call,
|
|
None if pending.completed_call(message.id).await.is_some() => {
|
|
return Err(Error::Protocol(format!(
|
|
"duplicate terminal ExecClientMessage id: {}",
|
|
message.id
|
|
)))
|
|
}
|
|
None => {
|
|
return Err(Error::Protocol(format!(
|
|
"unknown ExecClientMessage id: {}",
|
|
message.id
|
|
)))
|
|
}
|
|
};
|
|
let Some(wire_result) = &message.message else {
|
|
return Ok(ClientExecEvent::Pending);
|
|
};
|
|
let pb::exec_client_message::Message::ShellStream(stream) = wire_result else {
|
|
let entry = take(message.id, pending).await?;
|
|
return match entry.stage {
|
|
ExecStage::EditRead => advance_edit(entry, wire_result, pending).await,
|
|
ExecStage::Direct | ExecStage::DynamicMcp(_) | ExecStage::EditWrite(_) => {
|
|
completed(entry, wire_result.clone())
|
|
}
|
|
};
|
|
};
|
|
use pb::shell_stream::Event;
|
|
let event = match &stream.event {
|
|
Some(Event::Stdout(stdout)) => {
|
|
if pending.append_stdout(message.id, &stdout.data).await {
|
|
ClientExecEvent::Delta(Box::new(shell_delta(&call, true, &stdout.data)))
|
|
} else {
|
|
ClientExecEvent::Pending
|
|
}
|
|
}
|
|
Some(Event::Stderr(stderr)) => {
|
|
if pending.append_stderr(message.id, &stderr.data).await {
|
|
ClientExecEvent::Delta(Box::new(shell_delta(&call, false, &stderr.data)))
|
|
} else {
|
|
ClientExecEvent::Pending
|
|
}
|
|
}
|
|
Some(Event::Start(_)) | Some(Event::HookContext(_)) => ClientExecEvent::Pending,
|
|
Some(Event::Exit(exit)) => {
|
|
let entry = take(message.id, pending).await?;
|
|
let result = shell_exit_result(message, exit, &entry.stdout, &entry.stderr);
|
|
completed(entry, pb::exec_client_message::Message::ShellResult(result))?
|
|
}
|
|
Some(Event::Backgrounded(backgrounded)) => {
|
|
let entry = take(message.id, pending).await?;
|
|
let result = shell_backgrounded_result(
|
|
backgrounded,
|
|
&entry.stdout,
|
|
&entry.stderr,
|
|
&entry.context.terminals_folder,
|
|
);
|
|
completed(entry, pb::exec_client_message::Message::ShellResult(result))?
|
|
}
|
|
Some(Event::Rejected(value)) => {
|
|
let result = pb::ShellResult {
|
|
result: Some(pb::shell_result::Result::Rejected(value.clone())),
|
|
..Default::default()
|
|
};
|
|
complete(
|
|
message.id,
|
|
pending,
|
|
pb::exec_client_message::Message::ShellResult(result),
|
|
)
|
|
.await?
|
|
}
|
|
Some(Event::PermissionDenied(value)) => {
|
|
let result = pb::ShellResult {
|
|
result: Some(pb::shell_result::Result::PermissionDenied(value.clone())),
|
|
..Default::default()
|
|
};
|
|
complete(
|
|
message.id,
|
|
pending,
|
|
pb::exec_client_message::Message::ShellResult(result),
|
|
)
|
|
.await?
|
|
}
|
|
Some(Event::SandboxUnsupported(value)) => {
|
|
let result = pb::ShellResult {
|
|
result: Some(pb::shell_result::Result::SpawnError(pb::ShellSpawnError {
|
|
command: value.command.clone(),
|
|
working_directory: value.working_directory.clone(),
|
|
error: value.reason.clone(),
|
|
})),
|
|
..Default::default()
|
|
};
|
|
complete(
|
|
message.id,
|
|
pending,
|
|
pb::exec_client_message::Message::ShellResult(result),
|
|
)
|
|
.await?
|
|
}
|
|
None => ClientExecEvent::Pending,
|
|
};
|
|
Ok(event)
|
|
}
|
|
|
|
pub async fn stream_closed(id: u32, pending: &CursorToolRuntime) -> Result<Option<ToolCompletion>> {
|
|
if pending.is_interrupted(id).await {
|
|
pending.discard_exec(id).await;
|
|
return Ok(None);
|
|
}
|
|
let Some(entry) = pending.take_exec(id).await else {
|
|
return Ok(None);
|
|
};
|
|
let error = "Cursor Exec stream closed before returning a terminal result";
|
|
if entry.call.name.eq_ignore_ascii_case("Shell") {
|
|
let command = entry
|
|
.call
|
|
.arguments
|
|
.get("command")
|
|
.and_then(serde_json::Value::as_str)
|
|
.unwrap_or_default()
|
|
.to_string();
|
|
let working_directory = entry
|
|
.call
|
|
.arguments
|
|
.get("working_directory")
|
|
.and_then(serde_json::Value::as_str)
|
|
.unwrap_or_default()
|
|
.to_string();
|
|
return Ok(Some(result::from_exec(
|
|
entry,
|
|
&pb::exec_client_message::Message::ShellResult(pb::ShellResult {
|
|
result: Some(pb::shell_result::Result::SpawnError(pb::ShellSpawnError {
|
|
command,
|
|
working_directory,
|
|
error: error.into(),
|
|
})),
|
|
..Default::default()
|
|
}),
|
|
)?));
|
|
}
|
|
let rendered = match &entry.stage {
|
|
ExecStage::DynamicMcp(definition) => {
|
|
interaction::render_dynamic_mcp(&entry.call, definition, false)
|
|
}
|
|
_ => interaction::render_tool_call(&entry.call, false)?,
|
|
};
|
|
Ok(Some(ToolCompletion::from_rendered(
|
|
&entry.call,
|
|
entry.started_at_ms,
|
|
error.into(),
|
|
true,
|
|
rendered,
|
|
)?))
|
|
}
|
|
|
|
fn is_terminal(message: &pb::exec_client_message::Message) -> bool {
|
|
use pb::{exec_client_message::Message, shell_stream::Event};
|
|
|
|
match message {
|
|
Message::ShellStream(stream) => matches!(
|
|
stream.event.as_ref(),
|
|
Some(Event::Exit(_))
|
|
| Some(Event::Backgrounded(_))
|
|
| Some(Event::Rejected(_))
|
|
| Some(Event::PermissionDenied(_))
|
|
| Some(Event::SandboxUnsupported(_))
|
|
),
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
async fn advance_edit(
|
|
entry: PendingExec,
|
|
result: &pb::exec_client_message::Message,
|
|
registry: &CursorToolRuntime,
|
|
) -> Result<ClientExecEvent> {
|
|
let read = match result {
|
|
pb::exec_client_message::Message::ReadResult(result)
|
|
| pb::exec_client_message::Message::RedactedReadResult(result) => result,
|
|
_ => {
|
|
return Err(Error::Protocol(format!(
|
|
"expected ReadResult for edit tool {}",
|
|
entry.call.name
|
|
)))
|
|
}
|
|
};
|
|
let write = match edit::after_read(&entry.call, read) {
|
|
Ok(write) => write,
|
|
Err(error) => {
|
|
return Ok(ClientExecEvent::Completed(Box::new(result::edit_failure(
|
|
entry, error,
|
|
)?)))
|
|
}
|
|
};
|
|
let id = registry
|
|
.reserve_edit_write(
|
|
&entry.call,
|
|
&entry.context,
|
|
write.clone(),
|
|
entry.started_at_ms,
|
|
)
|
|
.await?;
|
|
Ok(ClientExecEvent::Message(Box::new(edit_write_request(
|
|
id,
|
|
&entry.call,
|
|
&write,
|
|
)?)))
|
|
}
|
|
|
|
async fn complete(
|
|
id: u32,
|
|
pending: &CursorToolRuntime,
|
|
result: pb::exec_client_message::Message,
|
|
) -> Result<ClientExecEvent> {
|
|
completed(take(id, pending).await?, result)
|
|
}
|
|
|
|
async fn take(id: u32, pending: &CursorToolRuntime) -> Result<PendingExec> {
|
|
pending
|
|
.take_exec(id)
|
|
.await
|
|
.ok_or_else(|| Error::Protocol(format!("unknown terminal Exec id: {id}")))
|
|
}
|
|
|
|
fn completed(
|
|
pending: PendingExec,
|
|
result: pb::exec_client_message::Message,
|
|
) -> Result<ClientExecEvent> {
|
|
Ok(ClientExecEvent::Completed(Box::new(result::from_exec(
|
|
pending, &result,
|
|
)?)))
|
|
}
|
|
|
|
fn shell_exit_result(
|
|
message: &pb::ExecClientMessage,
|
|
exit: &pb::ShellStreamExit,
|
|
stdout: &str,
|
|
stderr: &str,
|
|
) -> pb::ShellResult {
|
|
let result = if exit.code == 0 && !exit.aborted {
|
|
pb::shell_result::Result::Success(pb::ShellSuccess {
|
|
working_directory: exit.cwd.clone(),
|
|
exit_code: exit.code as i32,
|
|
stdout: stdout.into(),
|
|
stderr: stderr.into(),
|
|
interleaved_output: Some(format!("{stdout}{stderr}")),
|
|
local_execution_time_ms: exit
|
|
.local_execution_time_ms
|
|
.or(message.local_execution_time_ms),
|
|
..Default::default()
|
|
})
|
|
} else {
|
|
pb::shell_result::Result::Failure(pb::ShellFailure {
|
|
working_directory: exit.cwd.clone(),
|
|
exit_code: exit.code as i32,
|
|
stdout: stdout.into(),
|
|
stderr: stderr.into(),
|
|
interleaved_output: Some(format!("{stdout}{stderr}")),
|
|
abort_reason: exit.abort_reason,
|
|
aborted: exit.aborted,
|
|
local_execution_time_ms: exit
|
|
.local_execution_time_ms
|
|
.or(message.local_execution_time_ms),
|
|
..Default::default()
|
|
})
|
|
};
|
|
pb::ShellResult {
|
|
result: Some(result),
|
|
is_background: Some(false),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
fn shell_backgrounded_result(
|
|
backgrounded: &pb::ShellStreamBackgrounded,
|
|
stdout: &str,
|
|
stderr: &str,
|
|
terminals_folder: &str,
|
|
) -> pb::ShellResult {
|
|
pb::ShellResult {
|
|
result: Some(pb::shell_result::Result::Success(pb::ShellSuccess {
|
|
command: backgrounded.command.clone(),
|
|
working_directory: backgrounded.working_directory.clone(),
|
|
stdout: stdout.into(),
|
|
stderr: stderr.into(),
|
|
shell_id: Some(backgrounded.shell_id),
|
|
pid: backgrounded.pid,
|
|
ms_to_wait: backgrounded.ms_to_wait,
|
|
background_reason: backgrounded.reason,
|
|
interleaved_output: Some(format!("{stdout}{stderr}")),
|
|
..Default::default()
|
|
})),
|
|
is_background: Some(true),
|
|
terminals_folder: (!terminals_folder.is_empty()).then(|| terminals_folder.into()),
|
|
pid: backgrounded.pid,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
fn shell_delta(call: &ToolCall, stdout: bool, content: &str) -> pb::AgentServerMessage {
|
|
let delta = if stdout {
|
|
pb::shell_tool_call_delta::Delta::Stdout(pb::ShellToolCallStdoutDelta {
|
|
content: content.into(),
|
|
})
|
|
} else {
|
|
pb::shell_tool_call_delta::Delta::Stderr(pb::ShellToolCallStderrDelta {
|
|
content: content.into(),
|
|
})
|
|
};
|
|
interaction::server_interaction(pb::interaction_update::Message::ToolCallDelta(Box::new(
|
|
pb::ToolCallDeltaUpdate {
|
|
call_id: call.call_id.clone(),
|
|
tool_call_delta: Some(Box::new(pb::ToolCallDelta {
|
|
delta: Some(pb::tool_call_delta::Delta::ShellToolCallDelta(
|
|
pb::ShellToolCallDelta { delta: Some(delta) },
|
|
)),
|
|
})),
|
|
model_call_id: call.model_call_id.clone(),
|
|
},
|
|
)))
|
|
}
|