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
127 SupportRegistryReport {
128 schema: SUPPORT_REGISTRY_SCHEMA.into(),
129 harnesses: vec![
130 HarnessSupportDescriptor {
131 id: HarnessId::from(HarnessId::CLAUDE_CODE),
132 display_name: "Claude Code".into(),
133 native: built_in_native(),
134 runtime: built_in_runtime(
135 &claude,
136 "claude-stream-json",
137 RuntimeLaunch {
138 program: "claude".into(),
139 arguments: Vec::new(),
140 env: BTreeMap::new(),
141 },
142 ),
143 },
144 HarnessSupportDescriptor {
145 id: HarnessId::from(HarnessId::CODEX),
146 display_name: "Codex".into(),
147 native: built_in_native(),
148 runtime: built_in_runtime(
149 &codex,
150 "codex-app-server-jsonl",
151 RuntimeLaunch {
152 program: "codex".into(),
153 arguments: vec!["app-server".into()],
154 env: BTreeMap::new(),
155 },
156 ),
157 },
158 HarnessSupportDescriptor {
159 id: HarnessId::from(HarnessId::OPENCODE),
160 display_name: "OpenCode".into(),
161 native: built_in_native(),
162 runtime: built_in_runtime(
163 &opencode,
164 "opencode-http-sse",
165 RuntimeLaunch {
166 program: "opencode".into(),
167 arguments: vec!["serve".into()],
168 env: BTreeMap::new(),
169 },
170 ),
171 },
172 HarnessSupportDescriptor {
173 id: HarnessId::from(HarnessId::PI),
174 display_name: "Pi".into(),
175 native: built_in_native(),
176 runtime: built_in_runtime(
177 &pi,
178 "pi-rpc-jsonl",
179 RuntimeLaunch {
180 program: "pi".into(),
181 arguments: vec!["--mode".into(), "rpc".into()],
182 env: BTreeMap::new(),
183 },
184 ),
185 },
186 HarnessSupportDescriptor {
187 id: HarnessId::from(HarnessId::GROK),
188 display_name: "Grok".into(),
189 native: built_in_native(),
190 runtime: RuntimeSupport {
191 implementation: ImplementationKind::GenericProtocol,
192 protocol: "acp-v1-jsonrpc".into(),
193 default_launch: Some(grok_launch),
194 capabilities: grok.capabilities(),
195 },
196 },
197 ],
198 }
199}
200
201pub fn harness_support(id: &str) -> Option<HarnessSupportDescriptor> {
203 harness_support_registry()
204 .harnesses
205 .into_iter()
206 .find(|harness| harness.id.as_str() == id)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn registry_is_unique_and_reports_grok_native_support() {
215 let report = harness_support_registry();
216 assert_eq!(report.schema, SUPPORT_REGISTRY_SCHEMA);
217 assert_eq!(report.harnesses.len(), 5);
218 let ids = report
219 .harnesses
220 .iter()
221 .map(|harness| harness.id.as_str())
222 .collect::<std::collections::BTreeSet<_>>();
223 assert_eq!(ids.len(), report.harnesses.len());
224
225 let grok = report
226 .harnesses
227 .iter()
228 .find(|harness| harness.id.as_str() == HarnessId::GROK)
229 .unwrap();
230 assert_eq!(grok.native.discover, ImplementationKind::BuiltIn);
231 assert_eq!(grok.native.load, ImplementationKind::BuiltIn);
232 assert_eq!(grok.native.follow, ImplementationKind::BuiltIn);
233 assert_eq!(grok.native.import, ImplementationKind::BuiltIn);
234 assert_eq!(grok.native.export, ImplementationKind::BuiltIn);
235 assert_eq!(
236 grok.runtime.implementation,
237 ImplementationKind::GenericProtocol
238 );
239 assert_eq!(
240 grok.runtime.default_launch.as_ref().unwrap().arguments,
241 ["--sandbox", "workspace", "agent", "--no-leader", "stdio"]
242 );
243 assert!(!grok
244 .runtime
245 .default_launch
246 .as_ref()
247 .unwrap()
248 .arguments
249 .iter()
250 .any(|argument| argument == "--always-approve"));
251 assert!(grok.runtime.capabilities.resume_session);
252 }
253}