feat: first cut at dev brain
This commit is contained in:
88
src/agent.rs
88
src/agent.rs
@@ -302,6 +302,7 @@ pub(crate) struct Tools {
|
||||
jobs: HashMap<u32, BashJob>,
|
||||
next_job: u32,
|
||||
browser: Browser,
|
||||
dev_brain: Option<crate::dev_brain::DevBrain>,
|
||||
}
|
||||
|
||||
impl Tools {
|
||||
@@ -316,9 +317,19 @@ impl Tools {
|
||||
jobs: HashMap::new(),
|
||||
next_job: 1,
|
||||
browser: Browser::new()?,
|
||||
dev_brain: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn enable_dev_brain(
|
||||
&mut self,
|
||||
config: &crate::config::DevBrainConfig,
|
||||
projects: &[crate::database::Project],
|
||||
) -> Result<(), String> {
|
||||
self.dev_brain = Some(crate::dev_brain::DevBrain::open(config, projects)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute(&mut self, call: &ToolCall, cancel: &AtomicBool) -> String {
|
||||
let result = match call.name.as_str() {
|
||||
"read" => self.read(call),
|
||||
@@ -332,6 +343,9 @@ impl Tools {
|
||||
"bash_stop" => self.bash_observe(call, true, cancel),
|
||||
"google_search" => self.google_search(call, cancel),
|
||||
"visit_page" => self.visit_page(call, cancel),
|
||||
"dev_brain_search" => self.dev_brain_search(call),
|
||||
"dev_brain_read" => self.dev_brain_read(call),
|
||||
"dev_brain_publish" => self.dev_brain_publish(call),
|
||||
name => Err(format!("unknown tool: {name}")),
|
||||
};
|
||||
match result {
|
||||
@@ -349,6 +363,54 @@ impl Tools {
|
||||
}
|
||||
}
|
||||
|
||||
fn dev_brain_search(&mut self, call: &ToolCall) -> Result<String, String> {
|
||||
let query = required_string(call, "query")?;
|
||||
let limit = integer(call, "limit", 8, 1, 50);
|
||||
let authoritative = boolean(call, "authoritative", true);
|
||||
self.dev_brain
|
||||
.as_mut()
|
||||
.ok_or_else(|| "Dev Brain is disabled for this session.".to_owned())?
|
||||
.search(query, limit, authoritative)
|
||||
}
|
||||
|
||||
fn dev_brain_read(&mut self, call: &ToolCall) -> Result<String, String> {
|
||||
let path = required_string(call, "path")?;
|
||||
let authoritative = boolean(call, "authoritative", false);
|
||||
self.dev_brain
|
||||
.as_mut()
|
||||
.ok_or_else(|| "Dev Brain is disabled for this session.".to_owned())?
|
||||
.read(path, authoritative)
|
||||
}
|
||||
|
||||
fn dev_brain_publish(&mut self, call: &ToolCall) -> Result<String, String> {
|
||||
let files = call
|
||||
.arguments
|
||||
.get("files")
|
||||
.and_then(Value::as_object)
|
||||
.ok_or_else(|| "dev_brain_publish requires a files object.".to_owned())?;
|
||||
let remove = call
|
||||
.arguments
|
||||
.get("remove")
|
||||
.map(|value| {
|
||||
value
|
||||
.as_array()
|
||||
.ok_or_else(|| "remove must be an array of paths.".to_owned())?
|
||||
.iter()
|
||||
.map(|path| {
|
||||
path.as_str()
|
||||
.map(str::to_owned)
|
||||
.ok_or_else(|| "remove paths must be strings.".to_owned())
|
||||
})
|
||||
.collect::<Result<Vec<_>, String>>()
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
self.dev_brain
|
||||
.as_mut()
|
||||
.ok_or_else(|| "Dev Brain is disabled for this session.".to_owned())?
|
||||
.publish(files, &remove)
|
||||
}
|
||||
|
||||
fn result_limit(&self) -> usize {
|
||||
(self.context_tokens.max(4096) as usize * 2).min(512 * 1024)
|
||||
}
|
||||
@@ -1229,16 +1291,26 @@ pub(crate) fn parse_tool_calls(
|
||||
.map(|calls| (content, calls))
|
||||
}
|
||||
|
||||
pub(crate) fn system_prompt(model: ModelChoice, extra: &str) -> String {
|
||||
pub(crate) fn system_prompt(model: ModelChoice, extra: &str, dev_brain: bool) -> String {
|
||||
let schemas = if dev_brain {
|
||||
format!("{TOOL_SCHEMAS}\n{}", crate::dev_brain::TOOL_SCHEMAS)
|
||||
} else {
|
||||
TOOL_SCHEMAS.to_owned()
|
||||
};
|
||||
let tools = if model == ModelChoice::Glm52 {
|
||||
format!(
|
||||
"You are a coding agent running in a local workspace. Use tools for local file and system work. Avoid printing large file contents or large code blocks as answers; create or edit files with tools, then summarize results briefly.\n\n# Tools\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{TOOL_SCHEMAS}\n</tools>\n\nFor a function call, output exactly: <tool_call>function-name<arg_key>key</arg_key><arg_value>value</arg_value></tool_call>\nTool calls are not allowed inside <think></think>. Use read/search for focused context, edit with exact unique old text, and [upto] only between unique head and tail anchors. Use refresh_sec for long bash jobs and poll with bash_status or stop with bash_stop. Preserve the current system configuration unless the user explicitly asks otherwise."
|
||||
"You are a coding agent running in a local workspace. Use tools for local file and system work. Avoid printing large file contents or large code blocks as answers; create or edit files with tools, then summarize results briefly.\n\n# Tools\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n{schemas}\n</tools>\n\nFor a function call, output exactly: <tool_call>function-name<arg_key>key</arg_key><arg_value>value</arg_value></tool_call>\nTool calls are not allowed inside <think></think>. Use read/search for focused context, edit with exact unique old text, and [upto] only between unique head and tail anchors. Use refresh_sec for long bash jobs and poll with bash_status or stop with bash_stop. Preserve the current system configuration unless the user explicitly asks otherwise."
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"You are a coding agent running in a local workspace. Use tools for local file and system work. Avoid printing large file contents or large code blocks as answers; create or edit files with tools, then summarize results briefly.\n\n## Tools\n\nInvoke native DSML tools exactly as:\n<|DSML|tool_calls>\n<|DSML|invoke name=\"$TOOL_NAME\">\n<|DSML|parameter name=\"$PARAMETER_NAME\" string=\"true|false\">$PARAMETER_VALUE</|DSML|parameter>\n</|DSML|invoke>\n</|DSML|tool_calls>\n\nTool calls are not allowed inside <think></think>. String parameters use raw text and string=\"true\"; numbers and booleans use JSON text and string=\"false\". Read defaults to a bounded chunk; use more to continue and whole=true only when needed. Use write for new files or whole-file replacement. Use edit with path first and exact unique old text; old may contain one [upto] marker between unique head and tail anchors. For long bash commands pass refresh_sec, then use bash_status or bash_stop. The first web call asks permission to start visible Chrome.\n\n### Available Tool Schemas\n\n{TOOL_SCHEMAS}\n\n# Rules\n- Always use strict DSML syntax.\n- Use read/search to get anchors before editing.\n- Preserve the current system configuration unless explicitly asked otherwise."
|
||||
"You are a coding agent running in a local workspace. Use tools for local file and system work. Avoid printing large file contents or large code blocks as answers; create or edit files with tools, then summarize results briefly.\n\n## Tools\n\nInvoke native DSML tools exactly as:\n<|DSML|tool_calls>\n<|DSML|invoke name=\"$TOOL_NAME\">\n<|DSML|parameter name=\"$PARAMETER_NAME\" string=\"true|false\">$PARAMETER_VALUE</|DSML|parameter>\n</|DSML|invoke>\n</|DSML|tool_calls>\n\nTool calls are not allowed inside <think></think>. String parameters use raw text and string=\"true\"; numbers and booleans use JSON text and string=\"false\". Read defaults to a bounded chunk; use more to continue and whole=true only when needed. Use write for new files or whole-file replacement. Use edit with path first and exact unique old text; old may contain one [upto] marker between unique head and tail anchors. For long bash commands pass refresh_sec, then use bash_status or bash_stop. The first web call asks permission to start visible Chrome.\n\n### Available Tool Schemas\n\n{schemas}\n\n# Rules\n- Always use strict DSML syntax.\n- Use read/search to get anchors before editing.\n- Preserve the current system configuration unless explicitly asked otherwise."
|
||||
)
|
||||
};
|
||||
let tools = if dev_brain {
|
||||
format!("{tools}\n\n{}", crate::dev_brain::PROMPT)
|
||||
} else {
|
||||
tools
|
||||
};
|
||||
if extra.trim().is_empty() {
|
||||
tools
|
||||
} else {
|
||||
@@ -1246,10 +1318,10 @@ pub(crate) fn system_prompt(model: ModelChoice, extra: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn system_prompt_reminder(model: ModelChoice) -> String {
|
||||
pub(crate) fn system_prompt_reminder(model: ModelChoice, dev_brain: bool) -> String {
|
||||
format!(
|
||||
"[System prompt reminder follows.]\n{}\n[End system prompt reminder.]",
|
||||
system_prompt(model, "")
|
||||
system_prompt(model, "", dev_brain)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1654,7 +1726,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn prompts_and_parsers_expose_the_reference_tool_set() {
|
||||
let prompt = system_prompt(ModelChoice::DeepSeekV4Flash, "extra");
|
||||
let prompt = system_prompt(ModelChoice::DeepSeekV4Flash, "extra", false);
|
||||
for name in [
|
||||
"google_search",
|
||||
"visit_page",
|
||||
@@ -1672,9 +1744,11 @@ mod tests {
|
||||
}
|
||||
assert!(prompt.ends_with("extra"));
|
||||
assert!(
|
||||
system_prompt_reminder(ModelChoice::DeepSeekV4Flash)
|
||||
system_prompt_reminder(ModelChoice::DeepSeekV4Flash, false)
|
||||
.contains("[System prompt reminder follows.]")
|
||||
);
|
||||
assert!(!prompt.contains("dev_brain_search"));
|
||||
assert!(system_prompt(ModelChoice::DeepSeekV4Flash, "", true).contains("dev_brain_search"));
|
||||
assert!(datetime_context().starts_with("Current local date and time at session start:"));
|
||||
assert!(!prompt_reminder_due(49_999, 0));
|
||||
assert!(prompt_reminder_due(50_000, 0));
|
||||
|
||||
Reference in New Issue
Block a user