Skip to main content

gglib_core/
settings_update.rs

1//! The two types that sit beside [`Settings`](super::Settings): a partial
2//! update, and what validation refuses.
3//!
4//! A `#[path]` sibling of `settings.rs` rather than a section of it. The
5//! struct and the enum are each a flat list that grows by one line per
6//! setting, and `settings.rs` is at its size baseline with `Settings` itself
7//! still to grow; splitting the lists out is the only part of that file that
8//! can move without taking the `Settings` struct the surface checks parse out
9//! of the file they parse.
10
11use serde::{Deserialize, Serialize};
12
13use crate::domain::{InferenceConfig, InferenceProfile};
14
15use super::{Device, LoopGuardMode, RemotePairing, RemoteServe};
16
17/// Partial settings update.
18///
19/// Each field is `Option<Option<T>>`:
20/// - `None` = don't change this field
21/// - `Some(None)` = set field to None/null
22/// - `Some(Some(value))` = set field to value
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
24pub struct SettingsUpdate {
25    pub default_download_path: Option<Option<String>>,
26    pub default_context_size: Option<Option<u64>>,
27    pub proxy_port: Option<Option<u16>>,
28    pub llama_base_port: Option<Option<u16>>,
29    pub max_download_queue_size: Option<Option<u32>>,
30    pub show_memory_fit_indicators: Option<Option<bool>>,
31    pub max_tool_iterations: Option<Option<u32>>,
32    pub max_stagnation_steps: Option<Option<u32>>,
33    pub default_model_id: Option<Option<i64>>,
34    pub inference_defaults: Option<Option<InferenceConfig>>,
35    pub inference_profiles: Option<Option<Vec<InferenceProfile>>>,
36    pub setup_completed: Option<Option<bool>>,
37    pub title_generation_prompt: Option<Option<String>>,
38    pub bind_host: Option<Option<String>>,
39    pub share_lan: Option<Option<bool>>,
40    pub proxy_api_key: Option<Option<String>>,
41    pub trust_client_sampling: Option<Option<bool>>,
42    /// See [`Settings::loop_guard_mode`](super::Settings::loop_guard_mode).
43    /// Writing it **to a value** clears [`Self::proxy_loop_detection`], and
44    /// the other way round; `Some(None)` — the explicit clear — clears only
45    /// itself, because "forget this field" is not "forget both".
46    pub loop_guard_mode: Option<Option<LoopGuardMode>>,
47    /// **Deprecated**; see
48    /// [`Settings::proxy_loop_detection`](super::Settings::proxy_loop_detection).
49    pub proxy_loop_detection: Option<Option<bool>>,
50    pub tool_call_repair: Option<Option<bool>>,
51    /// See [`Settings::agentic_sampling`](super::Settings::agentic_sampling).
52    pub agentic_sampling: Option<Option<bool>>,
53    pub proxy_autostart: Option<Option<bool>>,
54    pub close_to_tray: Option<Option<bool>>,
55    pub start_at_login: Option<Option<bool>>,
56    /// See [`Settings::remote_pairing`](super::Settings::remote_pairing). Written whole or not at all: the
57    /// two halves have no separate update, which is what keeps them bound.
58    pub remote_pairing: Option<Option<RemotePairing>>,
59    /// See [`Settings::remote_enabled`](super::Settings::remote_enabled).
60    pub remote_enabled: Option<Option<bool>>,
61    /// See [`Settings::remote_serve`](super::Settings::remote_serve). Written whole, like the pairing.
62    pub remote_serve: Option<Option<RemoteServe>>,
63    /// See [`Settings::remote_devices`](super::Settings::remote_devices). Written whole.
64    pub remote_devices: Option<Option<Vec<Device>>>,
65}
66
67/// Settings validation error.
68#[derive(Debug, Clone, thiserror::Error)]
69pub enum SettingsError {
70    #[error("Context size must be between 512 and 1,000,000, got {0}")]
71    InvalidContextSize(u64),
72
73    #[error("Port should be >= 1024 (privileged ports require root), got {0}")]
74    InvalidPort(u16),
75
76    #[error("Max download queue size must be between 1 and 50, got {0}")]
77    InvalidQueueSize(u32),
78
79    #[error("Download path cannot be empty")]
80    EmptyDownloadPath,
81
82    #[error("Invalid inference parameter: {0}")]
83    InvalidInferenceConfig(String),
84
85    #[error("Invalid inference profile: {0}")]
86    InvalidInferenceProfile(String),
87
88    #[error("Bind host must be an IP address (e.g. 127.0.0.1 or 0.0.0.0), got '{0}'")]
89    InvalidBindHost(String),
90
91    #[error("Proxy API key cannot be blank — clear it instead to disable authentication")]
92    BlankProxyApiKey,
93
94    #[error("Remote API key cannot be blank — clear the pairing instead to forget it")]
95    BlankRemoteApiKey,
96
97    #[error("Remote ticket cannot be blank — clear the pairing instead to forget it")]
98    BlankRemoteTicket,
99
100    /// An id the tunnel edge would refuse to hold a token under.
101    #[error("Device id {0:?} must be 1-64 of ASCII letters, digits, '.', '_' or '-'")]
102    InvalidDeviceId(String),
103}