use crate::{Harness, HarnessInfo};
#[cfg(feature = "bob")]
use crate::Bob;
#[cfg(feature = "claude")]
use crate::Claude;
#[cfg(feature = "codex")]
use crate::Codex;
pub const DEFAULT_HARNESS_ID: &str = "bob";
#[derive(Default)]
pub struct Registry {
harnesses: Vec<Box<dyn Harness>>,
}
impl Registry {
pub fn new() -> Self {
Self::default()
}
pub fn register(mut self, harness: impl Harness + 'static) -> Self {
self.harnesses.push(Box::new(harness));
self
}
pub fn by_id(&self, id: &str) -> Option<&dyn Harness> {
self.harnesses
.iter()
.map(Box::as_ref)
.find(|h| h.info().id == id)
}
pub fn catalog(&self) -> Vec<HarnessInfo> {
self.harnesses.iter().map(|h| h.info()).collect()
}
pub fn ids(&self) -> Vec<String> {
self.harnesses.iter().map(|h| h.info().id).collect()
}
}
pub fn default_registry() -> Registry {
#[allow(unused_mut)]
let mut reg = Registry::new();
#[cfg(feature = "bob")]
{
reg = reg.register(Bob::new());
}
#[cfg(feature = "claude")]
{
reg = reg.register(Claude::new());
}
#[cfg(feature = "codex")]
{
reg = reg.register(Codex::new());
}
reg
}
pub fn harness_by_id(id: &str) -> Option<Box<dyn Harness>> {
let _ = id;
#[cfg(feature = "bob")]
{
if id == crate::BOB_HARNESS_ID {
return Some(Box::new(Bob::new()));
}
}
#[cfg(feature = "claude")]
{
if id == crate::CLAUDE_HARNESS_ID {
return Some(Box::new(Claude::new()));
}
}
#[cfg(feature = "codex")]
{
if id == crate::CODEX_HARNESS_ID {
return Some(Box::new(Codex::new()));
}
}
None
}
pub fn harness_catalog() -> Vec<HarnessInfo> {
default_registry().catalog()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
CredentialSpec, HarnessCapabilities, HarnessReadiness, InstallCallback, RunCallback,
RunHandle, RunRequest,
};
#[test]
fn default_registry_lists_bob_claude_codex_in_order() {
assert_eq!(default_registry().ids(), vec!["bob", "claude", "codex"]);
assert_eq!(default_registry().catalog()[0].id, DEFAULT_HARNESS_ID);
}
#[test]
fn harness_by_id_resolves_builtins_and_rejects_unknown() {
assert!(harness_by_id("bob").is_some());
assert!(harness_by_id("claude").is_some());
assert!(harness_by_id("codex").is_some());
assert!(harness_by_id("nope").is_none());
}
#[test]
fn capabilities_match_each_adapter_and_back_credential_required() {
let caps = |id: &str| harness_by_id(id).unwrap().info().capabilities;
let bob = caps("bob");
assert!(bob.credential_required && bob.previews_edits);
assert!(bob.models.is_empty() && !bob.supports_effort && !bob.supports_max_turns);
assert_eq!(
bob.credential_required,
harness_by_id("bob").unwrap().credential().required
);
let claude = caps("claude");
assert!(!claude.credential_required && !claude.previews_edits);
assert!(!claude.models.is_empty() && !claude.allows_custom_model);
assert!(claude.supports_max_turns && !claude.supports_effort);
let codex = caps("codex");
assert!(!codex.credential_required && !codex.previews_edits);
assert!(codex.allows_custom_model && codex.supports_effort && !codex.supports_max_turns);
assert!(claude.supports_login && codex.supports_login && !bob.supports_login);
}
struct Acme;
impl Harness for Acme {
fn info(&self) -> HarnessInfo {
HarnessInfo {
id: "acme".to_owned(),
display_name: "Acme".to_owned(),
description: "A custom third-party harness.".to_owned(),
requires_install: false,
capabilities: HarnessCapabilities {
credential_required: false,
previews_edits: false,
models: Vec::new(),
allows_custom_model: true,
supports_effort: false,
supports_max_turns: false,
supports_login: false,
},
}
}
fn readiness(&self) -> HarnessReadiness {
HarnessReadiness {
harness_id: "acme".to_owned(),
ready: true,
installed: true,
version: None,
auth_configured: true,
error: None,
details: serde_json::Value::Null,
}
}
fn install(&self, _on_event: InstallCallback) -> Result<(), crate::HarnessError> {
Ok(())
}
fn run(
&self,
_req: RunRequest,
_on_event: RunCallback,
) -> Result<RunHandle, crate::HarnessError> {
Err(crate::HarnessError::Other(
"acme: run not implemented in test".to_owned(),
))
}
fn credential(&self) -> CredentialSpec {
CredentialSpec {
label: "Acme key".to_owned(),
keychain_service: "acme".to_owned(),
keychain_account: "ACME_API_KEY".to_owned(),
required: false,
}
}
}
#[test]
fn custom_harness_registers_and_resolves_alongside_builtins() {
let reg = Registry::new().register(Bob::new()).register(Acme);
assert!(reg.by_id("bob").is_some());
assert!(reg.by_id("acme").is_some(), "custom harness must resolve");
assert_eq!(reg.ids(), vec!["bob", "acme"]);
}
}