Skip to main content

systemprompt_cli/commands/admin/config/rate_limits/
diff.rs

1//! `admin config rate-limits diff` command against defaults.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use anyhow::{Context, Result, bail};
7use std::fs;
8use std::path::Path;
9use systemprompt_config::ProfileBootstrap;
10use systemprompt_logging::CliService;
11use systemprompt_models::profile::{RateLimitsConfig, TierMultipliers};
12
13use super::DiffArgs;
14use crate::CliConfig;
15use crate::cli_settings::OutputFormat;
16use crate::shared::{CommandOutput, render_result};
17
18use super::super::types::{DiffEntry, DiffOutput};
19
20pub(super) fn execute_diff(args: &DiffArgs, config: &CliConfig) -> Result<()> {
21    let profile = ProfileBootstrap::get()?;
22    let current = &profile.rate_limits;
23
24    let (compare_with, source) = if args.defaults {
25        (RateLimitsConfig::default(), "defaults".to_owned())
26    } else if let Some(file_path) = &args.file {
27        let content = fs::read_to_string(file_path)
28            .with_context(|| format!("Failed to read file: {}", file_path))?;
29
30        let is_json = Path::new(file_path)
31            .extension()
32            .is_some_and(|ext| ext.eq_ignore_ascii_case("json"));
33
34        let limits: RateLimitsConfig = if is_json {
35            serde_json::from_str(&content)
36                .with_context(|| format!("Failed to parse JSON from: {}", file_path))?
37        } else {
38            serde_yaml::from_str(&content)
39                .with_context(|| format!("Failed to parse YAML from: {}", file_path))?
40        };
41
42        (limits, file_path.clone())
43    } else {
44        bail!("Must specify --defaults or --file");
45    };
46
47    let differences = collect_differences(current, &compare_with);
48    let count = differences.len();
49
50    let output = DiffOutput {
51        source,
52        identical: differences.is_empty(),
53        differences,
54    };
55
56    render_result(
57        &CommandOutput::table_of(vec!["field", "current", "other"], &output.differences)
58            .with_title("Rate Limits Diff"),
59        config,
60    );
61
62    if config.output_format() == OutputFormat::Table {
63        if count == 0 {
64            CliService::success("No differences found");
65        } else {
66            CliService::info(&format!("{count} difference(s) found"));
67        }
68    }
69
70    Ok(())
71}
72
73pub fn collect_differences(
74    current: &RateLimitsConfig,
75    compare_with: &RateLimitsConfig,
76) -> Vec<DiffEntry> {
77    let mut differences: Vec<DiffEntry> = Vec::new();
78
79    add_diff_if_different(
80        &mut differences,
81        "disabled",
82        &current.disabled,
83        &compare_with.disabled,
84    );
85    collect_rate_differences(current, compare_with, &mut differences);
86    add_diff_if_different(
87        &mut differences,
88        "burst_multiplier",
89        &current.burst_multiplier,
90        &compare_with.burst_multiplier,
91    );
92    collect_tier_differences(
93        &current.tier_multipliers,
94        &compare_with.tier_multipliers,
95        &mut differences,
96    );
97
98    differences
99}
100
101fn collect_rate_differences(
102    current: &RateLimitsConfig,
103    compare_with: &RateLimitsConfig,
104    diffs: &mut Vec<DiffEntry>,
105) {
106    let rates = [
107        (
108            "oauth_public_per_second",
109            current.oauth_public_per_second,
110            compare_with.oauth_public_per_second,
111        ),
112        (
113            "oauth_auth_per_second",
114            current.oauth_auth_per_second,
115            compare_with.oauth_auth_per_second,
116        ),
117        (
118            "contexts_per_second",
119            current.contexts_per_second,
120            compare_with.contexts_per_second,
121        ),
122        (
123            "tasks_per_second",
124            current.tasks_per_second,
125            compare_with.tasks_per_second,
126        ),
127        (
128            "artifacts_per_second",
129            current.artifacts_per_second,
130            compare_with.artifacts_per_second,
131        ),
132        (
133            "agent_registry_per_second",
134            current.agent_registry_per_second,
135            compare_with.agent_registry_per_second,
136        ),
137        (
138            "agents_per_second",
139            current.agents_per_second,
140            compare_with.agents_per_second,
141        ),
142        (
143            "mcp_registry_per_second",
144            current.mcp_registry_per_second,
145            compare_with.mcp_registry_per_second,
146        ),
147        (
148            "mcp_per_second",
149            current.mcp_per_second,
150            compare_with.mcp_per_second,
151        ),
152        (
153            "stream_per_second",
154            current.stream_per_second,
155            compare_with.stream_per_second,
156        ),
157        (
158            "content_per_second",
159            current.content_per_second,
160            compare_with.content_per_second,
161        ),
162    ];
163
164    for (field, current_val, other_val) in rates {
165        add_diff_if_different(diffs, field, &current_val, &other_val);
166    }
167}
168
169fn collect_tier_differences(
170    current: &TierMultipliers,
171    compare_with: &TierMultipliers,
172    diffs: &mut Vec<DiffEntry>,
173) {
174    let tiers = [
175        ("tier_multipliers.admin", current.admin, compare_with.admin),
176        ("tier_multipliers.user", current.user, compare_with.user),
177        ("tier_multipliers.a2a", current.a2a, compare_with.a2a),
178        ("tier_multipliers.mcp", current.mcp, compare_with.mcp),
179        (
180            "tier_multipliers.service",
181            current.service,
182            compare_with.service,
183        ),
184        ("tier_multipliers.anon", current.anon, compare_with.anon),
185    ];
186
187    for (field, current_val, other_val) in tiers {
188        add_diff_if_different_f64(diffs, field, current_val, other_val);
189    }
190}
191
192fn add_diff_if_different<T: std::fmt::Display + PartialEq>(
193    diffs: &mut Vec<DiffEntry>,
194    field: &str,
195    current: &T,
196    compare: &T,
197) {
198    if current != compare {
199        diffs.push(DiffEntry {
200            field: field.to_owned(),
201            current: current.to_string(),
202            other: compare.to_string(),
203        });
204    }
205}
206
207fn add_diff_if_different_f64(diffs: &mut Vec<DiffEntry>, field: &str, current: f64, compare: f64) {
208    if (current - compare).abs() > f64::EPSILON {
209        diffs.push(DiffEntry {
210            field: field.to_owned(),
211            current: format!("{:.1}", current),
212            other: format!("{:.1}", compare),
213        });
214    }
215}