use anyhow::{Result, bail};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HarnessKind {
#[cfg(feature = "norn")]
Norn,
#[cfg(feature = "acp")]
Acp,
}
impl HarnessKind {
pub(crate) const fn name(self) -> &'static str {
match self {
#[cfg(feature = "norn")]
Self::Norn => "norn",
#[cfg(feature = "acp")]
Self::Acp => "acp",
}
}
pub(crate) const fn description(self) -> &'static str {
match self {
#[cfg(feature = "norn")]
Self::Norn => {
"norn: a `norn --protocol jsonrpc` child process \
(aion-integration-norn)"
}
#[cfg(feature = "acp")]
Self::Acp => {
"acp: any agent speaking the Agent Client Protocol over its stdio \
(aion-integration-acp)"
}
}
}
pub(crate) fn registration_capabilities(self) -> aion_core::InterventionCapabilities {
match self {
#[cfg(feature = "norn")]
Self::Norn => aion_integration_norn::registration_capabilities(),
#[cfg(feature = "acp")]
Self::Acp => aion_integration_acp::registration_capabilities(),
}
}
}
#[cfg(all(test, any(feature = "norn", feature = "acp")))]
impl HarnessKind {
pub(crate) fn test_config(self) -> super::settings::HarnessConfig {
use super::settings::HarnessConfig;
match self {
#[cfg(feature = "norn")]
Self::Norn => HarnessConfig::Norn(super::settings::NornConfig {
binary: None,
workspace: aion_integrations::HarnessWorkspace::Fixed(std::path::PathBuf::from(
"/srv/workspace",
)),
args: Vec::new(),
environment: super::test_environment(),
}),
#[cfg(feature = "acp")]
Self::Acp => HarnessConfig::Acp(super::settings::AcpConfig {
command: std::path::PathBuf::from("/usr/local/bin/opencode"),
args: vec![String::from("acp")],
workspace: aion_integrations::HarnessWorkspace::Fixed(std::path::PathBuf::from(
"/srv/workspace",
)),
permission: aion_integration_acp::PermissionPolicy::Deny,
environment: super::test_environment(),
exit_grace: std::time::Duration::from_secs(2),
}),
}
}
}
pub(crate) fn compiled() -> Vec<HarnessKind> {
[
#[cfg(feature = "norn")]
HarnessKind::Norn,
#[cfg(feature = "acp")]
HarnessKind::Acp,
]
.to_vec()
}
fn carried() -> String {
let names = compiled()
.iter()
.map(|harness| harness.name())
.collect::<Vec<_>>()
.join(", ");
if names.is_empty() {
"none".to_owned()
} else {
names
}
}
pub(crate) fn resolve(declared: &str, document: &std::path::Path) -> Result<Option<HarnessKind>> {
let compiled = compiled();
if compiled.is_empty() {
return Ok(None);
}
match compiled
.iter()
.copied()
.find(|kind| kind.name() == declared)
{
Some(kind) => Ok(Some(kind)),
None => bail!(
"{} declares `harness kind {declared}`, which this `aion` build does not carry (it \
carries: {}). A harness is compiled in by a cargo feature, so a name this binary \
does not know cannot be supplied at runtime — rebuild with the feature, or declare \
one of the harnesses above.",
document.display(),
carried()
),
}
}
pub(crate) fn registration_capabilities(
harness: Option<HarnessKind>,
) -> aion_core::InterventionCapabilities {
harness.map_or_else(
aion_core::InterventionCapabilities::none,
HarnessKind::registration_capabilities,
)
}
pub fn announce_composed_harness() {
let compiled = compiled();
if compiled.is_empty() {
eprintln!(
"aion server: no agent harness compiled into this build \
(--no-default-features without `norn`/`acp`); agent activities are unavailable \
until a harness is composed in."
);
return;
}
for harness in &compiled {
eprintln!(
"aion server: agent harness composed at the binary root: {}. \
The worker drives it through the neutral AgentHarness trait.",
harness.description()
);
}
if compiled.len() > 1 {
eprintln!(
"aion server: {} harnesses are composed in, so a worker document\'s `harness` \
section selects one by name with `kind <{}>`.",
compiled.len(),
carried()
);
}
}