-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfig.rs
More file actions
140 lines (124 loc) · 4.61 KB
/
Copy pathconfig.rs
File metadata and controls
140 lines (124 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Repo-scoped configurable limits for the evolution loop.
//!
//! Storage is repo-scoped — values live under `<config_dir>/.nixmac/settings.json`
//! so they ride along with the user's nix config repo across machines.
//!
//! The struct is managed as an `Observable<EvolutionLimits>` at startup. The
//! derive macro pushes its metadata into the compile-time `inventory`
//! collection so Developer settings can render and update it without opening
//! store files directly.
use anyhow::Result;
use configurable::Configurable;
use serde::{Deserialize, Serialize};
use specta::Type;
use std::sync::Arc;
use tauri::{AppHandle, Runtime};
use crate::observable::{ConfiguredRepoScopedJson, Observable, Persistence};
use crate::state::preferences;
pub const EVOLUTION_LIMITS_CHANGED_EVENT: &str = "evolution_limits_changed";
#[derive(Configurable, Debug, Clone, Serialize, Deserialize, Type, PartialEq, Eq)]
#[serde(rename_all = "camelCase", default)]
#[config(
scope = "repo",
display_name = "Evolution",
description = "How long the agent will try before giving up."
)]
pub struct EvolutionLimits {
#[config(
default = 25,
key = "maxIterations",
label = "Max iterations (legacy)",
range = 1..=200,
help = "Legacy iteration cap. Used only when the provider doesn't report token usage; the token budget is the primary stopping rule.",
)]
pub max_iterations: usize,
#[config(
default = 50_000,
key = "maxTokenBudget",
label = "Token budget",
range = 1_000..=1_000_000,
help = "Provider-reported tokens before stopping. Lower = faster/cheaper, may not finish complex changes.",
)]
pub max_token_budget: u32,
#[config(
default = 5,
key = "maxBuildAttempts",
label = "Max build attempts",
range = 1..=20,
help = "Failed builds before giving up on a run.",
)]
pub max_build_attempts: usize,
#[config(
default = 32_768,
key = "maxOutputTokens",
label = "Max output tokens",
range = 1_024..=262_144,
help = "Completion tokens requested from the evolution model. Lower if a local model rejects requests for exceeding its context window.",
)]
pub max_output_tokens: usize,
}
// Matches the `#[config(default = ...)]` values above. Reached during
// startup when the repo's settings.json is absent or unreadable (see
// `preferences::load_or_default`), and on the dev-settings whole-struct
// `set` path when an incoming JSON payload is missing fields. Deriving
// `Default` would produce zeros, which would be wrong here.
impl Default for EvolutionLimits {
fn default() -> Self {
Self {
max_iterations: 25,
max_token_budget: 50_000,
max_build_attempts: 5,
max_output_tokens: 32_768,
}
}
}
pub fn load_observable<R: Runtime>(app: &AppHandle<R>) -> Result<Observable<EvolutionLimits>> {
let persistence: Arc<dyn Persistence> = Arc::new(ConfiguredRepoScopedJson::new(app.clone()));
let initial = preferences::load_or_default::<EvolutionLimits>(persistence.as_ref())?;
Ok(Observable::new(initial)
.emit_to(app, EVOLUTION_LIMITS_CHANGED_EVENT)
.persist_to(persistence))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_matches_configured_field_defaults() {
let limits = EvolutionLimits::default();
assert_eq!(limits.max_iterations, 25);
assert_eq!(limits.max_token_budget, 50_000);
assert_eq!(limits.max_build_attempts, 5);
assert_eq!(limits.max_output_tokens, 32_768);
}
#[test]
fn unknown_fields_do_not_change_limits() {
let limits: EvolutionLimits = serde_json::from_value(serde_json::json!({
"maxIterations": 11,
"maxTokenBudget": 80_000,
"maxBuildAttempts": 3,
"maxOutputTokens": 16_384,
"developerMode": true
}))
.expect("limits deserialize");
assert_eq!(
limits,
EvolutionLimits {
max_iterations: 11,
max_token_budget: 80_000,
max_build_attempts: 3,
max_output_tokens: 16_384,
}
);
}
#[test]
fn missing_fields_use_defaults() {
let limits: EvolutionLimits = serde_json::from_value(serde_json::json!({
"maxIterations": 11,
}))
.expect("limits deserialize");
assert_eq!(limits.max_iterations, 11);
assert_eq!(limits.max_token_budget, 50_000);
assert_eq!(limits.max_build_attempts, 5);
assert_eq!(limits.max_output_tokens, 32_768);
}
}