use std::collections::BTreeMap;
use std::env;
use std::path::{Component, Path as FsPath};
use axum::extract::{Path, State};
use axum::response::IntoResponse;
use axum::Json;
use base64::engine::general_purpose::STANDARD;
use base64::{DecodeError, Engine};
use embacle::config::CliRunnerType;
use embacle::discovery::resolve_binary;
use serde::{Deserialize, Serialize};
use super::error::UhpFailure;
use super::state::UhpState;
use crate::runner::ALL_PROVIDERS;
use crate::state::SharedState;
#[derive(Debug, Clone, Serialize)]
pub struct Harness {
pub id: String,
pub object: &'static str,
pub name: String,
pub base: String,
#[serde(rename = "defaultModel", skip_serializing_if = "Option::is_none")]
pub default_model: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub skills: Vec<Skill>,
#[serde(rename = "mcpServers", default, skip_serializing_if = "Vec::is_empty")]
pub mcp_servers: Vec<McpServer>,
#[serde(
rename = "disabledTools",
default,
skip_serializing_if = "Vec::is_empty"
)]
pub disabled_tools: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillFile {
pub path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_b64: Option<String>,
#[serde(flatten)]
pub rest: serde_json::Map<String, serde_json::Value>,
}
impl SkillFile {
#[must_use]
pub fn relative_path(&self) -> Option<&FsPath> {
let path = FsPath::new(&self.path);
let mut components = path.components().peekable();
components.peek()?;
components
.all(|c| matches!(c, Component::Normal(_)))
.then_some(path)
}
pub fn bytes(&self) -> Result<Vec<u8>, DecodeError> {
match (&self.content, &self.content_b64) {
(Some(text), _) => Ok(text.as_bytes().to_vec()),
(None, Some(encoded)) => STANDARD.decode(encoded),
(None, None) => Ok(Vec::new()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Skill {
pub name: String,
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub files: Vec<SkillFile>,
}
impl Skill {
pub const MANIFEST: &'static str = "SKILL.md";
#[must_use]
pub fn has_manifest(&self) -> bool {
self.files.iter().any(|f| f.path == Self::MANIFEST)
}
#[must_use]
pub fn has_plain_name(&self) -> bool {
let mut components = FsPath::new(&self.name).components();
matches!(
(components.next(), components.next()),
(Some(Component::Normal(_)), None)
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServer {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transport: Option<String>,
#[serde(default)]
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct HarnessList {
pub harnesses: Vec<Harness>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Model {
pub id: String,
pub backend: String,
pub available: bool,
pub default: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct BackendModels {
pub default: String,
pub models: Vec<Model>,
}
#[derive(Debug, Clone, Serialize)]
pub struct HarnessModels {
pub harness_id: String,
pub backend: String,
pub default: String,
pub models: Vec<Model>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ModelCatalog {
pub backends: BTreeMap<String, BackendModels>,
}
fn harness_id(provider: &str) -> String {
let slug: String = provider
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
.collect();
format!("chrn_{slug}")
}
fn installed(provider: CliRunnerType) -> bool {
let env_override = env::var(provider.env_override_key()).ok();
resolve_binary(provider.binary_name(), env_override.as_deref()).is_ok()
}
pub struct Discovered {
pub provider: CliRunnerType,
pub name: String,
pub harness: Harness,
pub models: Vec<String>,
}
pub async fn discover(state: &SharedState) -> Vec<Discovered> {
let mut found = Vec::new();
for &provider in ALL_PROVIDERS.iter() {
if !installed(provider) {
continue;
}
let Ok(runner) = state.get_runner(provider).await else {
continue;
};
let name = runner.name().to_owned();
let models = runner.available_models().to_vec();
let default_model = runner.default_model().to_owned();
found.push(Discovered {
provider,
harness: Harness {
id: harness_id(&name),
object: "harness",
name: name.clone(),
base: name.clone(),
default_model: Some(default_model),
skills: Vec::new(),
mcp_servers: Vec::new(),
disabled_tools: Vec::new(),
},
name,
models,
});
}
found
}
pub async fn resolve(state: &UhpState, id: &str) -> Option<Discovered> {
let found = discover(&state.shared).await;
let Some(configured) = state.configured.get(id) else {
return found.into_iter().find(|d| d.harness.id == id);
};
let base = found
.into_iter()
.find(|d| d.harness.base == configured.base)?;
Some(Discovered {
provider: base.provider,
name: base.name,
harness: Harness {
default_model: configured
.default_model
.clone()
.or(base.harness.default_model),
..configured
},
models: base.models,
})
}
pub async fn list(State(state): State<UhpState>) -> impl IntoResponse {
let mut harnesses: Vec<Harness> = discover(&state.shared)
.await
.into_iter()
.map(|d| d.harness)
.collect();
harnesses.extend(state.configured.all());
Json(HarnessList { harnesses })
}
pub async fn get(
State(state): State<UhpState>,
Path(id): Path<String>,
) -> Result<Json<Harness>, UhpFailure> {
if let Some(configured) = state.configured.get(&id) {
return Ok(Json(configured));
}
discover(&state.shared)
.await
.into_iter()
.find(|d| d.harness.id == id)
.map(|d| Json(d.harness))
.ok_or_else(|| {
UhpFailure::not_found("harness_not_found", "no harness with that id is configured")
})
}
fn catalogue(found: Vec<Discovered>) -> ModelCatalog {
let mut backends = BTreeMap::new();
for d in found {
let name = d.name;
let default = d.harness.default_model.unwrap_or_default();
let models = d
.models
.into_iter()
.map(|id| Model {
default: id == default,
id,
backend: name.clone(),
available: true,
})
.collect();
backends.insert(name, BackendModels { default, models });
}
ModelCatalog { backends }
}
pub async fn models(State(state): State<UhpState>) -> impl IntoResponse {
Json(catalogue(discover(&state.shared).await))
}
pub async fn harness_models(
State(state): State<UhpState>,
Path(id): Path<String>,
) -> Result<Json<HarnessModels>, UhpFailure> {
let d = resolve(&state, &id).await.ok_or_else(|| {
UhpFailure::not_found("harness_not_found", "no harness with that id is configured")
})?;
let name = d.name;
let default = d.harness.default_model.unwrap_or_default();
Ok(Json(HarnessModels {
harness_id: d.harness.id,
backend: name.clone(),
models: d
.models
.into_iter()
.map(|id| Model {
default: id == default,
id,
backend: name.clone(),
available: true,
})
.collect(),
default,
}))
}
pub async fn unknown_route() -> UhpFailure {
UhpFailure::not_found("not_found", "no such endpoint on this server")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_harness_id_satisfies_the_schema_pattern() {
assert!(harness_id("claude_code").starts_with("chrn_"));
assert_eq!(harness_id("claude-code"), "chrn_claude_code");
assert_eq!(harness_id("copilot"), "chrn_copilot");
}
#[test]
fn the_catalogue_groups_by_backend_and_marks_one_default() {
let found = vec![Discovered {
provider: CliRunnerType::Copilot,
name: "copilot".to_owned(),
harness: Harness {
id: harness_id("copilot"),
object: "harness",
name: "copilot".to_owned(),
base: "copilot".to_owned(),
default_model: Some("gpt-5".to_owned()),
skills: Vec::new(),
mcp_servers: Vec::new(),
disabled_tools: Vec::new(),
},
models: vec!["gpt-5".to_owned(), "claude-sonnet".to_owned()],
}];
let cat = catalogue(found);
let backend = cat.backends.get("copilot").expect("backend present"); assert_eq!(backend.default, "gpt-5");
assert_eq!(backend.models.len(), 2);
assert_eq!(
backend.models.iter().filter(|m| m.default).count(),
1,
"exactly one model is the default"
);
assert!(
backend.models.iter().all(|m| m.available),
"availability is a boolean on every model, never absent"
);
}
}