#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HarnessKind {
Generic,
ClaudeCode,
OpenCode,
Codex,
GitHubCopilot,
Pi,
}
impl HarnessKind {
pub fn canonical_name(self) -> &'static str {
match self {
Self::Generic => "generic",
Self::ClaudeCode => "claude-code",
Self::OpenCode => "opencode",
Self::Codex => "codex",
Self::GitHubCopilot => "github-copilot",
Self::Pi => "pi",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HarnessSessionEnv {
pub env_var: &'static str,
pub session_id: String,
pub harness: HarnessKind,
}
#[derive(Debug, Clone, Copy)]
struct HarnessSessionProbe {
env_var: &'static str,
harness: HarnessKind,
}
const HARNESS_SESSION_PROBES: &[HarnessSessionProbe] = &[
HarnessSessionProbe {
env_var: "ITO_HARNESS_SESSION_ID",
harness: HarnessKind::Generic,
},
HarnessSessionProbe {
env_var: "CLAUDE_SESSION_ID",
harness: HarnessKind::ClaudeCode,
},
HarnessSessionProbe {
env_var: "OPENCODE_SESSION_ID",
harness: HarnessKind::OpenCode,
},
HarnessSessionProbe {
env_var: "CODEX_SESSION_ID",
harness: HarnessKind::Codex,
},
HarnessSessionProbe {
env_var: "GITHUB_COPILOT_CHAT_SESSION_ID",
harness: HarnessKind::GitHubCopilot,
},
HarnessSessionProbe {
env_var: "PI_SESSION_ID",
harness: HarnessKind::Pi,
},
];
pub const HARNESS_SESSION_ENV_VARS: &[&str] = &[
"ITO_HARNESS_SESSION_ID",
"CLAUDE_SESSION_ID",
"OPENCODE_SESSION_ID",
"CODEX_SESSION_ID",
"GITHUB_COPILOT_CHAT_SESSION_ID",
"PI_SESSION_ID",
];
pub fn resolve_harness_session_env() -> Option<HarnessSessionEnv> {
resolve_harness_session_env_with(|env_var| std::env::var(env_var).ok())
}
fn resolve_harness_session_env_with(
mut get_env: impl FnMut(&str) -> Option<String>,
) -> Option<HarnessSessionEnv> {
for HarnessSessionProbe { env_var, harness } in HARNESS_SESSION_PROBES {
let Some(session_id) = get_env(env_var) else {
continue;
};
let session_id = session_id.trim();
if session_id.is_empty() {
continue;
}
let session_id = session_id.to_owned();
return Some(HarnessSessionEnv {
env_var,
session_id,
harness: *harness,
});
}
None
}
pub fn resolve_harness_session_id() -> Option<String> {
resolve_harness_session_env().map(|env| env.session_id)
}
pub fn detect_harness_name() -> &'static str {
resolve_harness_session_env()
.map(|env| env.harness.canonical_name())
.unwrap_or("unknown")
}
#[cfg(test)]
#[path = "harness_tests.rs"]
mod harness_tests;