agent_diva_nano/config.rs
1use std::collections::HashMap;
2use std::path::PathBuf;
3
4/// Re-export MCP server configuration from the core crate.
5pub use agent_diva_core::config::MCPServerConfig;
6
7/// Re-export tool assembly types.
8pub use crate::tool_assembly::{BuiltInToolsConfig, ShellToolConfig, WebToolConfig};
9
10/// Simplified web search configuration.
11#[derive(Debug, Clone)]
12pub struct WebSearchConfig {
13 /// Search provider name (e.g. "bocha").
14 pub provider: String,
15 /// API key for the search provider.
16 pub api_key: Option<String>,
17 /// Maximum number of results to return.
18 pub max_results: u32,
19}
20
21impl Default for WebSearchConfig {
22 fn default() -> Self {
23 Self {
24 provider: "bocha".to_string(),
25 api_key: None,
26 max_results: 5,
27 }
28 }
29}
30
31/// Soul (identity) configuration.
32#[derive(Debug, Clone)]
33pub struct SoulConfig {
34 /// Whether soul context injection is enabled.
35 pub enabled: bool,
36 /// Maximum characters for soul context.
37 pub max_chars: usize,
38 /// Bootstrap soul only once.
39 pub bootstrap_once: bool,
40 /// Notify on soul changes.
41 pub notify_on_change: bool,
42 /// Window in seconds for frequent-change detection.
43 pub frequent_change_window_secs: u64,
44 /// Threshold for frequent-change hints.
45 pub frequent_change_threshold: usize,
46 /// Show boundary confirmation hint.
47 pub boundary_confirmation_hint: bool,
48}
49
50impl Default for SoulConfig {
51 fn default() -> Self {
52 Self {
53 enabled: true,
54 max_chars: 4000,
55 bootstrap_once: true,
56 notify_on_change: true,
57 frequent_change_window_secs: 300,
58 frequent_change_threshold: 3,
59 boundary_confirmation_hint: true,
60 }
61 }
62}
63
64/// Minimal configuration to create an agent.
65///
66/// # Example
67/// ```rust,no_run
68/// use agent_diva_nano::NanoConfig;
69/// use std::path::PathBuf;
70///
71/// let config = NanoConfig {
72/// model: "deepseek-chat".to_string(),
73/// api_key: std::env::var("NANO_API_KEY").unwrap(),
74/// ..Default::default()
75/// };
76/// ```
77#[derive(Debug, Clone)]
78pub struct NanoConfig {
79 /// LLM model identifier, e.g. "deepseek-chat", "gpt-4o", "openrouter/anthropic/claude-sonnet-4".
80 pub model: String,
81 /// API key for the provider.
82 pub api_key: String,
83 /// Custom API base URL (optional, for private deployments or OpenRouter).
84 pub api_base: Option<String>,
85 /// Workspace directory — root for skills, memory, and SOUL.md.
86 pub workspace: PathBuf,
87 /// Maximum tool-call iterations per turn (default: 20).
88 pub max_iterations: usize,
89 /// Shell command execution timeout in seconds (default: 60).
90 pub exec_timeout: u64,
91 /// Restrict file-system tools to the workspace directory (default: true).
92 pub restrict_to_workspace: bool,
93 /// Web search configuration (optional).
94 pub web_search: Option<WebSearchConfig>,
95 /// MCP server configurations.
96 pub mcp_servers: HashMap<String, MCPServerConfig>,
97 /// Soul / identity context configuration.
98 pub soul: SoulConfig,
99 /// Built-in tools enable/disable configuration.
100 /// If not set, defaults to BuiltInToolsConfig::default().
101 pub builtin_tools: Option<BuiltInToolsConfig>,
102}
103
104impl Default for NanoConfig {
105 fn default() -> Self {
106 Self {
107 model: String::new(),
108 api_key: String::new(),
109 api_base: None,
110 workspace: PathBuf::from("."),
111 max_iterations: 20,
112 exec_timeout: 60,
113 restrict_to_workspace: true,
114 web_search: None,
115 mcp_servers: HashMap::new(),
116 soul: SoulConfig::default(),
117 builtin_tools: None, // Uses BuiltInToolsConfig::default() when not set
118 }
119 }
120}
121
122impl NanoConfig {
123 /// Build from environment variables:
124 /// - `NANO_MODEL`
125 /// - `NANO_API_KEY`
126 /// - `NANO_API_BASE` (optional)
127 pub fn from_env() -> Result<Self, String> {
128 let model = std::env::var("NANO_MODEL")
129 .map_err(|_| "NANO_MODEL environment variable not set")?;
130 let api_key = std::env::var("NANO_API_KEY")
131 .map_err(|_| "NANO_API_KEY environment variable not set")?;
132 let api_base = std::env::var("NANO_API_BASE").ok().filter(|s| !s.is_empty());
133
134 Ok(Self {
135 model,
136 api_key,
137 api_base,
138 ..Default::default()
139 })
140 }
141}