Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion server/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ impl App {
.set_port(store.port_settings().await?.service_port);
}
let assets = PromptAssets::embedded()?;
let compiler = PromptCompiler::new(assets);
let compiler = PromptCompiler::with_compaction_prompt_path(
assets,
crate::config::compaction_prompt_path()?,
);
let provider = std::sync::Arc::new(ProviderRouter::new(
store.clone(),
config.provider_request_timeout,
Expand Down
36 changes: 36 additions & 0 deletions server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const DATA_DIR_NAME: &str = ".cursor-byok-v3";
const DATABASE_FILE_NAME: &str = "cursor-byok.db";
const V0049_DATA_DIR_NAME: &str = ".cursor-local-assistant-v2";
const V0049_CONFIG_FILE_NAME: &str = "config.yaml";
const COMPACTION_PROMPT_PATH: &str = "prompts/compaction.md";

pub fn managed_data_dir() -> Result<PathBuf> {
let home_dir = dirs::home_dir()
Expand All @@ -28,6 +29,26 @@ pub fn v0049_config_path() -> Result<PathBuf> {
.join(V0049_CONFIG_FILE_NAME))
}

pub fn compaction_prompt_path() -> Result<PathBuf> {
Ok(managed_data_dir()?.join(COMPACTION_PROMPT_PATH))
}

pub fn compaction_prompt_override() -> Result<Option<String>> {
compaction_prompt_override_at(&compaction_prompt_path()?)
}

pub(crate) fn compaction_prompt_override_at(path: &std::path::Path) -> Result<Option<String>> {
match fs::read_to_string(path) {
Ok(prompt) if prompt.trim().is_empty() => Ok(None),
Ok(prompt) => Ok(Some(prompt)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(Error::Config(format!(
"cannot read compaction prompt at {}: {error}",
path.display()
))),
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProviderKind {
OpenAiChat,
Expand Down Expand Up @@ -156,6 +177,21 @@ fn database_url_for_dir(data_dir: &std::path::Path) -> Result<String> {
mod tests {
use super::*;

#[test]
fn compaction_prompt_override_is_optional_and_reloaded() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("compaction.md");

assert_eq!(compaction_prompt_override_at(&path).unwrap(), None);
fs::write(&path, " \n").unwrap();
assert_eq!(compaction_prompt_override_at(&path).unwrap(), None);
fs::write(&path, "custom prompt").unwrap();
assert_eq!(
compaction_prompt_override_at(&path).unwrap().as_deref(),
Some("custom prompt")
);
}

#[tokio::test]
async fn managed_database_supports_home_paths_with_spaces() {
let directory = tempfile::tempdir().unwrap();
Expand Down
26 changes: 19 additions & 7 deletions server/src/cursor/prompting/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, path::PathBuf};

use crate::{
model::{ModelSpec, PromptSpec, ToolDefinition},
Expand All @@ -10,11 +10,22 @@ use super::{assets::runtime_expression, Mode, PromptAssets};
#[derive(Clone)]
pub struct PromptCompiler {
assets: PromptAssets,
compaction_prompt_path: Option<PathBuf>,
}

impl PromptCompiler {
pub fn new(assets: PromptAssets) -> Self {
Self { assets }
Self {
assets,
compaction_prompt_path: None,
}
}

pub fn with_compaction_prompt_path(assets: PromptAssets, path: PathBuf) -> Self {
Self {
assets,
compaction_prompt_path: Some(path),
}
}

pub fn runtime_message(&self, mode: Mode, values: &BTreeMap<&str, String>) -> Result<String> {
Expand All @@ -39,12 +50,13 @@ impl PromptCompiler {
.display_name
.as_deref()
.unwrap_or(model.model_id.as_str());
let prompt = match (mode, &self.compaction_prompt_path) {
(Mode::Compaction, Some(path)) => crate::config::compaction_prompt_override_at(path)?
.unwrap_or_else(|| self.assets.mode(mode).prompt.clone()),
_ => self.assets.mode(mode).prompt.clone(),
};
Ok(PromptSpec {
instructions: self
.assets
.mode(mode)
.prompt
.replace("{{FAKE_MODEL_NAME}}", fake_model_name),
instructions: prompt.replace("{{FAKE_MODEL_NAME}}", fake_model_name),
tools,
})
}
Expand Down
5 changes: 4 additions & 1 deletion server/src/run/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,14 +541,17 @@ impl RunEngine {
model.max_output_tokens = Some(COMPACTION_OUTPUT_TOKENS);
model.reasoning.enabled = false;
model.reasoning.effort = None;
let instructions = crate::config::compaction_prompt_override()
.map_err(|error| RunOutcome::Failed(error.into()))?
.unwrap_or_else(|| COMPACTION_INSTRUCTIONS.into());
let invocation = crate::model::ModelInvocation {
call_id: format!("{}:{provider_call_index}", prepared.run_id),
run_id: prepared.run_id.to_string(),
conversation_id: prepared.conversation_id.to_string(),
provider_call_index,
request: crate::model::ModelRequest {
prompt: crate::model::PromptSpec {
instructions: COMPACTION_INSTRUCTIONS.into(),
instructions,
tools: Vec::new(),
},
model,
Expand Down