1use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 AcpRuntimeBackend, ClaudeCodeRuntimeBackend, CodexRuntimeBackend, HarnessId,
14 OpenCodeRuntimeBackend, PiRuntimeBackend, RuntimeBackend, RuntimeCapabilities, RuntimeLaunch,
15};
16
17pub const SUPPORT_REGISTRY_SCHEMA: &str = "supercode.support-registry.v1";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum ImplementationKind {
24 BuiltIn,
26 GenericProtocol,
28 Absent,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct NativeSupport {
35 pub discover: ImplementationKind,
37 pub load: ImplementationKind,
39 pub follow: ImplementationKind,
41 pub import: ImplementationKind,
43 pub export: ImplementationKind,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct RuntimeSupport {
50 pub implementation: ImplementationKind,
52 pub protocol: String,
54 pub default_launch: Option<RuntimeLaunch>,
56 pub capabilities: RuntimeCapabilities,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct HarnessSupportDescriptor {
65 pub id: HarnessId,
67 pub display_name: String,
69 pub native: NativeSupport,
71 pub runtime: RuntimeSupport,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct SupportRegistryReport {
78 pub schema: String,
80 pub harnesses: Vec<HarnessSupportDescriptor>,
82}
83
84fn built_in_native() -> NativeSupport {
85 NativeSupport {
86 discover: ImplementationKind::BuiltIn,
87 load: ImplementationKind::BuiltIn,
88 follow: ImplementationKind::BuiltIn,
89 import: ImplementationKind::BuiltIn,
90 export: ImplementationKind::BuiltIn,
91 }
92}
93
94fn built_in_runtime(
95 backend: &dyn RuntimeBackend,
96 protocol: &str,
97 launch: RuntimeLaunch,
98) -> RuntimeSupport {
99 RuntimeSupport {
100 implementation: ImplementationKind::BuiltIn,
101 protocol: protocol.into(),
102 default_launch: Some(launch),
103 capabilities: backend.capabilities(),
104 }
105}
106
107pub fn harness_support_registry() -> SupportRegistryReport {
109 let claude = ClaudeCodeRuntimeBackend::new();
110 let codex = CodexRuntimeBackend::new();
111 let opencode = OpenCodeRuntimeBackend::new();
112 let pi = PiRuntimeBackend::new();
113 let grok_launch = RuntimeLaunch {
114 program: "grok".into(),
115 arguments: vec![
116 "--sandbox".into(),
117 "workspace".into(),
118 "agent".into(),
119 "--no-leader".into(),
120 "stdio".into(),
121 ],
122 env: BTreeMap::from([("GROK_AGENT_DASHBOARD".into(), "0".into())]),
123 };
124 let grok = AcpRuntimeBackend::new(HarnessId::from(HarnessId::GROK), grok_launch.clone())
125 .with_resume_support(true);
126 let gemini_launch = RuntimeLaunch {
127 program: "gemini".into(),
128 arguments: vec!["--acp".into()],
129 env: BTreeMap::new(),
130 };
131 let gemini = AcpRuntimeBackend::new(HarnessId::from(HarnessId::GEMINI), gemini_launch.clone())
132 .with_resume_support(true);
133 let goose_launch = RuntimeLaunch {
134 program: "goose".into(),
135 arguments: vec!["acp".into()],
136 env: BTreeMap::new(),
137 };
138 let goose = AcpRuntimeBackend::new(HarnessId::from(HarnessId::GOOSE), goose_launch.clone())
139 .with_resume_support(true);
140 let supercode_launch = RuntimeLaunch {
141 program: "supercode".into(),
142 arguments: vec!["acp".into()],
143 env: BTreeMap::new(),
144 };
145 let supercode = AcpRuntimeBackend::new(
146 HarnessId::from(HarnessId::SUPERCODE),
147 supercode_launch.clone(),
148 )
149 .with_resume_support(true);
150
151 SupportRegistryReport {
152 schema: SUPPORT_REGISTRY_SCHEMA.into(),
153 harnesses: vec![
154 HarnessSupportDescriptor {
155 id: HarnessId::from(HarnessId::CLAUDE_CODE),
156 display_name: "Claude Code".into(),
157 native: built_in_native(),
158 runtime: built_in_runtime(
159 &claude,
160 "claude-stream-json",
161 RuntimeLaunch {
162 program: "claude".into(),
163 arguments: Vec::new(),
164 env: BTreeMap::new(),
165 },
166 ),
167 },
168 HarnessSupportDescriptor {
169 id: HarnessId::from(HarnessId::CODEX),
170 display_name: "Codex".into(),
171 native: built_in_native(),
172 runtime: built_in_runtime(
173 &codex,
174 "codex-app-server-jsonl",
175 RuntimeLaunch {
176 program: "codex".into(),
177 arguments: vec!["app-server".into()],
178 env: BTreeMap::new(),
179 },
180 ),
181 },
182 HarnessSupportDescriptor {
183 id: HarnessId::from(HarnessId::OPENCODE),
184 display_name: "OpenCode".into(),
185 native: built_in_native(),
186 runtime: built_in_runtime(
187 &opencode,
188 "opencode-http-sse",
189 RuntimeLaunch {
190 program: "opencode".into(),
191 arguments: vec!["serve".into()],
192 env: BTreeMap::new(),
193 },
194 ),
195 },
196 HarnessSupportDescriptor {
197 id: HarnessId::from(HarnessId::PI),
198 display_name: "Pi".into(),
199 native: built_in_native(),
200 runtime: built_in_runtime(
201 &pi,
202 "pi-rpc-jsonl",
203 RuntimeLaunch {
204 program: "pi".into(),
205 arguments: vec!["--mode".into(), "rpc".into()],
206 env: BTreeMap::new(),
207 },
208 ),
209 },
210 HarnessSupportDescriptor {
211 id: HarnessId::from(HarnessId::GROK),
212 display_name: "Grok".into(),
213 native: built_in_native(),
214 runtime: RuntimeSupport {
215 implementation: ImplementationKind::GenericProtocol,
216 protocol: "acp-v1-jsonrpc".into(),
217 default_launch: Some(grok_launch),
218 capabilities: grok.capabilities(),
219 },
220 },
221 HarnessSupportDescriptor {
222 id: HarnessId::from(HarnessId::GEMINI),
223 display_name: "Gemini CLI".into(),
224 native: built_in_native(),
225 runtime: RuntimeSupport {
226 implementation: ImplementationKind::GenericProtocol,
227 protocol: "acp-v1-jsonrpc".into(),
228 default_launch: Some(gemini_launch),
229 capabilities: gemini.capabilities(),
230 },
231 },
232 HarnessSupportDescriptor {
233 id: HarnessId::from(HarnessId::GOOSE),
234 display_name: "Goose".into(),
235 native: built_in_native(),
236 runtime: RuntimeSupport {
237 implementation: ImplementationKind::GenericProtocol,
238 protocol: "acp-v1-jsonrpc".into(),
239 default_launch: Some(goose_launch),
240 capabilities: goose.capabilities(),
241 },
242 },
243 HarnessSupportDescriptor {
244 id: HarnessId::from(HarnessId::SUPERCODE),
245 display_name: "Supercode".into(),
246 native: built_in_native(),
247 runtime: RuntimeSupport {
248 implementation: ImplementationKind::GenericProtocol,
249 protocol: "acp-v1-jsonrpc".into(),
250 default_launch: Some(supercode_launch),
251 capabilities: supercode.capabilities(),
252 },
253 },
254 ],
255 }
256}
257
258pub fn harness_support(id: &str) -> Option<HarnessSupportDescriptor> {
260 harness_support_registry()
261 .harnesses
262 .into_iter()
263 .find(|harness| harness.id.as_str() == id)
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn registry_is_unique_and_reports_all_native_support() {
272 let report = harness_support_registry();
273 assert_eq!(report.schema, SUPPORT_REGISTRY_SCHEMA);
274 assert_eq!(report.harnesses.len(), 8);
275 let ids = report
276 .harnesses
277 .iter()
278 .map(|harness| harness.id.as_str())
279 .collect::<std::collections::BTreeSet<_>>();
280 assert_eq!(ids.len(), report.harnesses.len());
281
282 let grok = report
283 .harnesses
284 .iter()
285 .find(|harness| harness.id.as_str() == HarnessId::GROK)
286 .unwrap();
287 assert_eq!(grok.native.discover, ImplementationKind::BuiltIn);
288 assert_eq!(grok.native.load, ImplementationKind::BuiltIn);
289 assert_eq!(grok.native.follow, ImplementationKind::BuiltIn);
290 assert_eq!(grok.native.import, ImplementationKind::BuiltIn);
291 for id in [HarnessId::GEMINI, HarnessId::SUPERCODE] {
292 let harness = report
293 .harnesses
294 .iter()
295 .find(|harness| harness.id.as_str() == id)
296 .unwrap();
297 assert_eq!(harness.native.discover, ImplementationKind::BuiltIn);
298 assert_eq!(harness.native.load, ImplementationKind::BuiltIn);
299 assert_eq!(harness.native.follow, ImplementationKind::BuiltIn);
300 }
301 assert_eq!(grok.native.export, ImplementationKind::BuiltIn);
302 assert_eq!(
303 grok.runtime.implementation,
304 ImplementationKind::GenericProtocol
305 );
306 assert_eq!(
307 grok.runtime.default_launch.as_ref().unwrap().arguments,
308 ["--sandbox", "workspace", "agent", "--no-leader", "stdio"]
309 );
310 assert!(!grok
311 .runtime
312 .default_launch
313 .as_ref()
314 .unwrap()
315 .arguments
316 .iter()
317 .any(|argument| argument == "--always-approve"));
318 assert!(grok.runtime.capabilities.resume_session);
319 }
320}