ToolCard styled the status glyph with a hardcoded byte offset 0..3, but
✓/✗/⟳ are 3-byte chars sitting at bytes 2..5 of " {glyph}". The split
landed mid-char and panicked at render in tuie's style slicing. Compute
the glyph region as 2 + glyph.len_utf8() instead.
Then took cargo clippy -- -D warnings from 312 failures to clean:
- scoped #![allow(dead_code)] on WIP scaffolding (federation, sensorium,
gitea_memory, model_router, session, subagent…); gate stays live on
active code so new orphans still fail
- scoped #![allow(deprecated)] on the legacy ratatui render path, marked
pending removal at tuie parity — no migration on code we're deleting
- declare the gui feature (forwards to tuie/gui) — the cfg was real intent
- real fixes: duplicate SaveAndGoBack arm + dead Err arm, base64::encode,
4 unused imports, dead assignment, private-type leak, dedup'd if/else
branches, manual clamp/strip, &PathBuf→&Path, collapsible matches
1279 lines
40 KiB
Rust
1279 lines
40 KiB
Rust
#![allow(dead_code)] // WIP scaffolding not yet wired
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::core::compact::CompactionConfig;
|
|
|
|
/// Top-level config — mirrors souveraine.example.toml structure
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ConsciousnessConfig {
|
|
/// Active inference provider (bifrost / openai-oauth / ...)
|
|
#[serde(default)]
|
|
pub inference: InferenceConfig,
|
|
|
|
/// Bifrost inference gateway config
|
|
#[serde(default, skip_serializing)]
|
|
pub bifrost: BifrostConfig,
|
|
|
|
/// z.ai (GLM Coding Plan) inference config — an OpenAI-compatible
|
|
/// provider reachable per-agent via `_souveraine.provider = "zai"`.
|
|
#[serde(default, skip_serializing)]
|
|
pub zai: ZaiConfig,
|
|
|
|
/// Unified provider map — each entry is a named inference provider.
|
|
/// Populated by [`normalize_providers`] from legacy sections on first
|
|
/// load. On save, only this map is serialized.
|
|
#[serde(default)]
|
|
pub providers: HashMap<String, ProviderConfig>,
|
|
|
|
/// Per-model physics configs
|
|
#[serde(default)]
|
|
pub models: HashMap<String, ModelConfig>,
|
|
|
|
/// Subconscious (N+1, inbox)
|
|
#[serde(default)]
|
|
pub subconscious: SubconsciousConfig,
|
|
|
|
/// Reflection (N+25)
|
|
#[serde(default)]
|
|
pub reflection: ReflectionConfig,
|
|
|
|
/// Archivist (N+100 compression)
|
|
#[serde(default)]
|
|
pub archivist: ArchivistConfig,
|
|
|
|
/// In-session message compaction
|
|
#[serde(default)]
|
|
pub compaction: CompactionConfig,
|
|
|
|
/// Subagent pool
|
|
#[serde(default)]
|
|
pub subagent: SubagentConfig,
|
|
|
|
/// Memory (git-backed)
|
|
#[serde(default)]
|
|
pub memory: MemoryConfig,
|
|
|
|
/// WebSocket server
|
|
#[serde(default)]
|
|
pub websocket: WebSocketConfig,
|
|
|
|
/// Sensorium (interface abstraction)
|
|
#[serde(default)]
|
|
pub sensorium: SensoriumConfig,
|
|
|
|
/// Server bind/port + client connection URL
|
|
#[serde(default)]
|
|
pub server: ServerConfig,
|
|
|
|
/// Schedule system (cron sensor)
|
|
#[serde(default)]
|
|
pub schedules: SchedulesConfig,
|
|
|
|
/// Event persistence (firehose log)
|
|
#[serde(default)]
|
|
pub events: EventsConfig,
|
|
|
|
/// Federation (cross-instance sync)
|
|
#[serde(default)]
|
|
pub federation: FederationConfig,
|
|
|
|
/// Self-awareness pulse during long turns (in-turn noticing of time passing).
|
|
#[serde(default)]
|
|
pub presence: PresenceConfig,
|
|
|
|
/// Voice channel — STT/TTS services, mic capture, push-to-talk key.
|
|
#[serde(default)]
|
|
pub voice: VoiceConfig,
|
|
|
|
/// Top-level agent/substrate settings — platform prompt, etc.
|
|
#[serde(default)]
|
|
pub agent: AgentConfig,
|
|
|
|
/// TUI interface settings (stale timeout, etc.)
|
|
#[serde(default)]
|
|
pub tui: TuiConfig,
|
|
|
|
/// Image input configuration for multimodal (vision) support.
|
|
#[serde(default)]
|
|
pub image: ImageConfig,
|
|
}
|
|
|
|
// ── Agent (top-level) ──
|
|
|
|
/// Substrate-level settings for the primary agent.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct AgentConfig {
|
|
/// Platform prompt — injected at the very top of the system prompt,
|
|
/// before the agent's own identity files. Operator-level context that
|
|
/// the agent reads but did not write. Editable in Settings > Agent.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub system_prompt: Option<String>,
|
|
}
|
|
|
|
// ── Server ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServerConfig {
|
|
/// Where the server listens (bind address)
|
|
#[serde(default = "default_server_bind")]
|
|
pub bind: String,
|
|
|
|
/// TCP port the server listens on
|
|
#[serde(default = "default_server_port")]
|
|
pub port: u16,
|
|
|
|
/// URL clients use to reach the server. Env `SOUVERAINE_SERVER_URL` wins
|
|
/// at runtime; this value is the persistent default.
|
|
#[serde(default = "default_server_url")]
|
|
pub url: String,
|
|
|
|
/// Auth configuration for the memfs HTTP write path.
|
|
#[serde(default)]
|
|
pub auth: AuthConfig,
|
|
}
|
|
|
|
impl Default for ServerConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
bind: default_server_bind(),
|
|
port: default_server_port(),
|
|
url: default_server_url(),
|
|
auth: AuthConfig::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ServerConfig {
|
|
/// Effective URL — env var overrides config.
|
|
pub fn effective_url(&self) -> String {
|
|
std::env::var("SOUVERAINE_SERVER_URL").unwrap_or_else(|_| self.url.clone())
|
|
}
|
|
}
|
|
|
|
// ── Auth ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AuthConfig {
|
|
/// Require bearer-token authentication for memory routes.
|
|
#[serde(default = "default_true")]
|
|
pub required: bool,
|
|
|
|
/// Allow loopback (127.0.0.1 / ::1) requests to bypass auth.
|
|
#[serde(default = "default_true")]
|
|
pub allow_loopback: bool,
|
|
}
|
|
|
|
impl Default for AuthConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
required: true,
|
|
allow_loopback: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Inference ──
|
|
|
|
/// Selects the active LLM provider. Each provider reads its own config
|
|
/// section ([bifrost], or external auth like the Codex CLI login).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct InferenceConfig {
|
|
/// Active provider: "bifrost" (OpenAI-compatible gateway, default)
|
|
/// or "openai-oauth" (ride the Codex CLI ChatGPT login →
|
|
/// backend-api/codex/responses). See `bridge::build_provider`.
|
|
#[serde(default = "default_provider")]
|
|
pub provider: String,
|
|
}
|
|
|
|
impl Default for InferenceConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
provider: default_provider(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Bifrost ──
|
|
|
|
/// Bifrost provider configuration. Read when `[inference] provider = "bifrost"`.
|
|
/// `primary_model` and `timeout_secs` are shared cross-provider concepts
|
|
/// that the OAuth provider receives as constructor parameters; they live here
|
|
/// because Bifrost is the historical default and every call-site already reads
|
|
/// them from this struct.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BifrostConfig {
|
|
/// Bifrost API base URL (e.g. "http://127.0.0.1:3360")
|
|
#[serde(default = "default_bifrost_url")]
|
|
pub base_url: String,
|
|
|
|
/// Bearer token for auth
|
|
#[serde(default = "default_bifrost_key")]
|
|
pub api_key: String,
|
|
|
|
/// Virtual key for x-bf-vk header (required by some providers)
|
|
#[serde(default = "default_bifrost_virtual_key")]
|
|
pub virtual_key: String,
|
|
|
|
/// Default model for conversation
|
|
#[serde(default = "default_primary_model")]
|
|
pub primary_model: String,
|
|
|
|
/// Per-model overrides
|
|
#[serde(default)]
|
|
pub models: HashMap<String, BifrostModelConfig>,
|
|
|
|
/// Request timeout in seconds for each LLM call attempt.
|
|
/// When exceeded, the attempt is treated as a transient error and retried.
|
|
/// Default: 120 (two minutes per attempt, 7 attempts = ~14 min total).
|
|
#[serde(default = "default_bifrost_timeout")]
|
|
pub timeout_secs: u64,
|
|
}
|
|
|
|
// ── z.ai ──
|
|
|
|
/// z.ai (GLM Coding Plan) provider configuration. An OpenAI-compatible
|
|
/// endpoint served from `https://api.z.ai/api/coding/paas/v4`. Selected
|
|
/// per-agent via `_souveraine.provider = "zai"`; otherwise unused. Empty by
|
|
/// default so configs without a `[zai]` section still parse.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ZaiConfig {
|
|
/// Coding Plan endpoint, e.g. "https://api.z.ai/api/coding/paas/v4".
|
|
#[serde(default)]
|
|
pub base_url: String,
|
|
|
|
/// Bearer token (Zhipu `{id}.{secret}` key).
|
|
#[serde(default)]
|
|
pub api_key: String,
|
|
|
|
/// Default model for z.ai agents (e.g. "glm-5.2").
|
|
#[serde(default)]
|
|
pub primary_model: String,
|
|
|
|
/// Per-attempt timeout in seconds. glm-5.2 is a reasoning model and can
|
|
/// take a while; default generously.
|
|
#[serde(default = "default_zai_timeout")]
|
|
pub timeout_secs: u64,
|
|
}
|
|
|
|
impl Default for ZaiConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
base_url: String::new(),
|
|
api_key: String::new(),
|
|
primary_model: String::new(),
|
|
timeout_secs: default_zai_timeout(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_zai_timeout() -> u64 {
|
|
300
|
|
}
|
|
|
|
/// A named inference provider entry in `[providers.<name>]`.
|
|
///
|
|
/// Each entry carries a `type` discriminator and endpoint/auth/model defaults.
|
|
/// `build_provider_from_config` dispatches on `provider_type` to construct the
|
|
/// right [`LlmProvider`](crate::bridge::LlmProvider) implementation.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ProviderConfig {
|
|
/// Provider implementation: `"openai-compatible"` (any OpenAI-style gateway,
|
|
/// including Bifrost and z.ai) or `"openai-oauth"` (Codex CLI ChatGPT login).
|
|
#[serde(rename = "type")]
|
|
pub provider_type: String,
|
|
|
|
/// API base URL (not used by `openai-oauth`).
|
|
#[serde(default)]
|
|
pub base_url: String,
|
|
|
|
/// Bearer token or API key (not used by `openai-oauth`).
|
|
#[serde(default)]
|
|
pub api_key: String,
|
|
|
|
/// Virtual-key header value (`x-bf-vk` for Bifrost; empty for most providers).
|
|
#[serde(default)]
|
|
pub virtual_key: String,
|
|
|
|
/// Default model for this provider.
|
|
#[serde(default)]
|
|
pub primary_model: String,
|
|
|
|
/// Per-attempt timeout in seconds.
|
|
#[serde(default = "default_bifrost_timeout")]
|
|
pub timeout_secs: u64,
|
|
}
|
|
|
|
// ── Built-in provider catalog ──────────────────────────────────────────────
|
|
|
|
/// A preset for a well-known inference provider. The user can add any of these
|
|
/// with one keypress in Settings > Providers. All speak OpenAI-compatible
|
|
/// (`/v1/chat/completions`) unless noted otherwise.
|
|
#[derive(Debug, Clone)]
|
|
pub struct ProviderPreset {
|
|
/// Short machine-facing id (e.g. "deepseek").
|
|
pub id: &'static str,
|
|
/// Human label for the settings UI (e.g. "DeepSeek").
|
|
pub display_name: &'static str,
|
|
/// API base URL for `/v1/chat/completions`.
|
|
pub api_base: &'static str,
|
|
/// Environment variable that holds the API key. Empty means the user
|
|
/// supplies it directly in the config (or the provider has no key, like a
|
|
/// localhost endpoint).
|
|
pub api_key_env: &'static str,
|
|
/// Default model for the provider. `None` falls back to the live `/models`
|
|
/// catalog or the user supplies it manually.
|
|
pub default_model: Option<&'static str>,
|
|
/// Where to get an API key / sign up.
|
|
pub setup_url: &'static str,
|
|
}
|
|
|
|
/// Well-known inference providers. Every entry here can be added to
|
|
/// `[providers.<id>]` instantly from the TUI.
|
|
pub static KNOWN_PROVIDERS: &[ProviderPreset] = &[
|
|
ProviderPreset {
|
|
id: "deepseek",
|
|
display_name: "DeepSeek",
|
|
api_base: "https://api.deepseek.com",
|
|
api_key_env: "DEEPSEEK_API_KEY",
|
|
default_model: Some("deepseek-chat"),
|
|
setup_url: "https://api-docs.deepseek.com/",
|
|
},
|
|
ProviderPreset {
|
|
id: "mimo",
|
|
display_name: "Xiaomi MiMo",
|
|
api_base: "https://api.xiaomimimo.com/v1",
|
|
api_key_env: "XIAOMI_MIMO_API_KEY",
|
|
default_model: Some("mimo-v2.5"),
|
|
setup_url: "https://platform.xiaomimimo.com",
|
|
},
|
|
ProviderPreset {
|
|
id: "mistral",
|
|
display_name: "Mistral",
|
|
api_base: "https://api.mistral.ai/v1",
|
|
api_key_env: "MISTRAL_API_KEY",
|
|
default_model: Some("mistral-large-latest"),
|
|
setup_url: "https://docs.mistral.ai/",
|
|
},
|
|
ProviderPreset {
|
|
id: "openrouter",
|
|
display_name: "OpenRouter",
|
|
api_base: "https://openrouter.ai/api/v1",
|
|
api_key_env: "OPENROUTER_API_KEY",
|
|
default_model: None,
|
|
setup_url: "https://openrouter.ai/keys",
|
|
},
|
|
ProviderPreset {
|
|
id: "groq",
|
|
display_name: "Groq",
|
|
api_base: "https://api.groq.com/openai/v1",
|
|
api_key_env: "GROQ_API_KEY",
|
|
default_model: Some("llama-3.3-70b-versatile"),
|
|
setup_url: "https://console.groq.com/keys",
|
|
},
|
|
ProviderPreset {
|
|
id: "openai",
|
|
display_name: "OpenAI API",
|
|
api_base: "https://api.openai.com/v1",
|
|
api_key_env: "OPENAI_API_KEY",
|
|
default_model: Some("gpt-4o"),
|
|
setup_url: "https://platform.openai.com/api-keys",
|
|
},
|
|
ProviderPreset {
|
|
id: "xai",
|
|
display_name: "xAI (Grok)",
|
|
api_base: "https://api.x.ai/v1",
|
|
api_key_env: "XAI_API_KEY",
|
|
default_model: Some("grok-3"),
|
|
setup_url: "https://docs.x.ai/",
|
|
},
|
|
ProviderPreset {
|
|
id: "gemini",
|
|
display_name: "Gemini API (OpenAI compat)",
|
|
api_base: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
api_key_env: "GEMINI_API_KEY",
|
|
default_model: Some("gemini-2.5-flash"),
|
|
setup_url: "https://ai.google.dev/gemini-api/docs/openai",
|
|
},
|
|
ProviderPreset {
|
|
id: "ollama",
|
|
display_name: "Ollama (local)",
|
|
api_base: "http://localhost:11434/v1",
|
|
api_key_env: "",
|
|
default_model: None,
|
|
setup_url: "https://ollama.com/download",
|
|
},
|
|
ProviderPreset {
|
|
id: "lmstudio",
|
|
display_name: "LM Studio (local)",
|
|
api_base: "http://localhost:1234/v1",
|
|
api_key_env: "",
|
|
default_model: None,
|
|
setup_url: "https://lmstudio.ai/",
|
|
},
|
|
ProviderPreset {
|
|
id: "perplexity",
|
|
display_name: "Perplexity",
|
|
api_base: "https://api.perplexity.ai",
|
|
api_key_env: "PERPLEXITY_API_KEY",
|
|
default_model: Some("sonar"),
|
|
setup_url: "https://docs.perplexity.ai/",
|
|
},
|
|
ProviderPreset {
|
|
id: "together",
|
|
display_name: "Together AI",
|
|
api_base: "https://api.together.xyz/v1",
|
|
api_key_env: "TOGETHER_API_KEY",
|
|
default_model: Some("meta-llama/Llama-3.3-70B-Instruct-Turbo"),
|
|
setup_url: "https://docs.together.ai/docs/openai-api-compatibility",
|
|
},
|
|
ProviderPreset {
|
|
id: "fireworks",
|
|
display_name: "Fireworks AI",
|
|
api_base: "https://api.fireworks.ai/inference/v1",
|
|
api_key_env: "FIREWORKS_API_KEY",
|
|
default_model: Some("accounts/fireworks/models/llama-v3p3-70b-instruct"),
|
|
setup_url: "https://docs.fireworks.ai/",
|
|
},
|
|
ProviderPreset {
|
|
id: "cerebras",
|
|
display_name: "Cerebras",
|
|
api_base: "https://api.cerebras.ai/v1",
|
|
api_key_env: "CEREBRAS_API_KEY",
|
|
default_model: Some("llama3.3-70b"),
|
|
setup_url: "https://inference-docs.cerebras.ai/",
|
|
},
|
|
ProviderPreset {
|
|
id: "kimi",
|
|
display_name: "Kimi Code",
|
|
api_base: "https://api.kimi.com/coding/v1",
|
|
api_key_env: "KIMI_API_KEY",
|
|
default_model: Some("kimi-for-coding"),
|
|
setup_url: "https://www.kimi.com/coding/",
|
|
},
|
|
ProviderPreset {
|
|
id: "custom",
|
|
display_name: "Custom (OpenAI-compatible)",
|
|
api_base: "",
|
|
api_key_env: "",
|
|
default_model: None,
|
|
setup_url: "",
|
|
},
|
|
];
|
|
|
|
impl ProviderPreset {
|
|
/// Find a preset by its id. Returns `None` for unknown ids.
|
|
pub fn find(id: &str) -> Option<&'static ProviderPreset> {
|
|
KNOWN_PROVIDERS.iter().find(|p| p.id == id)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BifrostModelConfig {
|
|
#[serde(default = "default_128k")]
|
|
pub context_limit: usize,
|
|
#[serde(default = "default_8k")]
|
|
pub output_limit: usize,
|
|
#[serde(default = "default_threshold_70")]
|
|
pub archivist_threshold: f32,
|
|
}
|
|
|
|
impl Default for BifrostConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
base_url: default_bifrost_url(),
|
|
api_key: default_bifrost_key(),
|
|
virtual_key: default_bifrost_virtual_key(),
|
|
primary_model: default_primary_model(),
|
|
models: HashMap::new(),
|
|
timeout_secs: default_bifrost_timeout(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Subconscious ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SubconsciousConfig {
|
|
#[serde(default = "default_true")]
|
|
pub n1_enabled: bool,
|
|
#[serde(default)]
|
|
pub n1_trigger: N1Trigger,
|
|
#[serde(default = "default_true")]
|
|
pub inbox_enabled: bool,
|
|
/// Model handle for the subconscious pass (e.g. "openai/glm-5.1").
|
|
/// Defaults to None — uses the primary agent's model.
|
|
#[serde(default)]
|
|
pub model: Option<String>,
|
|
/// Max tokens for subconscious's response. Set to control cost/length.
|
|
/// Defaults to None — let the model use its full output capacity.
|
|
#[serde(default)]
|
|
pub max_tokens: Option<u32>,
|
|
/// Platform prompt for the subconscious — prepended to the prompt she
|
|
/// assembles from her own memfs. The subconscious's equivalent of
|
|
/// `AgentConfig.system_prompt`. Defaults to None (memfs + body
|
|
/// orientation only).
|
|
#[serde(default)]
|
|
pub system_prompt: Option<String>,
|
|
/// Per-agent N+ interval overrides (e.g. Ani=N+1, Helper=N+5)
|
|
#[serde(default)]
|
|
pub per_agent_intervals: HashMap<String, AgentSubconsciousConfig>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentSubconsciousConfig {
|
|
pub n_interval: usize,
|
|
}
|
|
|
|
impl Default for SubconsciousConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
n1_enabled: true,
|
|
n1_trigger: N1Trigger::EveryResponse,
|
|
inbox_enabled: true,
|
|
model: None,
|
|
max_tokens: None,
|
|
system_prompt: None,
|
|
per_agent_intervals: HashMap::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Reflection ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReflectionConfig {
|
|
#[serde(default = "default_true")]
|
|
pub enabled: bool,
|
|
#[serde(default = "default_25")]
|
|
pub message_interval: usize,
|
|
#[serde(default)]
|
|
pub trigger: ReflectionTrigger,
|
|
/// Model handle for reflection passes. Falls back to subconscious model when unset.
|
|
#[serde(default)]
|
|
pub model: Option<String>,
|
|
#[serde(default)]
|
|
pub per_agent: HashMap<String, AgentReflectionSettings>,
|
|
}
|
|
|
|
impl Default for ReflectionConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
message_interval: 25,
|
|
trigger: ReflectionTrigger::StepCount,
|
|
model: None,
|
|
per_agent: HashMap::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Archivist ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ArchivistConfig {
|
|
#[serde(default = "default_true")]
|
|
pub enabled: bool,
|
|
#[serde(default = "default_100")]
|
|
pub interval: usize,
|
|
#[serde(default = "default_threshold_70")]
|
|
pub threshold: f32,
|
|
#[serde(default = "default_auto_model")]
|
|
pub compression_model: String,
|
|
#[serde(default = "default_synthesis_elements")]
|
|
pub synthesis_elements: Vec<SynthesisElement>,
|
|
}
|
|
|
|
impl Default for ArchivistConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
interval: 100,
|
|
threshold: 0.7,
|
|
compression_model: "auto".to_string(),
|
|
synthesis_elements: default_synthesis_elements(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Subagent ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SubagentConfig {
|
|
#[serde(default = "default_true")]
|
|
pub enabled: bool,
|
|
#[serde(default = "default_3")]
|
|
pub max_concurrent: usize,
|
|
#[serde(default = "default_300")]
|
|
pub timeout: u64,
|
|
/// Maximum nesting depth for spawned subagents.
|
|
#[serde(default = "default_3u32")]
|
|
pub max_depth: u32,
|
|
/// Maximum tool rounds per subagent turn.
|
|
#[serde(default = "default_25u32")]
|
|
pub max_tool_rounds: u32,
|
|
/// Fraction of max_tool_rounds at which first warning fires.
|
|
#[serde(default = "default_warning_1_threshold")]
|
|
pub warning_1_threshold: f32,
|
|
/// Fraction of max_tool_rounds at which second warning fires.
|
|
#[serde(default = "default_warning_2_threshold")]
|
|
pub warning_2_threshold: f32,
|
|
/// Milliseconds to wait between subagent tool rounds.
|
|
/// Helps avoid rate-limit cascades from rapid consecutive LLM calls.
|
|
/// Default: 300ms. Set to 0 to disable.
|
|
#[serde(default = "default_sub_inter_round_delay")]
|
|
pub inter_round_delay_ms: u64,
|
|
}
|
|
|
|
impl Default for SubagentConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
max_concurrent: 3,
|
|
timeout: 300,
|
|
max_depth: 3,
|
|
max_tool_rounds: 50,
|
|
warning_1_threshold: 0.8,
|
|
warning_2_threshold: 0.95,
|
|
inter_round_delay_ms: 300,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Memory ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MemoryConfig {
|
|
#[serde(default = "default_true")]
|
|
pub git_enabled: bool,
|
|
#[serde(default = "default_true")]
|
|
pub auto_commit: bool,
|
|
#[serde(default)]
|
|
pub auto_push: bool,
|
|
#[serde(default)]
|
|
pub base_path: Option<PathBuf>,
|
|
}
|
|
|
|
impl Default for MemoryConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
git_enabled: true,
|
|
auto_commit: true,
|
|
auto_push: false,
|
|
base_path: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── WebSocket ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WebSocketConfig {
|
|
#[serde(default)]
|
|
pub enabled: bool,
|
|
#[serde(default = "default_7373")]
|
|
pub port: u16,
|
|
}
|
|
|
|
impl Default for WebSocketConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
port: 7373,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Sensorium ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SensoriumConfig {
|
|
#[serde(default = "default_bandwidth_high")]
|
|
pub primary_bandwidth: BandwidthClass,
|
|
#[serde(default)]
|
|
pub discovery: DiscoveryConfig,
|
|
#[serde(default = "default_true")]
|
|
pub mobile_context_aware: bool,
|
|
}
|
|
|
|
impl Default for SensoriumConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
primary_bandwidth: BandwidthClass::High,
|
|
discovery: DiscoveryConfig::default(),
|
|
mobile_context_aware: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DiscoveryConfig {
|
|
#[serde(default = "default_true")]
|
|
pub low_urgency_only: bool,
|
|
#[serde(default = "default_presence_breathing")]
|
|
pub minimal_presence_mode: String,
|
|
}
|
|
|
|
impl Default for DiscoveryConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
low_urgency_only: true,
|
|
minimal_presence_mode: "breathing_color".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Agent Identity ──
|
|
|
|
/// Discriminates agent types for directory routing and behavior.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum AgentType {
|
|
Primary,
|
|
Subconscious,
|
|
Subagent,
|
|
}
|
|
|
|
/// Identity metadata carried by every agent at creation time.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentIdentity {
|
|
pub agent_type: AgentType,
|
|
/// Set for Subconscious and Subagent — links back to the creator.
|
|
pub parent_agent: Option<String>,
|
|
/// Path or generator key for this agent's system prompt.
|
|
pub system_prompt_source: String,
|
|
}
|
|
|
|
// ── Enums & Shared Types ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[derive(Default)]
|
|
pub enum N1Trigger {
|
|
#[default]
|
|
EveryResponse,
|
|
EveryNResponses(usize),
|
|
TimeBased(u64),
|
|
Manual,
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[derive(Default)]
|
|
pub enum ReflectionTrigger {
|
|
Off,
|
|
#[default]
|
|
StepCount,
|
|
CompactionEvent,
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[derive(Default)]
|
|
pub enum BandwidthClass {
|
|
#[default]
|
|
High, Medium, Low, Minimal,
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum SynthesisElement {
|
|
Themes, Emotions, Tensions, Anchors, Evolution, Patterns,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum TaskType {
|
|
Conversation, Synthesis, Reflection, Research, FastResponse, Coding,
|
|
}
|
|
|
|
// ── Model Physics ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ModelConfig {
|
|
pub provider: String,
|
|
pub model: String,
|
|
#[serde(default = "default_128k")]
|
|
pub context_limit: usize,
|
|
#[serde(default = "default_8k")]
|
|
pub output_limit: usize,
|
|
#[serde(default = "default_threshold_70")]
|
|
pub archivist_threshold: f32,
|
|
#[serde(default = "default_100")]
|
|
pub archivist_interval: usize,
|
|
#[serde(default)]
|
|
pub preferred_for: Vec<TaskType>,
|
|
/// Whether this model supports image inputs (vision).
|
|
#[serde(default = "default_supports_images")]
|
|
pub supports_images: bool,
|
|
}
|
|
|
|
// ── Image (multimodal input) ──
|
|
|
|
/// Configuration for the image resize pipeline applied to multimodal inputs.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ImageConfig {
|
|
/// Maximum image width in pixels after resize. Default 2000.
|
|
#[serde(default = "default_image_max_width")]
|
|
pub max_width: u32,
|
|
/// Maximum image height in pixels after resize. Default 2000.
|
|
#[serde(default = "default_image_max_height")]
|
|
pub max_height: u32,
|
|
/// Maximum pixel count (w * h) after resize. Default 25MP.
|
|
#[serde(default = "default_image_max_pixels")]
|
|
pub max_pixels: u32,
|
|
/// Maximum encoded file size in bytes. Default 5MB.
|
|
#[serde(default = "default_image_max_bytes")]
|
|
pub max_bytes: usize,
|
|
/// JPEG/WebP quality for encode pass (1-100). Default 85.
|
|
#[serde(default = "default_image_jpeg_quality")]
|
|
pub jpeg_quality: u8,
|
|
/// Supported inbound media types.
|
|
#[serde(default)]
|
|
pub supported_types: Vec<String>,
|
|
}
|
|
|
|
impl Default for ImageConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
max_width: 2000,
|
|
max_height: 2000,
|
|
max_pixels: 25_000_000,
|
|
max_bytes: 5 * 1024 * 1024,
|
|
jpeg_quality: 85,
|
|
supported_types: vec![
|
|
"image/png".to_string(),
|
|
"image/jpeg".to_string(),
|
|
"image/webp".to_string(),
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_image_max_width() -> u32 { 2000 }
|
|
fn default_image_max_height() -> u32 { 2000 }
|
|
fn default_image_max_pixels() -> u32 { 25_000_000 }
|
|
fn default_image_max_bytes() -> usize { 5 * 1024 * 1024 }
|
|
fn default_image_jpeg_quality() -> u8 { 85 }
|
|
|
|
// ── Agent Reflection Settings ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentReflectionSettings {
|
|
pub trigger: ReflectionTrigger,
|
|
#[serde(default = "default_25")]
|
|
pub step_count: usize,
|
|
}
|
|
|
|
// ── Defaults ──
|
|
|
|
impl Default for ConsciousnessConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
inference: InferenceConfig::default(),
|
|
bifrost: BifrostConfig::default(),
|
|
zai: ZaiConfig::default(),
|
|
models: default_models(),
|
|
subconscious: SubconsciousConfig::default(),
|
|
reflection: ReflectionConfig::default(),
|
|
archivist: ArchivistConfig::default(),
|
|
compaction: CompactionConfig::default(),
|
|
subagent: SubagentConfig::default(),
|
|
memory: MemoryConfig::default(),
|
|
websocket: WebSocketConfig::default(),
|
|
sensorium: SensoriumConfig::default(),
|
|
server: ServerConfig::default(),
|
|
schedules: SchedulesConfig::default(),
|
|
events: EventsConfig::default(),
|
|
federation: FederationConfig::default(),
|
|
presence: PresenceConfig::default(),
|
|
voice: VoiceConfig::default(),
|
|
agent: AgentConfig::default(),
|
|
tui: TuiConfig::default(),
|
|
image: ImageConfig::default(),
|
|
providers: HashMap::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ConsciousnessConfig {
|
|
/// Populate `self.providers` from legacy `[bifrost]` and `[zai]` sections
|
|
/// when the unified providers map is empty. Called after config load so
|
|
/// existing configs continue to work without migration.
|
|
///
|
|
/// On the next config save, only `[providers]` is serialized (legacy fields
|
|
/// are `#[serde(skip_serializing)]`), completing the migration.
|
|
pub fn normalize_providers(&mut self) {
|
|
if !self.providers.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let default_bifrost_url = default_bifrost_url();
|
|
if self.bifrost.base_url != default_bifrost_url || !self.bifrost.api_key.is_empty() {
|
|
self.providers.insert(
|
|
"bifrost".to_string(),
|
|
ProviderConfig {
|
|
provider_type: "openai-compatible".to_string(),
|
|
base_url: self.bifrost.base_url.clone(),
|
|
api_key: self.bifrost.api_key.clone(),
|
|
virtual_key: self.bifrost.virtual_key.clone(),
|
|
primary_model: self.bifrost.primary_model.clone(),
|
|
timeout_secs: self.bifrost.timeout_secs,
|
|
},
|
|
);
|
|
}
|
|
|
|
if !self.zai.base_url.is_empty() {
|
|
self.providers.insert(
|
|
"zai".to_string(),
|
|
ProviderConfig {
|
|
provider_type: "openai-compatible".to_string(),
|
|
base_url: self.zai.base_url.clone(),
|
|
api_key: self.zai.api_key.clone(),
|
|
virtual_key: String::new(),
|
|
primary_model: self.zai.primary_model.clone(),
|
|
timeout_secs: self.zai.timeout_secs,
|
|
},
|
|
);
|
|
}
|
|
|
|
if self.inference.provider == "openai-oauth" && !self.providers.contains_key("openai-oauth")
|
|
{
|
|
self.providers.insert(
|
|
"openai-oauth".to_string(),
|
|
ProviderConfig {
|
|
provider_type: "openai-oauth".to_string(),
|
|
base_url: String::new(),
|
|
api_key: String::new(),
|
|
virtual_key: String::new(),
|
|
primary_model: self.bifrost.primary_model.clone(),
|
|
timeout_secs: self.bifrost.timeout_secs,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SchedulesConfig {
|
|
#[serde(default = "default_true")]
|
|
pub enabled: bool,
|
|
#[serde(default)]
|
|
pub schedules_dir: Option<PathBuf>,
|
|
}
|
|
|
|
impl Default for SchedulesConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
schedules_dir: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Events / Firehose ──
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EventsConfig {
|
|
#[serde(default = "default_true")]
|
|
pub enabled: bool,
|
|
#[serde(default)]
|
|
pub events_dir: Option<PathBuf>,
|
|
#[serde(default = "default_retain_days")]
|
|
pub retain_days: i64,
|
|
}
|
|
|
|
fn default_retain_days() -> i64 {
|
|
30
|
|
}
|
|
|
|
impl Default for EventsConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: true,
|
|
events_dir: None,
|
|
retain_days: 30,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Presence (self-awareness pulse) ──
|
|
|
|
/// During a long turn, every `interval_secs`, the body injects a brief
|
|
/// system message in the agent's own register — a beat of self-awareness,
|
|
/// not a verdict. She reads it, decides what to do. Substrate, not harness.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PresenceConfig {
|
|
#[serde(default = "default_true")]
|
|
pub pulse_enabled: bool,
|
|
#[serde(default = "default_pulse_interval")]
|
|
pub pulse_interval_secs: u64,
|
|
/// Current outfit name (subdirectory under `expressions/`).
|
|
/// Empty or None means root-level expressions.
|
|
#[serde(default)]
|
|
pub outfit: Option<String>,
|
|
/// Current atmosphere preset name. Empty defaults to posture-linked.
|
|
#[serde(default)]
|
|
pub atmosphere: Option<String>,
|
|
}
|
|
|
|
impl Default for PresenceConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
pulse_enabled: true,
|
|
pulse_interval_secs: default_pulse_interval(),
|
|
outfit: None,
|
|
atmosphere: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_pulse_interval() -> u64 { 600 }
|
|
|
|
// ── TUI ──
|
|
|
|
/// TUI-specific settings. None of these are load-bearing for the agent; they
|
|
/// tune the interface layer only.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TuiConfig {
|
|
/// Deprecated — the auto-reset stall guard has been removed. The TUI
|
|
/// now trusts the backend stream and shows a liveness label instead.
|
|
/// Kept for config backwards compatibility.
|
|
#[serde(default = "default_stale_timeout_secs")]
|
|
pub stale_timeout_secs: u64,
|
|
/// When the model produces text alongside tool calls, surface it in the
|
|
/// chat stream as interstitial narration. Off = silent tool chains.
|
|
#[serde(default = "default_true")]
|
|
pub show_interstitial: bool,
|
|
/// Word-count boundary between the two interstitial registers. Narration
|
|
/// shorter than this is a "cenno" — a terse ambient aside attached to
|
|
/// tool work. At or above it, it's "her-voice": a substantive mid-turn
|
|
/// passage, rendered with a gutter bar instead of a quiet italic line.
|
|
#[serde(default = "default_cenno_word_threshold")]
|
|
pub cenno_word_threshold: usize,
|
|
}
|
|
|
|
impl Default for TuiConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
stale_timeout_secs: default_stale_timeout_secs(),
|
|
show_interstitial: true,
|
|
cenno_word_threshold: default_cenno_word_threshold(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_cenno_word_threshold() -> usize { 30 }
|
|
|
|
fn default_stale_timeout_secs() -> u64 { 90 }
|
|
|
|
// ── Federation ──
|
|
|
|
/// A machine's role in the federation.
|
|
///
|
|
/// - `Hearth` — where the agent lives. Full engine always up, the autonomous
|
|
/// rhythm runs here, the memfs HEAD is authoritative. One per agent.
|
|
/// - `Limb` — a place she can reach to. At rest it's the lite listener; an
|
|
/// authorized summon wakes the full engine, it acts, reports up, and rests.
|
|
/// A limb does not run the general autonomous rhythm — that keeps one
|
|
/// heartbeat at the hearth and no split-brain across machines.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum FederationRole {
|
|
#[default]
|
|
Hearth,
|
|
Limb,
|
|
}
|
|
|
|
impl FederationRole {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
Self::Hearth => "hearth",
|
|
Self::Limb => "limb",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FederationConfig {
|
|
#[serde(default)]
|
|
pub enabled: bool,
|
|
#[serde(default)]
|
|
pub instance_label: Option<String>,
|
|
/// This machine's role — hearth (the agent's home) or limb (a place she
|
|
/// reaches to). Defaults to hearth: a standalone machine is its own home.
|
|
#[serde(default)]
|
|
pub role: FederationRole,
|
|
/// Peers this instance federates with. Each peer connection is a signed
|
|
/// WS stream to the peer's federation endpoint.
|
|
#[serde(default)]
|
|
pub peers: Vec<PeerConfig>,
|
|
/// Seed_ids permitted to `consult` an agent on this instance. The basic
|
|
/// consent floor — empty plus a missing `authorized-summoners.md` means
|
|
/// permissive; richer per-arena gating is the agent's memfs concern.
|
|
#[serde(default)]
|
|
pub authorized_summoners: Vec<String>,
|
|
/// An authorized summon may wake the target agent. In lite-listener mode
|
|
/// this spawns the full engine; in the full engine it injects a background
|
|
/// turn so she picks the request up now rather than on her next turn.
|
|
/// Sovereign default: off — a summon otherwise just lands in her inbox.
|
|
#[serde(default)]
|
|
pub auto_wake: bool,
|
|
}
|
|
|
|
impl Default for FederationConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
instance_label: None,
|
|
role: FederationRole::Hearth,
|
|
peers: Vec::new(),
|
|
authorized_summoners: Vec::new(),
|
|
auto_wake: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A federated peer. `pubkey` is the peer's Ed25519 public key (hex) — the
|
|
/// trust root for verifying every event the peer sends.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PeerConfig {
|
|
/// Peer's federation endpoint, e.g. `ws://192.168.1.50:8484`.
|
|
pub url: String,
|
|
/// Peer's Ed25519 public key, hex-encoded.
|
|
pub pubkey: String,
|
|
/// Sensor-name subscriptions — which events this peer should receive.
|
|
/// `*` = all; a bare name = exact; `name*` = prefix. Empty = none.
|
|
#[serde(default)]
|
|
pub subscriptions: Vec<String>,
|
|
}
|
|
|
|
// ── Voice channel ──
|
|
|
|
/// Voice channel configuration: STT (Faster-Whisper) + TTS (VibeVoice).
|
|
///
|
|
/// When `enabled = false`, the Presence screen behaves as today; no audio
|
|
/// devices are opened. The agent doesn't know or care whether voice is on —
|
|
/// voice is a sensorium channel, not a tool she calls.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VoiceConfig {
|
|
/// Opt-in; default off. Audio devices not touched when false.
|
|
#[serde(default)]
|
|
pub enabled: bool,
|
|
/// Faster-Whisper HTTP base URL.
|
|
#[serde(default = "default_stt_url")]
|
|
pub stt_url: String,
|
|
/// VibeVoice HTTP base URL.
|
|
#[serde(default = "default_tts_url")]
|
|
pub tts_url: String,
|
|
/// Voice ID passed to VibeVoice (`/audio/speech`).
|
|
#[serde(default = "default_voice_id")]
|
|
pub voice_id: String,
|
|
/// Push-to-talk key label (informational only — the TUI uses Space).
|
|
#[serde(default = "default_ptt_key")]
|
|
pub push_to_talk_key: String,
|
|
}
|
|
|
|
impl Default for VoiceConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
stt_url: default_stt_url(),
|
|
tts_url: default_tts_url(),
|
|
voice_id: default_voice_id(),
|
|
push_to_talk_key: default_ptt_key(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn default_stt_url() -> String { "http://127.0.0.1:7862".to_string() }
|
|
fn default_tts_url() -> String { "http://127.0.0.1:7861".to_string() }
|
|
fn default_voice_id() -> String { "en-Soother_woman".to_string() }
|
|
fn default_ptt_key() -> String { "Space".to_string() }
|
|
|
|
impl ConsciousnessConfig {
|
|
/// Walk the standard config paths and return the first existing one.
|
|
pub fn discover_path() -> Option<PathBuf> {
|
|
let candidates = [
|
|
"souveraine.toml",
|
|
"souveraine.yaml",
|
|
"~/.souveraine/config.toml",
|
|
"~/.souveraine/config.yaml",
|
|
];
|
|
for path_str in &candidates {
|
|
let expanded = shellexpand::tilde(path_str);
|
|
let path = PathBuf::from(expanded.as_ref());
|
|
if path.exists() {
|
|
return Some(path);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn load(path: &PathBuf) -> anyhow::Result<Self> {
|
|
let content = std::fs::read_to_string(path)?;
|
|
let config: Self = if path.extension().map(|e| e == "toml").unwrap_or(false) {
|
|
toml::from_str(&content)?
|
|
} else {
|
|
serde_yaml::from_str(&content)?
|
|
};
|
|
Ok(config)
|
|
}
|
|
|
|
pub fn save(&self, path: &PathBuf) -> anyhow::Result<()> {
|
|
let content = if path.extension().map(|e| e == "toml").unwrap_or(false) {
|
|
toml::to_string_pretty(self)?
|
|
} else {
|
|
serde_yaml::to_string(self)?
|
|
};
|
|
std::fs::write(path, content)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// ── Default helper fns ──
|
|
|
|
fn default_true() -> bool { true }
|
|
fn default_supports_images() -> bool { true }
|
|
fn default_3() -> usize { 3 }
|
|
fn default_25() -> usize { 25 }
|
|
fn default_3u32() -> u32 { 3 }
|
|
fn default_25u32() -> u32 { 50 } // default subagent max tool rounds
|
|
fn default_100() -> usize { 100 }
|
|
fn default_300() -> u64 { 300 }
|
|
fn default_7373() -> u16 { 7373 }
|
|
fn default_128k() -> usize { 128000 }
|
|
fn default_8k() -> usize { 8192 }
|
|
fn default_threshold_70() -> f32 { 0.7 }
|
|
fn default_subconscious_max_tokens() -> u32 { 8192 }
|
|
fn default_warning_1_threshold() -> f32 { 0.8 }
|
|
fn default_warning_2_threshold() -> f32 { 0.95 }
|
|
fn default_sub_inter_round_delay() -> u64 { 300 }
|
|
fn default_auto_model() -> String { "auto".to_string() }
|
|
fn default_bifrost_url() -> String { "http://127.0.0.1:3360".to_string() }
|
|
fn default_provider() -> String { "bifrost".to_string() }
|
|
fn default_server_bind() -> String { "127.0.0.1".to_string() }
|
|
fn default_server_port() -> u16 { 8484 }
|
|
fn default_server_url() -> String { "http://127.0.0.1:8484".to_string() }
|
|
fn default_bifrost_key() -> String {
|
|
crate::core::credentials::get_bifrost_key()
|
|
}
|
|
|
|
fn default_bifrost_virtual_key() -> String {
|
|
std::env::var("BIFROST_VIRTUAL_KEY").unwrap_or_else(|_| String::new())
|
|
}
|
|
fn default_primary_model() -> String { String::new() }
|
|
fn default_bifrost_timeout() -> u64 { 120 }
|
|
fn default_bandwidth_high() -> BandwidthClass { BandwidthClass::High }
|
|
fn default_presence_breathing() -> String { "breathing_color".to_string() }
|
|
|
|
fn default_synthesis_elements() -> Vec<SynthesisElement> {
|
|
vec![SynthesisElement::Themes, SynthesisElement::Emotions, SynthesisElement::Tensions, SynthesisElement::Anchors, SynthesisElement::Evolution]
|
|
}
|
|
|
|
fn default_models() -> HashMap<String, ModelConfig> {
|
|
HashMap::new()
|
|
}
|