use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use supercode_harness::configfile::{resolve, ResolveError, ResolveOptions};
use supercode_harness::model_catalog::Routing;
use supercode_harness::store::SessionStore;
use supercode_harness::tools::ToolRegistry;
use supercode_harness::{
Agent, AgentEvent, ChatMessage, ChatRequest, Config, Error, Provider, Usage,
};
fn temp_root(tag: &str) -> std::path::PathBuf {
let p = std::env::temp_dir().join(format!(
"supercode-bp13-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&p).unwrap();
p
}
fn parity_config(preset: &str, cwd: &std::path::Path, extra: &str) -> Config {
let mut resolved = resolve(
&format!("extends = \"{preset}\"\n{extra}"),
None,
&ResolveOptions { strict: true },
)
.unwrap_or_else(|e| panic!("preset `{preset}` failed to resolve: {e}"));
resolved.config.cwd = cwd.to_path_buf();
resolved.config
}
fn parity_routing(preset: &str) -> Routing {
resolve(
&format!("extends = \"{preset}\"\n"),
None,
&ResolveOptions { strict: true },
)
.unwrap()
.config
.model_routing
}
struct Recording {
seen: Arc<Mutex<Vec<ChatRequest>>>,
served: Option<String>,
}
impl Recording {
fn new() -> (Self, Arc<Mutex<Vec<ChatRequest>>>) {
let seen = Arc::new(Mutex::new(Vec::new()));
(
Recording {
seen: seen.clone(),
served: None,
},
seen,
)
}
fn serving(model: &str) -> (Self, Arc<Mutex<Vec<ChatRequest>>>) {
let (mut p, seen) = Recording::new();
p.served = Some(model.to_string());
(p, seen)
}
}
#[async_trait]
impl Provider for Recording {
async fn complete(
&self,
req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
self.seen.lock().unwrap().push(req.clone());
let mut reply = ChatMessage::assistant("done");
if let Some(served) = &self.served {
reply.metadata.insert(
supercode_harness::SERVED_MODEL_KEY.to_string(),
served.clone(),
);
}
Ok((reply, Usage::default()))
}
}
struct FailsFor {
failing: Vec<String>,
status: u16,
seen: Arc<Mutex<Vec<String>>>,
calls: AtomicUsize,
}
impl FailsFor {
fn new(failing: &[&str], status: u16) -> (Self, Arc<Mutex<Vec<String>>>) {
let seen = Arc::new(Mutex::new(Vec::new()));
(
FailsFor {
failing: failing.iter().map(|m| (*m).to_string()).collect(),
status,
seen: seen.clone(),
calls: AtomicUsize::new(0),
},
seen,
)
}
}
#[async_trait]
impl Provider for FailsFor {
async fn complete(
&self,
req: &ChatRequest,
_on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode_harness::Result<(ChatMessage, Usage)> {
self.calls.fetch_add(1, Ordering::SeqCst);
self.seen.lock().unwrap().push(req.model.clone());
if self.failing.iter().any(|m| *m == req.model) {
return Err(Error::Provider {
status: self.status,
body: "overloaded".into(),
});
}
Ok((ChatMessage::assistant("answered"), Usage::default()))
}
}
#[test]
fn cc_parity_resolves_account_provider_and_pattern_aliases() {
let routing = parity_routing("cc-parity");
assert_eq!(routing.provider.as_deref(), Some("anthropic"));
assert_eq!(routing.account.as_deref(), Some("max"));
assert_eq!(
routing.resolve_alias("default"),
"anthropic/claude-opus-4-8"
);
assert_eq!(routing.resolve_alias("best"), "anthropic/claude-opus-4-8");
assert_eq!(routing.resolve_alias("fast"), "anthropic/claude-haiku-4-5");
assert_eq!(
routing.resolve_alias("sonnet"),
"anthropic/claude-sonnet-4-6"
);
assert_eq!(
routing.resolve_alias("sonnet[1m]"),
"anthropic/claude-sonnet-4-6[1m]"
);
assert_eq!(
routing.resolve_alias("default[1m]"),
"anthropic/claude-opus-4-8[1m]"
);
assert_eq!(routing.resolve_alias("vendor/whatever"), "vendor/whatever");
let root = temp_root("aliases");
let config = parity_config(
"cc-parity",
&root,
r#"
[capabilities.model_catalog]
account = "pro"
[capabilities.model_catalog.providers.anthropic.accounts.pro.aliases]
default = "anthropic/claude-sonnet-4-6"
"#,
);
assert_eq!(
config.model_routing.resolve_alias("default"),
"anthropic/claude-sonnet-4-6"
);
}
#[tokio::test]
async fn a_config_alias_reaches_the_wire_under_cc_parity() {
let root = temp_root("alias-wire");
let config = parity_config("cc-parity", &root, "[core]\nmodel = \"default[1m]\"\n");
let (provider, seen) = Recording::new();
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("hi").await.unwrap();
assert_eq!(
seen.lock().unwrap()[0].model,
"anthropic/claude-opus-4-8[1m]"
);
}
#[tokio::test]
async fn both_presets_switch_the_model_mid_session_without_losing_the_session() {
for (preset, expects_notice) in [("cc-parity", false), ("cx-parity", true)] {
let root = temp_root("switch");
let store = SessionStore::open(root.join("sessions")).unwrap();
let name = "sess-switch";
let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let sink = events.clone();
let mut config = parity_config(preset, &root, "");
assert!(
config.model_switch_allow_switch,
"{preset} must set core.model_switch.allow_switch"
);
assert_eq!(
config.model_switch_notice, expects_notice,
"{preset}: switch-notice posture"
);
config.event_sink = Some(Box::new(move |event: AgentEvent| {
if let AgentEvent::ModelChanged { from, to, reason } = event {
sink.lock()
.unwrap()
.push(format!("{from}->{to} ({reason:?})"));
}
}));
let (provider, seen) = Recording::new();
let mut agent = Agent::with_provider(config, Box::new(provider));
supercode_harness::session_journal::arm(&mut agent, &store, name);
agent.send("first").await.unwrap();
let before_model = agent.model().to_string();
let before_len = agent.history().len();
let target = agent.config().model_routing.resolve_alias("haiku");
agent.switch_model(target.clone());
assert_eq!(agent.model(), "anthropic/claude-haiku-4-5");
assert_ne!(agent.model(), before_model);
assert!(
agent.history().len() >= before_len,
"{preset}: the switch must not shorten the conversation"
);
assert_eq!(
events.lock().unwrap().len(),
1,
"{preset}: no event emitted"
);
let records = agent.model_change_records();
assert_eq!(records.len(), 1, "{preset}");
assert_eq!(records[0].from_model, before_model);
assert_eq!(records[0].to_model, "anthropic/claude-haiku-4-5");
assert!(records[0].reasoning_filtered, "{preset}: dep 8 must run");
assert!(
!records[0].automatic(),
"{preset}: a user switch has no reason"
);
let journal = store.load_journal(name).unwrap().expect("journal");
assert_eq!(journal.model_changes.len(), 1, "{preset}: journal record");
assert_eq!(
journal.model_changes[0].to_model,
"anthropic/claude-haiku-4-5"
);
let notices = agent
.history()
.iter()
.filter(|m| {
m.content
.as_deref()
.is_some_and(|c| c.starts_with("[model changed:"))
})
.count();
assert_eq!(notices, usize::from(expects_notice), "{preset}: notice");
agent.send("second").await.unwrap();
let requests = seen.lock().unwrap();
assert_eq!(requests[0].model, before_model, "{preset}");
assert_eq!(requests[1].model, "anthropic/claude-haiku-4-5", "{preset}");
assert!(
requests[1].messages.len() > requests[0].messages.len(),
"{preset}: the session was lost across the switch"
);
}
}
#[tokio::test]
async fn effort_tiers_budgets_and_caps_reach_the_request_under_both_presets() {
for preset in ["cc-parity", "cx-parity"] {
let root = temp_root("effort");
let config = parity_config(
preset,
&root,
"[capabilities.model_catalog]\nthinking_budget = 12000\n",
);
assert_eq!(config.effort.as_deref(), Some("medium"), "{preset}");
let (provider, seen) = Recording::new();
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("hi").await.unwrap();
{
let requests = seen.lock().unwrap();
assert_eq!(requests[0].effort.as_deref(), Some("medium"), "{preset}");
assert_eq!(requests[0].thinking_budget, Some(12000), "{preset}");
}
agent.switch_model("anthropic/claude-haiku-4-5");
agent.send("again").await.unwrap();
let requests = seen.lock().unwrap();
let expected = if preset == "cc-parity" {
Some("low")
} else {
Some("medium")
};
assert_eq!(requests[1].effort.as_deref(), expected, "{preset}");
}
}
#[tokio::test]
async fn cc_parity_plan_mode_steers_the_effort_tier_while_it_is_active() {
let root = temp_root("plan-effort");
let config = parity_config("cc-parity", &root, "");
assert_eq!(config.plan_mode_effort.as_deref(), Some("high"));
let (provider, seen) = Recording::new();
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("before").await.unwrap();
agent.plan_mode().enter(Some("research first"));
agent.send("during").await.unwrap();
agent.plan_mode().exit();
agent.send("after").await.unwrap();
let requests = seen.lock().unwrap();
assert_eq!(requests[0].effort.as_deref(), Some("medium"));
assert_eq!(requests[1].effort.as_deref(), Some("high"));
assert_eq!(requests[2].effort.as_deref(), Some("medium"));
}
#[tokio::test]
async fn an_overloaded_model_walks_the_configured_chain_under_both_presets() {
for preset in ["cc-parity", "cx-parity"] {
let root = temp_root("fallback");
let store = SessionStore::open(root.join("sessions")).unwrap();
let name = "sess-fallback";
let config = parity_config(
preset,
&root,
"[capabilities.model_catalog]\nfallback = [\"sonnet\", \"haiku\"]\n",
);
let main = config.model.clone();
assert_eq!(
config.model_fallback,
vec![
"anthropic/claude-sonnet-4-6".to_string(),
"anthropic/claude-haiku-4-5".to_string(),
],
"{preset}: the chain resolves through the alias table"
);
let (provider, attempts) = FailsFor::new(&[&main, "anthropic/claude-sonnet-4-6"], 529);
let mut agent = Agent::with_provider(config, Box::new(provider));
supercode_harness::session_journal::arm(&mut agent, &store, name);
agent.send("hello").await.unwrap();
assert_eq!(
*attempts.lock().unwrap(),
vec![
main.clone(),
"anthropic/claude-sonnet-4-6".to_string(),
"anthropic/claude-haiku-4-5".to_string(),
],
"{preset}: the chain must be walked in order"
);
assert_eq!(agent.model(), "anthropic/claude-haiku-4-5", "{preset}");
let records = agent.model_change_records();
assert_eq!(records.len(), 2, "{preset}: one record per hop");
assert!(records[0].automatic(), "{preset}: a hop names its cause");
assert!(records[1]
.reason
.as_deref()
.is_some_and(|r| r.contains("529")));
let journal = store.load_journal(name).unwrap().expect("journal");
assert_eq!(journal.model_changes.len(), 2, "{preset}: persisted hops");
}
}
#[tokio::test]
async fn a_caller_error_never_walks_the_chain() {
let root = temp_root("no-fallback");
let config = parity_config(
"cc-parity",
&root,
"[capabilities.model_catalog]\nfallback = [\"sonnet\"]\n",
);
let main = config.model.clone();
let (provider, attempts) = FailsFor::new(&[&main], 400);
let mut agent = Agent::with_provider(config, Box::new(provider));
assert!(agent.send("hello").await.is_err());
assert_eq!(*attempts.lock().unwrap(), vec![main.clone()]);
assert_eq!(agent.model(), main);
assert!(agent.model_change_records().is_empty());
}
#[test]
fn cx_parity_swaps_the_write_surface_per_model() {
let root = temp_root("bits");
let base = parity_config("cx-parity", &root, "");
assert!(
!base.core_tools_enabled.iter().any(|t| t == "edit_file"),
"cx-parity's core tool list must not name edit_file"
);
let surface = |model: &str| {
let config = parity_config(
"cx-parity",
&root,
&format!("[core]\nmodel = \"{model}\"\n"),
);
let registry = ToolRegistry::from_config(&config);
(
registry.get("apply_patch").is_some(),
registry.get("edit_file").is_some(),
registry.get("write_file").is_some(),
)
};
assert_eq!(surface("openai/gpt-5.5"), (true, false, false));
assert_eq!(surface("openai/gpt-5-codex"), (true, false, false));
assert_eq!(surface("openai/gpt-4.1"), (false, true, true));
assert_eq!(surface("vendor/unknown"), (true, false, false));
}
#[tokio::test]
async fn the_service_tier_rides_the_request_and_the_fast_toggle_wins() {
for preset in ["cc-parity", "cx-parity"] {
let root = temp_root("tier");
let config = parity_config(preset, &root, "");
let (provider, seen) = Recording::new();
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("one").await.unwrap();
agent.set_service_tier(Some("priority".into()));
agent.send("two").await.unwrap();
agent.set_service_tier(None);
agent.send("three").await.unwrap();
let requests = seen.lock().unwrap();
assert_eq!(
requests[0].service_tier.as_deref(),
Some("auto"),
"{preset}"
);
assert_eq!(
requests[1].service_tier.as_deref(),
Some("priority"),
"{preset}"
);
assert_eq!(
requests[2].service_tier.as_deref(),
Some("auto"),
"{preset}"
);
}
}
#[test]
fn the_allowlist_refuses_and_the_effort_cap_clamps_over_cc_parity() {
let attempt = |extra: &str| {
resolve(
&format!("extends = \"cc-parity\"\n{extra}"),
None,
&ResolveOptions { strict: true },
)
};
let err = attempt(
r#"
[capabilities.model_catalog]
denied_models = ["anthropic/claude-opus-*"]
"#,
)
.expect_err("a denied model must refuse");
assert!(matches!(err, ResolveError::ModelNotAllowed(_)), "{err}");
let err = attempt(
r#"
[capabilities.model_catalog]
allowed_models = ["anthropic/claude-opus-4-8"]
"#,
)
.expect_err("the small model is outside the allowlist");
assert!(matches!(err, ResolveError::ModelNotAllowed(_)), "{err}");
let ok = attempt(
r#"
[capabilities.model_catalog]
allowed_models = ["anthropic/*"]
"#,
)
.expect("an allowed model resolves");
assert_eq!(ok.config.model, "anthropic/claude-opus-4-8");
let layered = supercode_harness::configfile::resolve(
r#"
extends = "cc-parity"
[capabilities.model_catalog]
denied_models = ["anthropic/claude-opus-*"]
"#,
Some(
r#"
[capabilities.model_catalog]
denied_models = []
[capabilities.model_catalog.aliases]
house = "anthropic/claude-sonnet-4-6"
"#,
),
&ResolveOptions { strict: false },
);
assert!(
matches!(layered, Err(ResolveError::ModelNotAllowed(_))),
"a project layer must not be able to lift the deny list"
);
let capped = attempt(
r#"
[core]
effort = "ultra"
[capabilities.model_catalog]
max_effort = "low"
"#,
)
.expect("a cap clamps rather than refusing");
assert!(
capped.warnings.iter().any(|w| w.contains("max_effort")),
"the clamp must be named: {:?}",
capped.warnings
);
let routing = capped.config.model_routing;
assert_eq!(
routing
.effective_effort("anthropic/claude-opus-4-8", Some("ultra"))
.as_deref(),
Some("low")
);
}
#[tokio::test]
async fn the_usage_record_shows_served_vs_requested_and_survives_the_process() {
for preset in ["cc-parity", "cx-parity"] {
let root = temp_root("provenance");
let store = SessionStore::open(root.join("sessions")).unwrap();
let name = "sess-provenance";
let config = parity_config(preset, &root, "");
let requested = config.model.clone();
let (provider, _seen) = Recording::serving("anthropic/claude-opus-4-8-20260101");
let mut agent = Agent::with_provider(config, Box::new(provider));
supercode_harness::session_journal::arm(&mut agent, &store, name);
agent.send("hi").await.unwrap();
let records = agent.usage_records();
assert_eq!(records.len(), 1, "{preset}");
assert_eq!(records[0].model, requested, "{preset}");
assert_eq!(
records[0].served_model.as_deref(),
Some("anthropic/claude-opus-4-8-20260101"),
"{preset}"
);
assert!(records[0].diverged(), "{preset}");
drop(agent);
let journal = store.load_journal(name).unwrap().expect("journal");
assert_eq!(journal.usage.len(), 1, "{preset}: persisted usage record");
assert_eq!(journal.usage[0].model, requested, "{preset}");
assert_eq!(
journal.usage[0].served_model.as_deref(),
Some("anthropic/claude-opus-4-8-20260101"),
"{preset}"
);
}
}
#[tokio::test]
async fn a_silent_provider_never_makes_the_record_claim_agreement() {
let root = temp_root("provenance-silent");
let config = parity_config("cc-parity", &root, "");
let (provider, _seen) = Recording::new();
let mut agent = Agent::with_provider(config, Box::new(provider));
agent.send("hi").await.unwrap();
let records = agent.usage_records();
assert_eq!(records[0].served_model, None);
assert!(!records[0].diverged());
}
#[test]
fn the_small_model_side_call_is_armed_under_both_presets() {
for preset in ["cc-parity", "cx-parity"] {
let root = temp_root("small-model");
let config = parity_config(preset, &root, "");
assert!(config.compaction_summarize, "{preset}");
assert!(config.compaction_enabled, "{preset}");
assert!(
config.compact_after_messages.is_some() || config.compaction_reserve_tokens.is_some(),
"{preset}: compaction must be able to fire at all"
);
let routed = config
.small_model
.clone()
.unwrap_or_else(|| config.model.clone());
if preset == "cc-parity" {
assert_eq!(routed, "anthropic/claude-haiku-4-5");
assert_ne!(
routed, config.model,
"cc-parity must route somewhere cheaper"
);
assert!(config.auto_title, "cc-parity must arm the titler");
} else {
assert_eq!(routed, config.model, "cx-parity is the D-9 fallback case");
assert!(
!config
.module_activation
.is_active(supercode_harness::modules::ModuleId::Memory),
"cx-parity keeps memories off"
);
}
}
}