feat: first cut at dev brain
This commit is contained in:
@@ -94,7 +94,7 @@ pub(crate) fn run(args: impl Iterator<Item = String>) -> Result<(), String> {
|
||||
.timeout_recv_body(Some(Duration::from_secs(30)))
|
||||
.build()
|
||||
.into();
|
||||
let mut system = crate::agent::system_prompt(model, &config.generation.system_prompt);
|
||||
let mut system = crate::agent::system_prompt(model, &config.generation.system_prompt, false);
|
||||
system.push_str("\n\n");
|
||||
system.push_str(crate::a2ui::SYSTEM_PROMPT);
|
||||
let metadata = Store::default().client_metadata();
|
||||
|
||||
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));
|
||||
|
||||
40
src/app.rs
40
src/app.rs
@@ -14,7 +14,7 @@ use preferences::PreferenceDraft;
|
||||
#[cfg(test)]
|
||||
use preferences::{parse_optional_gib, parse_streaming_cache};
|
||||
|
||||
use crate::config::{Config, EndpointConfig};
|
||||
use crate::config::{Config, DevBrainConfig, EndpointConfig};
|
||||
use crate::database::{Database, ProjectWithSessions, SessionState, StoredMessage};
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::engine::ChatTurn;
|
||||
@@ -247,6 +247,7 @@ pub(super) enum DetailTab {
|
||||
pub(super) enum PreferenceSection {
|
||||
Model,
|
||||
Endpoint,
|
||||
DevBrain,
|
||||
Generation,
|
||||
Execution,
|
||||
Acceleration,
|
||||
@@ -255,9 +256,10 @@ pub(super) enum PreferenceSection {
|
||||
}
|
||||
|
||||
impl PreferenceSection {
|
||||
const ALL: [Self; 7] = [
|
||||
const ALL: [Self; 8] = [
|
||||
Self::Model,
|
||||
Self::Endpoint,
|
||||
Self::DevBrain,
|
||||
Self::Generation,
|
||||
Self::Execution,
|
||||
Self::Acceleration,
|
||||
@@ -269,6 +271,7 @@ impl PreferenceSection {
|
||||
match self {
|
||||
Self::Model => "preferences-model",
|
||||
Self::Endpoint => "preferences-endpoint",
|
||||
Self::DevBrain => "preferences-dev-brain",
|
||||
Self::Generation => "preferences-generation",
|
||||
Self::Execution => "preferences-execution",
|
||||
Self::Acceleration => "preferences-acceleration",
|
||||
@@ -281,6 +284,7 @@ impl PreferenceSection {
|
||||
match self {
|
||||
Self::Model => "Model & lifecycle",
|
||||
Self::Endpoint => "Local endpoint",
|
||||
Self::DevBrain => "Dev Brain",
|
||||
Self::Generation => "Generation",
|
||||
Self::Execution => "Execution",
|
||||
Self::Acceleration => "Acceleration & memory",
|
||||
@@ -338,6 +342,10 @@ pub(crate) enum Message {
|
||||
PreferenceEndpointPortChanged(String),
|
||||
PreferenceEndpointEnabledChanged(bool),
|
||||
PreferenceEndpointCorsChanged(bool),
|
||||
PreferenceDevBrainEnabledChanged(bool),
|
||||
PreferenceDevBrainVaultChanged(String),
|
||||
ChooseDevBrainVault,
|
||||
DevBrainVaultPicked(Option<PathBuf>),
|
||||
PreferenceContextChanged(String),
|
||||
PreferenceMaxTokensChanged(String),
|
||||
PreferenceSystemPromptAction(text_editor::Action),
|
||||
@@ -1157,6 +1165,33 @@ impl App {
|
||||
self.preference_draft.endpoint_cors = enabled;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceDevBrainEnabledChanged(enabled) => {
|
||||
self.preference_draft.dev_brain_enabled = enabled;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceDevBrainVaultChanged(value) => {
|
||||
self.preference_draft.dev_brain_vault_path = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::ChooseDevBrainVault => {
|
||||
return Task::perform(
|
||||
async {
|
||||
AsyncFileDialog::new()
|
||||
.set_title("Choose an Obsidian vault")
|
||||
.pick_folder()
|
||||
.await
|
||||
.map(|folder| folder.path().to_path_buf())
|
||||
},
|
||||
Message::DevBrainVaultPicked,
|
||||
);
|
||||
}
|
||||
Message::DevBrainVaultPicked(path) => {
|
||||
if let Some(path) = path {
|
||||
self.preference_draft.dev_brain_vault_path =
|
||||
path.to_string_lossy().into_owned();
|
||||
self.preference_error = None;
|
||||
}
|
||||
}
|
||||
Message::PreferenceContextChanged(value) => {
|
||||
self.preference_draft.context_tokens = value;
|
||||
self.preference_error = None;
|
||||
@@ -1702,6 +1737,7 @@ impl App {
|
||||
self.tokens_per_second = None;
|
||||
}
|
||||
self.reload_projects();
|
||||
self.invalidate_dev_brain_context();
|
||||
self.finish_cache_change();
|
||||
}
|
||||
Err(error) => self.error = Some(error),
|
||||
|
||||
@@ -382,7 +382,7 @@ fn title_context(messages: impl IntoIterator<Item = ChatTurn>) -> Vec<ChatTurn>
|
||||
|
||||
impl App {
|
||||
fn chat_system_prompt(&self, model: ModelChoice, prompt: &str, agents: Option<&str>) -> String {
|
||||
let mut prompt = crate::agent::system_prompt(model, prompt);
|
||||
let mut prompt = crate::agent::system_prompt(model, prompt, self.config.dev_brain.enabled);
|
||||
if self.config.a2ui_enabled {
|
||||
prompt.push_str("\n\n");
|
||||
prompt.push_str(crate::a2ui::SYSTEM_PROMPT);
|
||||
@@ -668,7 +668,10 @@ impl App {
|
||||
}
|
||||
|
||||
fn system_prompt_reminders(&self, model: ModelChoice) -> Vec<String> {
|
||||
let mut reminders = vec![crate::agent::system_prompt_reminder(model)];
|
||||
let mut reminders = vec![crate::agent::system_prompt_reminder(
|
||||
model,
|
||||
self.config.dev_brain.enabled,
|
||||
)];
|
||||
if self.config.a2ui_enabled {
|
||||
reminders.push(crate::a2ui::SYSTEM_PROMPT.to_owned());
|
||||
}
|
||||
@@ -1163,7 +1166,15 @@ impl App {
|
||||
.find(|project| project.project.id == project_id)
|
||||
.map(|project| PathBuf::from(&project.project.path))
|
||||
.ok_or_else(|| "The active project is unavailable.".to_owned())?;
|
||||
let tools = crate::agent::Tools::new(&root, self.config.generation.context_tokens)?;
|
||||
let mut tools = crate::agent::Tools::new(&root, self.config.generation.context_tokens)?;
|
||||
if self.config.dev_brain.enabled {
|
||||
let projects = self
|
||||
.projects
|
||||
.iter()
|
||||
.map(|project| project.project.clone())
|
||||
.collect::<Vec<_>>();
|
||||
tools.enable_dev_brain(&self.config.dev_brain, &projects)?;
|
||||
}
|
||||
self.agent_tools = Some((session_id, Arc::new(Mutex::new(tools))));
|
||||
}
|
||||
let tools = Arc::clone(&self.agent_tools.as_ref().unwrap().1);
|
||||
|
||||
@@ -10,6 +10,8 @@ pub(super) struct PreferenceDraft {
|
||||
pub(super) endpoint_port: String,
|
||||
pub(super) endpoint_enabled: bool,
|
||||
pub(super) endpoint_cors: bool,
|
||||
pub(super) dev_brain_enabled: bool,
|
||||
pub(super) dev_brain_vault_path: String,
|
||||
pub(super) context_tokens: String,
|
||||
pub(super) max_generated_tokens: String,
|
||||
pub(super) system_prompt: text_editor::Content,
|
||||
@@ -60,6 +62,8 @@ impl PreferenceDraft {
|
||||
endpoint_port: config.endpoint.port.to_string(),
|
||||
endpoint_enabled: config.endpoint.enabled,
|
||||
endpoint_cors: config.endpoint.cors,
|
||||
dev_brain_enabled: config.dev_brain.enabled,
|
||||
dev_brain_vault_path: config.dev_brain.vault_path.clone().unwrap_or_default(),
|
||||
context_tokens: generation.context_tokens.to_string(),
|
||||
max_generated_tokens: generation.max_generated_tokens.to_string(),
|
||||
system_prompt: text_editor::Content::with_text(&generation.system_prompt),
|
||||
@@ -368,6 +372,10 @@ impl App {
|
||||
enabled: self.preference_draft.endpoint_enabled,
|
||||
cors: self.preference_draft.endpoint_cors,
|
||||
},
|
||||
dev_brain: DevBrainConfig {
|
||||
enabled: self.preference_draft.dev_brain_enabled,
|
||||
vault_path: optional_text(&self.preference_draft.dev_brain_vault_path),
|
||||
},
|
||||
generation,
|
||||
runtime,
|
||||
interface: self.config.interface.clone(),
|
||||
@@ -376,6 +384,18 @@ impl App {
|
||||
self.preference_error = Some(error);
|
||||
return;
|
||||
}
|
||||
if config.dev_brain.enabled {
|
||||
let projects = self
|
||||
.projects
|
||||
.iter()
|
||||
.map(|project| project.project.clone())
|
||||
.collect::<Vec<_>>();
|
||||
if let Err(error) = crate::dev_brain::DevBrain::open(&config.dev_brain, &projects) {
|
||||
self.preference_error = Some(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let dev_brain_changed = self.config.dev_brain != config.dev_brain;
|
||||
#[cfg(target_os = "macos")]
|
||||
let endpoint_changed = self.config.endpoint != config.endpoint;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -415,6 +435,10 @@ impl App {
|
||||
}
|
||||
self.config = config;
|
||||
#[cfg(target_os = "macos")]
|
||||
if dev_brain_changed {
|
||||
self.invalidate_dev_brain_context();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
update_runtime_config(&self.runtime_config, &self.config);
|
||||
#[cfg(target_os = "macos")]
|
||||
if endpoint_changed {
|
||||
|
||||
@@ -152,6 +152,7 @@ impl App {
|
||||
self.project_name_input.clear();
|
||||
self.error = None;
|
||||
self.reload_projects();
|
||||
self.invalidate_dev_brain_context();
|
||||
self.refresh_git_state();
|
||||
}
|
||||
Err(error) => self.error = Some(error),
|
||||
@@ -349,6 +350,21 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn invalidate_dev_brain_context(&mut self) {
|
||||
if !self.config.dev_brain.enabled {
|
||||
return;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self.agent_tools = None;
|
||||
for chat in self.background_chats.values_mut() {
|
||||
chat.agent_tools = None;
|
||||
chat.system_prompt_seen_at = 0;
|
||||
}
|
||||
}
|
||||
self.system_prompt_seen_at = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn read_git_state(path: &Path) -> Option<GitState> {
|
||||
|
||||
@@ -156,6 +156,33 @@ impl App {
|
||||
]
|
||||
.spacing(10),
|
||||
);
|
||||
let dev_brain_group = preference_group(
|
||||
PreferenceSection::DevBrain,
|
||||
"DEV BRAIN",
|
||||
column![
|
||||
hint(
|
||||
checkbox(self.preference_draft.dev_brain_enabled)
|
||||
.label("Enable project-backed LLM wiki")
|
||||
.on_toggle(Message::PreferenceDevBrainEnabledChanged),
|
||||
"Adds validated search, read, and batch-publication tools for a dedicated Obsidian vault. Disabled means no Dev Brain tools or prompt instructions.",
|
||||
),
|
||||
row![
|
||||
text_input(
|
||||
"/path/to/Obsidian vault",
|
||||
&self.preference_draft.dev_brain_vault_path,
|
||||
)
|
||||
.on_input(Message::PreferenceDevBrainVaultChanged)
|
||||
.padding(9)
|
||||
.width(Length::Fill),
|
||||
action_button("Choose…").on_press(Message::ChooseDevBrainVault),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center),
|
||||
text("The folder must already contain .obsidian. DS4Server manages only its declared wiki pages and leaves settings, attachments, hidden files, and unrelated notes untouched.")
|
||||
.size(12),
|
||||
]
|
||||
.spacing(10),
|
||||
);
|
||||
let generation_group = preference_group(
|
||||
PreferenceSection::Generation,
|
||||
"GENERATION",
|
||||
@@ -552,6 +579,7 @@ impl App {
|
||||
let mut fields = column![
|
||||
model_group,
|
||||
endpoint_group,
|
||||
dev_brain_group,
|
||||
generation_group,
|
||||
execution_group,
|
||||
acceleration_group,
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct Config {
|
||||
pub model: ModelChoice,
|
||||
pub idle_timeout_minutes: i32,
|
||||
pub a2ui_enabled: bool,
|
||||
pub dev_brain: DevBrainConfig,
|
||||
pub endpoint: EndpointConfig,
|
||||
pub generation: GenerationPreferences,
|
||||
pub runtime: RuntimePreferences,
|
||||
@@ -27,6 +28,7 @@ impl Default for Config {
|
||||
model: ModelChoice::default(),
|
||||
idle_timeout_minutes: 10,
|
||||
a2ui_enabled: true,
|
||||
dev_brain: DevBrainConfig::default(),
|
||||
endpoint: EndpointConfig::default(),
|
||||
generation: GenerationPreferences::default(),
|
||||
runtime: RuntimePreferences::default(),
|
||||
@@ -35,6 +37,35 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct DevBrainConfig {
|
||||
pub enabled: bool,
|
||||
pub vault_path: Option<String>,
|
||||
}
|
||||
|
||||
impl DevBrainConfig {
|
||||
pub fn vault(&self) -> Result<std::path::PathBuf, String> {
|
||||
if !self.enabled {
|
||||
return Err("Dev Brain is disabled.".into());
|
||||
}
|
||||
let path = self
|
||||
.vault_path
|
||||
.as_deref()
|
||||
.filter(|path| !path.trim().is_empty())
|
||||
.ok_or_else(|| "Choose an Obsidian vault before enabling Dev Brain.".to_owned())?;
|
||||
crate::dev_brain::validate_vault(Path::new(path))
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
if self.enabled {
|
||||
self.vault().map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct EndpointConfig {
|
||||
@@ -113,7 +144,8 @@ impl Config {
|
||||
return Err("Endpoint port must be between 1 and 65535.".into());
|
||||
}
|
||||
self.generation.validate()?;
|
||||
self.runtime.validate(self.model)
|
||||
self.runtime.validate(self.model)?;
|
||||
self.dev_brain.validate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1665
src/dev_brain.rs
Normal file
1665
src/dev_brain.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ mod app;
|
||||
mod compaction;
|
||||
mod config;
|
||||
mod database;
|
||||
mod dev_brain;
|
||||
mod engine;
|
||||
mod metrics;
|
||||
mod model;
|
||||
|
||||
Reference in New Issue
Block a user