use agent_client_protocol::schema::v1::{SessionUpdate, ToolCallContent};
use serde::Serialize;
use serde_json::Value;
use crate::permission::PermissionMode;
#[derive(Serialize)]
struct ExtensionReply {
outcome: ExtensionOutcome,
}
#[derive(Serialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
enum ExtensionOutcome {
Accepted,
Skipped { reason: &'static str },
Rejected { reason: &'static str },
}
pub(crate) fn answer(mode: PermissionMode, method: &str, _params: &Value) -> Option<Value> {
let outcome = match method {
"cursor/ask_question" => ExtensionOutcome::Skipped {
reason: "no interactive user; pick the best option and continue",
},
"cursor/create_plan" => match mode {
PermissionMode::Bypass => ExtensionOutcome::Accepted,
PermissionMode::Plan
| PermissionMode::Auto
| PermissionMode::AllowEdits
| PermissionMode::Supervised => ExtensionOutcome::Rejected {
reason: "plan-only run: report the plan as your final answer",
},
},
_ => return None,
};
Some(
serde_json::to_value(ExtensionReply { outcome })
.expect("static Cursor extension reply serializes"),
)
}
pub(crate) fn normalize_update(mut update: SessionUpdate) -> SessionUpdate {
let content = match &mut update {
SessionUpdate::ToolCall(call) => Some(&mut call.content),
SessionUpdate::ToolCallUpdate(call) => call.fields.content.as_mut(),
_ => None,
};
if let Some(content) = content {
for item in content {
match item {
ToolCallContent::Diff(diff) if diff.old_text.as_deref() == Some("-- /dev/null") => {
diff.old_text = None;
if diff.new_text.starts_with("++ b/") {
diff.new_text = diff
.new_text
.split_once('\n')
.map_or("", |(_, text)| text)
.to_owned();
}
}
_ => {}
}
}
}
update
}
#[cfg(test)]
#[path = "acp_extensions_tests.rs"]
mod tests;