Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 2 additions & 64 deletions apps/daemon/src/agents/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,68 +199,6 @@ pub fn select_provider(ctx: &SelectionContext) -> Provider {

// ─── Tests ────────────────────────────────────────────────────────────────────

// Tests live in capabilities/tests.rs.
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn reviewer_cross_model_claude_to_codex() {
let ctx = SelectionContext {
role: "reviewer".to_string(),
complexity: "medium".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: Some(Provider::Claude),
};
assert_eq!(select_provider(&ctx), Provider::Codex);
}

#[test]
fn reviewer_cross_model_codex_to_claude() {
let ctx = SelectionContext {
role: "reviewer".to_string(),
complexity: "medium".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: Some(Provider::Codex),
};
assert_eq!(select_provider(&ctx), Provider::Claude);
}

#[test]
fn implementer_always_claude() {
let ctx = SelectionContext {
role: "implementer".to_string(),
complexity: "high".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: None,
};
assert_eq!(select_provider(&ctx), Provider::Claude);
}

#[test]
fn qa_prefers_codex() {
let ctx = SelectionContext {
role: "qa".to_string(),
complexity: "low".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: None,
};
assert_eq!(select_provider(&ctx), Provider::Codex);
}

#[test]
fn falls_back_when_preferred_unavailable() {
let ctx = SelectionContext {
role: "implementer".to_string(),
complexity: "medium".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Codex],
previous_provider: None,
};
// Claude is preferred but not available — should return Codex.
assert_eq!(select_provider(&ctx), Provider::Codex);
}
}
mod tests;
194 changes: 194 additions & 0 deletions apps/daemon/src/agents/capabilities/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
//! Mutation-targeted tests for provider capabilities and role routing.
//!
//! Written against the gate's surviving-mutant list for `agents/capabilities.rs`
//! (run 53a9d6d4): 12 missed. Ten of them are killable and covered here; the
//! other two are equivalent and are documented at the bottom rather than
//! chased.
//!
//! The existing tests in this module assert which `Provider` comes back. That
//! leaves the cost fields untouched, which is where eight of the twelve
//! mutants live: `3.0 / 1000.0` mutated to `3.0 % 1000.0` (= 3.0) or
//! `3.0 * 1000.0` (= 3000.0). Nothing asserted those numbers at all.

use super::*;

// ─── cost arithmetic ────────────────────────────────────────────────────────

#[test]
fn claude_costs_are_per_token_not_per_thousand() {
let c = ProviderCapabilities::claude();
// 3.0 / 1000.0. The `%` mutant yields 3.0 and the `*` mutant 3000.0, so an
// exact assertion kills both.
assert!(
(c.cost_per_1k_tokens_in - 0.003).abs() < 1e-9,
"input cost: got {}",
c.cost_per_1k_tokens_in
);
assert!(
(c.cost_per_1k_tokens_out - 0.015).abs() < 1e-9,
"output cost: got {}",
c.cost_per_1k_tokens_out
);
// Output must cost more than input — a sanity relation that also dies if
// either division is mangled independently.
assert!(c.cost_per_1k_tokens_out > c.cost_per_1k_tokens_in);
}

#[test]
fn codex_costs_are_per_token_not_per_thousand() {
let c = ProviderCapabilities::codex();
assert!(
(c.cost_per_1k_tokens_in - 0.0015).abs() < 1e-9,
"input cost: got {}",
c.cost_per_1k_tokens_in
);
assert!(
(c.cost_per_1k_tokens_out - 0.006).abs() < 1e-9,
"output cost: got {}",
c.cost_per_1k_tokens_out
);
assert!(c.cost_per_1k_tokens_out > c.cost_per_1k_tokens_in);
}

#[test]
fn codex_is_the_cheaper_provider_on_both_directions() {
// A relation between the two constructors, so a mutant that mangles one
// side's division shows up even if its absolute value were plausible.
let claude = ProviderCapabilities::claude();
let codex = ProviderCapabilities::codex();
assert!(codex.cost_per_1k_tokens_in < claude.cost_per_1k_tokens_in);
assert!(codex.cost_per_1k_tokens_out < claude.cost_per_1k_tokens_out);
}

#[test]
fn context_windows_and_capability_flags_are_exact() {
let claude = ProviderCapabilities::claude();
assert_eq!(claude.max_context_tokens, 200_000);
assert!(
!claude.supports_sandbox,
"Claude Code has no built-in sandbox"
);
assert!(claude.supports_mcp);

let codex = ProviderCapabilities::codex();
assert_eq!(codex.max_context_tokens, 128_000);
assert!(
codex.supports_sandbox,
"Codex sandboxes network and filesystem"
);
assert!(codex.supports_mcp);
}

// ─── role routing ───────────────────────────────────────────────────────────

#[test]
fn router_and_reviewer_roles_go_to_codex() {
// These two are the only match arms whose result DIFFERS from the `_`
// default, so they are the only two arm-deletions that are observable.
// Complexity is ignored by both arms, so several values are checked.
for complexity in ["low", "high", "", "anything"] {
assert_eq!(
recommend_provider("router", complexity),
Provider::Codex,
"router/{complexity}"
);
assert_eq!(
recommend_provider("reviewer", complexity),
Provider::Codex,
"reviewer/{complexity}"
);
}
}

#[test]
fn qa_role_goes_to_codex() {
assert_eq!(recommend_provider("qa", "low"), Provider::Codex);
assert_eq!(recommend_provider("qa", "high"), Provider::Codex);
}

#[test]
fn planner_and_implementer_and_unknown_roles_go_to_claude() {
assert_eq!(recommend_provider("planner", "high"), Provider::Claude);
assert_eq!(recommend_provider("implementer", "low"), Provider::Claude);
assert_eq!(
recommend_provider("something-else", "low"),
Provider::Claude
);
// A planner at a complexity other than "high" falls to the default, which
// is also Claude — asserted so the arm ordering stays visible.
assert_eq!(recommend_provider("planner", "low"), Provider::Claude);
}

// NOTE on the two EQUIVALENT mutants in recommend_provider.
//
// Deleting the ("planner", "high") arm, or the ("implementer", _) arm, changes
// nothing observable: both return Provider::Claude, and the `_` fallback they
// drop through to also returns Provider::Claude. No input can distinguish the
// mutant from the original.
//
// They are only killable if the default ever stops being Claude, at which point
// these arms start carrying real meaning. Recorded here so the file's ceiling
// is understood as 10 of 12 rather than treated as a gap.

// ─── pre-existing tests, kept verbatim ──────────────────────────────────────

#[test]
fn reviewer_cross_model_claude_to_codex() {
let ctx = SelectionContext {
role: "reviewer".to_string(),
complexity: "medium".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: Some(Provider::Claude),
};
assert_eq!(select_provider(&ctx), Provider::Codex);
}

#[test]
fn reviewer_cross_model_codex_to_claude() {
let ctx = SelectionContext {
role: "reviewer".to_string(),
complexity: "medium".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: Some(Provider::Codex),
};
assert_eq!(select_provider(&ctx), Provider::Claude);
}

#[test]
fn implementer_always_claude() {
let ctx = SelectionContext {
role: "implementer".to_string(),
complexity: "high".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: None,
};
assert_eq!(select_provider(&ctx), Provider::Claude);
}

#[test]
fn qa_prefers_codex() {
let ctx = SelectionContext {
role: "qa".to_string(),
complexity: "low".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Claude, Provider::Codex],
previous_provider: None,
};
assert_eq!(select_provider(&ctx), Provider::Codex);
}

#[test]
fn falls_back_when_preferred_unavailable() {
let ctx = SelectionContext {
role: "implementer".to_string(),
complexity: "medium".to_string(),
cost_budget_usd: None,
available_providers: vec![Provider::Codex],
previous_provider: None,
};
// Claude is preferred but not available — should return Codex.
assert_eq!(select_provider(&ctx), Provider::Codex);
}
118 changes: 2 additions & 116 deletions apps/daemon/src/intelligence/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,120 +160,6 @@ pub fn optimize_context(

// ─── Tests ────────────────────────────────────────────────────────────────────

// Tests live in context/tests.rs.
#[cfg(test)]
mod tests {
use super::*;

fn make_msg(role: &str, content: &str, pinned: bool) -> ContextMessage {
ContextMessage {
role: role.to_owned(),
content: content.to_owned(),
pinned,
}
}

#[test]
fn test_estimate_tokens_empty() {
assert_eq!(estimate_tokens(""), 0);
}

#[test]
fn test_estimate_tokens_four_chars() {
// 4 chars = 1 token
assert_eq!(estimate_tokens("abcd"), 1);
}

#[test]
fn test_estimate_tokens_five_chars() {
// 5 chars → ceil(5/4) = 2
assert_eq!(estimate_tokens("abcde"), 2);
}

#[test]
fn test_truncate_exact_fit() {
let s = "abcd"; // 1 token
assert_eq!(truncate_to_tokens(s, 1), s);
}

#[test]
fn test_truncate_over_limit() {
let s = "a".repeat(100);
let result = truncate_to_tokens(&s, 5); // 5 tokens = 20 chars
assert!(result.len() < s.len(), "should be shorter");
assert!(result.ends_with('…'), "should end with ellipsis");
}

#[test]
fn test_truncate_zero_limit() {
let result = truncate_to_tokens("hello", 0);
assert!(result.is_empty());
}

#[test]
fn test_optimize_keeps_all_within_budget() {
let messages = vec![
make_msg("system", "You are a helpful assistant.", false),
make_msg("user", "Hello!", false),
make_msg("assistant", "Hi there!", false),
];
let config = ContextConfig {
max_tokens: 10_000,
response_reserve_tokens: 500,
};
let result = optimize_context(&messages, &config);
assert_eq!(result.len(), 3, "all 3 messages should fit");
}

#[test]
fn test_optimize_drops_old_messages_first() {
// Create a tight budget so only system + last user message fit.
let system_content = "sys";
let old_user = "a".repeat(1000);
let new_user = "new question";

let messages = vec![
make_msg("system", system_content, false),
make_msg("user", &old_user, false),
make_msg("user", new_user, false),
];

// Budget: system (~1 tok) + new_user (~3 tok) + overhead = ~20.
// old_user (250 tok) should be dropped.
let config = ContextConfig {
max_tokens: 30,
response_reserve_tokens: 4,
};
let result = optimize_context(&messages, &config);

// System must be there.
assert!(result.iter().any(|m| m.role == "system"));
// New user message must be there.
assert!(result.iter().any(|m| m.content == new_user));
// Old (1000-char) message must be dropped.
assert!(!result.iter().any(|m| m.content == old_user));
}

#[test]
fn test_optimize_pinned_always_included() {
let pinned_msg = make_msg("user", "important pinned message", true);
let other = make_msg("user", "regular message", false);

let messages = vec![pinned_msg, other];
// Very tight budget — only the pinned message can fit.
let config = ContextConfig {
max_tokens: 10,
response_reserve_tokens: 0,
};
let result = optimize_context(&messages, &config);
assert!(
result.iter().any(|m| m.pinned),
"pinned message must survive budget cuts"
);
}

#[test]
fn test_optimize_empty_input() {
let result = optimize_context(&[], &ContextConfig::default());
assert!(result.is_empty());
}
}
mod tests;
Loading