1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum HarnessKind {
6 Generic,
8 ClaudeCode,
10 OpenCode,
12 Codex,
14 GitHubCopilot,
16 Pi,
18}
19
20impl HarnessKind {
21 pub fn canonical_name(self) -> &'static str {
23 match self {
24 Self::Generic => "generic",
25 Self::ClaudeCode => "claude-code",
26 Self::OpenCode => "opencode",
27 Self::Codex => "codex",
28 Self::GitHubCopilot => "github-copilot",
29 Self::Pi => "pi",
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct HarnessSessionEnv {
37 pub env_var: &'static str,
39 pub session_id: String,
41 pub harness: HarnessKind,
43}
44
45#[derive(Debug, Clone, Copy)]
46struct HarnessSessionProbe {
47 env_var: &'static str,
48 harness: HarnessKind,
49}
50
51const HARNESS_SESSION_PROBES: &[HarnessSessionProbe] = &[
52 HarnessSessionProbe {
53 env_var: "ITO_HARNESS_SESSION_ID",
54 harness: HarnessKind::Generic,
55 },
56 HarnessSessionProbe {
57 env_var: "CLAUDE_SESSION_ID",
58 harness: HarnessKind::ClaudeCode,
59 },
60 HarnessSessionProbe {
61 env_var: "OPENCODE_SESSION_ID",
62 harness: HarnessKind::OpenCode,
63 },
64 HarnessSessionProbe {
65 env_var: "CODEX_SESSION_ID",
66 harness: HarnessKind::Codex,
67 },
68 HarnessSessionProbe {
69 env_var: "GITHUB_COPILOT_CHAT_SESSION_ID",
70 harness: HarnessKind::GitHubCopilot,
71 },
72 HarnessSessionProbe {
73 env_var: "PI_SESSION_ID",
74 harness: HarnessKind::Pi,
75 },
76];
77
78pub const HARNESS_SESSION_ENV_VARS: &[&str] = &[
80 "ITO_HARNESS_SESSION_ID",
81 "CLAUDE_SESSION_ID",
82 "OPENCODE_SESSION_ID",
83 "CODEX_SESSION_ID",
84 "GITHUB_COPILOT_CHAT_SESSION_ID",
85 "PI_SESSION_ID",
86];
87
88pub fn resolve_harness_session_env() -> Option<HarnessSessionEnv> {
90 resolve_harness_session_env_with(|env_var| std::env::var(env_var).ok())
91}
92
93fn resolve_harness_session_env_with(
94 mut get_env: impl FnMut(&str) -> Option<String>,
95) -> Option<HarnessSessionEnv> {
96 for HarnessSessionProbe { env_var, harness } in HARNESS_SESSION_PROBES {
97 let Some(session_id) = get_env(env_var) else {
98 continue;
99 };
100 let session_id = session_id.trim();
101 if session_id.is_empty() {
102 continue;
103 }
104 let session_id = session_id.to_owned();
105 return Some(HarnessSessionEnv {
106 env_var,
107 session_id,
108 harness: *harness,
109 });
110 }
111
112 None
113}
114
115pub fn resolve_harness_session_id() -> Option<String> {
117 resolve_harness_session_env().map(|env| env.session_id)
118}
119
120pub fn detect_harness_name() -> &'static str {
122 resolve_harness_session_env()
123 .map(|env| env.harness.canonical_name())
124 .unwrap_or("unknown")
125}
126
127#[cfg(test)]
128#[path = "harness_tests.rs"]
129mod harness_tests;