use serde_json::{Value, json};
pub(crate) const MCP_TEXT_ARG_CHAR_LIMIT: usize = 16 * 1024;
pub(crate) fn validate_mcp_text_arg(
name: &str,
value: &str,
limit: usize,
) -> Result<(), (i32, String)> {
if value.chars().count() > limit {
return Err((-32602, format!("{name} must be {limit} chars or fewer")));
}
Ok(())
}
pub(crate) fn rule_injection_disabled() -> Option<&'static str> {
if is_disable_rules_env_set() {
return Some("rule injection disabled via DIFFLORE_DISABLE_RULES");
}
if haiku_auto_disable_active() {
return Some(
"rule injection auto-disabled on haiku (override: DIFFLORE_FORCE_RULES_ON_HAIKU=1)",
);
}
None
}
fn is_disable_rules_env_set() -> bool {
crate::infra::env::truthy(crate::infra::env::DIFFLORE_DISABLE_RULES)
}
pub fn detect_active_model() -> Option<String> {
for key in ["DIFFLORE_AGENT_MODEL", "ANTHROPIC_MODEL", "CLAUDE_MODEL"] {
if let Some(v) = crate::infra::env::var(key) {
let trimmed = v.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_ascii_lowercase());
}
}
}
None
}
pub fn is_haiku_model(model: &str) -> bool {
model.to_ascii_lowercase().contains("haiku")
}
pub fn haiku_auto_disable_active() -> bool {
let Some(model) = detect_active_model() else {
return false;
};
if !is_haiku_model(&model) {
return false;
}
!crate::infra::env::truthy(crate::infra::env::DIFFLORE_FORCE_RULES_ON_HAIKU)
}
pub(crate) fn disabled_response(reason: &str) -> Value {
json!({
"content": [{
"type": "text",
"text": format!(
"DiffLore: {reason}.\n\n\
No rules surfaced. Unset DIFFLORE_DISABLE_RULES to re-enable. \
Rule injection is off by default on haiku-class models. \
Sonnet+ users should leave the var unset."
)
}],
"_meta": {
"impact": { "rulesInjected": 0, "kind": "rules", "disabled": true },
"embedding": {
"activeProfile": null,
"indexProfile": null,
"profileMatch": false,
"degraded": false,
"degradedReason": "rules_disabled",
"vectorLaneAvailable": false
}
}
})
}