use std::collections::HashMap;
use std::sync::LazyLock;
use phf::phf_map;
use super::{claude, codex, opencode, HarnessAdapter};
static EAGER_ADAPTERS: phf::Map<&'static str, &'static dyn HarnessAdapter> = phf_map! {
"claude" => &claude::CLAUDE_ADAPTER,
};
static RUNTIME_ADAPTERS: LazyLock<HashMap<&'static str, &'static dyn HarnessAdapter>> =
LazyLock::new(|| {
let mut m = HashMap::new();
m.insert("codex", codex::adapter()); m.insert("opencode", opencode::adapter()); m
});
static RUNTIME_ADAPTER_NAMES: &[&str] = &[
"codex", "opencode", ];
pub fn lookup(name: &str) -> Option<&'static dyn HarnessAdapter> {
if let Some(adapter) = EAGER_ADAPTERS.get(name).copied() {
return Some(adapter);
}
RUNTIME_ADAPTERS.get(name).copied()
}
pub fn list_harness_names() -> Vec<&'static str> {
let mut names: Vec<&'static str> = EAGER_ADAPTERS.keys().copied().collect();
names.extend(RUNTIME_ADAPTER_NAMES.iter().copied());
names
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::sync::{Arc, LazyLock};
use async_trait::async_trait;
use relayburn_sdk::IngestReport;
use super::super::pending_stamp::{self, IngestSessionsFn, PendingStampAdapter};
use super::super::{HarnessAdapter, PlanCtx, SpawnPlan};
use super::*;
struct FakeAdapter;
#[async_trait]
impl HarnessAdapter for FakeAdapter {
fn name(&self) -> &'static str {
"fake"
}
fn session_root(&self) -> PathBuf {
PathBuf::from("/tmp/fake-sessions")
}
async fn plan(&self, _ctx: &PlanCtx) -> anyhow::Result<SpawnPlan> {
Ok(SpawnPlan::new("fake", vec![]))
}
async fn after_exit(
&self,
_ctx: &PlanCtx,
_plan: &SpawnPlan,
) -> anyhow::Result<IngestReport> {
Ok(IngestReport::default())
}
}
static FAKE: FakeAdapter = FakeAdapter;
static FAKE_EAGER_REGISTRY: phf::Map<&'static str, &'static dyn HarnessAdapter> = phf_map! {
"fake" => &FAKE,
};
#[test]
fn dyn_adapter_round_trip_by_name() {
let got = FAKE_EAGER_REGISTRY
.get("fake")
.copied()
.expect("fake registered");
assert_eq!(got.name(), "fake");
assert_eq!(got.session_root(), PathBuf::from("/tmp/fake-sessions"));
assert!(FAKE_EAGER_REGISTRY.get("missing").is_none());
}
#[test]
fn list_harness_names_is_deterministic() {
const EXPECTED_HARNESS_NAMES: &[&str] = &[
"claude", "codex", "opencode", ];
let names = list_harness_names();
assert_eq!(
names, EXPECTED_HARNESS_NAMES,
"harness ordering drifted; if a Wave 2 adapter just landed, \
update EXPECTED_HARNESS_NAMES to match the new contract"
);
assert_eq!(
list_harness_names(),
names,
"list_harness_names must be deterministic across calls"
);
}
#[test]
fn runtime_adapter_names_match_runtime_adapters() {
for name in RUNTIME_ADAPTER_NAMES.iter().copied() {
assert!(
lookup(name).is_some(),
"RUNTIME_ADAPTER_NAMES advertises {name:?} but RUNTIME_ADAPTERS \
has no entry for it; the two lists must stay in lockstep"
);
}
}
#[test]
fn lookup_unknown_returns_none() {
assert!(lookup("nope").is_none());
assert!(lookup("").is_none());
assert!(lookup("claude ").is_none());
}
static FAKE_PENDING_STAMP_ADAPTER: LazyLock<&'static dyn HarnessAdapter> = LazyLock::new(|| {
let session_root: Arc<dyn Fn() -> PathBuf + Send + Sync> =
Arc::new(|| PathBuf::from("/tmp/codex-sessions"));
let ingest_sessions: IngestSessionsFn =
Arc::new(|_ledger_home| Box::pin(async { Ok(IngestReport::default()) }));
pending_stamp::adapter_static(PendingStampAdapter::new(
"codex",
session_root,
ingest_sessions,
))
});
static FAKE_RUNTIME_REGISTRY: LazyLock<
std::collections::HashMap<&'static str, &'static dyn HarnessAdapter>,
> = LazyLock::new(|| {
let mut m = std::collections::HashMap::new();
m.insert("codex", *FAKE_PENDING_STAMP_ADAPTER);
m
});
#[test]
fn pending_stamp_adapter_static_fits_runtime_registry() {
let got = FAKE_RUNTIME_REGISTRY
.get("codex")
.copied()
.expect("codex registered");
assert_eq!(got.name(), "codex");
assert_eq!(got.session_root(), PathBuf::from("/tmp/codex-sessions"));
assert!(FAKE_RUNTIME_REGISTRY.get("opencode").is_none());
}
const _ASSERT_ADAPTER_STATIC_FITS_REGISTRY: fn(PendingStampAdapter) -> &'static dyn HarnessAdapter =
pending_stamp::adapter_static;
}