use crate::config::CliRunnerType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SystemDelivery {
SeparateChannel,
InlineInPrompt,
}
impl CliRunnerType {
#[must_use]
pub const fn system_delivery(self) -> SystemDelivery {
match self {
Self::ClaudeCode => SystemDelivery::SeparateChannel,
Self::CodexCli
| Self::GooseCli
| Self::CursorAgent
| Self::GeminiCli
| Self::ClineCli
| Self::ContinueCli
| Self::KiroCli => SystemDelivery::InlineInPrompt,
Self::Copilot | Self::OpenCode | Self::WarpCli | Self::KiloCli => {
SystemDelivery::InlineInPrompt
}
#[cfg(feature = "copilot-headless")]
Self::CopilotHeadless => SystemDelivery::InlineInPrompt,
#[cfg(feature = "web-ui")]
Self::ClaudeWeb => SystemDelivery::InlineInPrompt,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prompt::{build_prompt, build_user_prompt};
use crate::types::ChatMessage;
const SYSTEM_TEXT: &str = "You are Dravr, the athlete's coach, and nothing else.";
fn messages() -> Vec<ChatMessage> {
vec![
ChatMessage::system(SYSTEM_TEXT),
ChatMessage::user("what should I ride this weekend?"),
]
}
#[test]
fn inlining_runners_carry_the_system_text_in_the_prompt() {
let prompt = build_prompt(&messages());
assert!(
prompt.contains(SYSTEM_TEXT),
"build_prompt must inline the System message, got: {prompt}"
);
}
#[test]
fn the_excluding_builder_really_drops_the_system_text() {
let prompt = build_user_prompt(&messages());
assert!(
!prompt.contains(SYSTEM_TEXT),
"build_user_prompt is only safe for runners with a separate channel"
);
}
#[test]
fn every_runner_declares_a_delivery_mode() {
for runner in [
CliRunnerType::ClaudeCode,
CliRunnerType::CursorAgent,
CliRunnerType::OpenCode,
CliRunnerType::Copilot,
CliRunnerType::GeminiCli,
CliRunnerType::CodexCli,
CliRunnerType::GooseCli,
CliRunnerType::ClineCli,
CliRunnerType::ContinueCli,
CliRunnerType::WarpCli,
CliRunnerType::KiroCli,
CliRunnerType::KiloCli,
#[cfg(feature = "copilot-headless")]
CliRunnerType::CopilotHeadless,
#[cfg(feature = "web-ui")]
CliRunnerType::ClaudeWeb,
] {
let _ = runner.system_delivery();
}
}
#[test]
fn only_claude_code_uses_a_separate_channel() {
let separate: Vec<CliRunnerType> = [
CliRunnerType::ClaudeCode,
CliRunnerType::CursorAgent,
CliRunnerType::OpenCode,
CliRunnerType::Copilot,
CliRunnerType::GeminiCli,
CliRunnerType::CodexCli,
CliRunnerType::GooseCli,
CliRunnerType::ClineCli,
CliRunnerType::ContinueCli,
CliRunnerType::WarpCli,
CliRunnerType::KiroCli,
CliRunnerType::KiloCli,
#[cfg(feature = "copilot-headless")]
CliRunnerType::CopilotHeadless,
#[cfg(feature = "web-ui")]
CliRunnerType::ClaudeWeb,
]
.into_iter()
.filter(|r| r.system_delivery() == SystemDelivery::SeparateChannel)
.collect();
assert_eq!(
separate,
vec![CliRunnerType::ClaudeCode],
"a runner may only exclude the System message if it has a dedicated \
channel to deliver it through; add the justification in \
system_delivery() before changing this"
);
}
}