use std::fs;
use std::path::{Path, PathBuf};
use crate::cli::{ProvidersBuildCapabilitiesArgs, ProvidersBuildConfigArgs};
const PROVIDER_CONFIG_GENERATED_HEADER: &str = "\
# @generated by `harn providers build-config`; do not edit directly.
# Edit crates/harn-vm/src/llm/catalog_sources/**/*.toml instead.
";
const PROVIDER_CAPABILITIES_GENERATED_HEADER: &str = "\
# @generated by `harn providers build-capabilities`; do not edit directly.
# Edit crates/harn-vm/src/llm/capability_sources/**/*.toml instead.
";
pub(crate) fn run_build_config(args: &ProvidersBuildConfigArgs) -> Result<(), String> {
let generated = generated_provider_config(&args.source_dir)?;
if args.check {
match fs::read_to_string(&args.output) {
Ok(existing) if existing == generated.body => {
println!("provider config snapshot is up to date");
return Ok(());
}
Ok(_) | Err(_) => {
eprintln!(
"error: generated provider config is stale or missing: {}",
args.output.display()
);
return Err("provider config snapshot drifted".to_string());
}
}
}
if let Some(parent) = args.output.parent() {
fs::create_dir_all(parent).map_err(|error| {
format!(
"failed to create provider config directory {}: {error}",
parent.display()
)
})?;
}
fs::write(&args.output, generated.body)
.map_err(|error| format!("failed to write {}: {error}", args.output.display()))?;
println!(
"wrote {} from {} TOML fragments",
args.output.display(),
generated.fragment_count
);
Ok(())
}
pub(crate) fn run_build_capabilities(args: &ProvidersBuildCapabilitiesArgs) -> Result<(), String> {
let generated = generated_provider_capabilities(&args.source_dir)?;
if args.check {
match fs::read_to_string(&args.output) {
Ok(existing) if existing == generated.body => {
println!("provider capability snapshot is up to date");
return Ok(());
}
Ok(_) | Err(_) => {
eprintln!(
"error: generated provider capability snapshot is stale or missing: {}",
args.output.display()
);
return Err("provider capability snapshot drifted".to_string());
}
}
}
if let Some(parent) = args.output.parent() {
fs::create_dir_all(parent).map_err(|error| {
format!(
"failed to create provider capability directory {}: {error}",
parent.display()
)
})?;
}
fs::write(&args.output, generated.body)
.map_err(|error| format!("failed to write {}: {error}", args.output.display()))?;
println!(
"wrote {} from {} TOML fragments",
args.output.display(),
generated.fragment_count
);
Ok(())
}
struct GeneratedProviderConfig {
body: String,
fragment_count: usize,
}
fn generated_provider_config(source_dir: &Path) -> Result<GeneratedProviderConfig, String> {
let mut fragments = Vec::new();
collect_toml_fragments(source_dir, &mut fragments)?;
fragments.sort();
if fragments.is_empty() {
return Err(format!(
"provider catalog source directory has no TOML fragments: {}",
source_dir.display()
));
}
let mut body = String::from(PROVIDER_CONFIG_GENERATED_HEADER);
for path in &fragments {
let fragment = fs::read_to_string(path)
.map_err(|error| format!("failed to read {}: {error}", path.display()))?;
body.push_str("\n# --- source: ");
body.push_str(&fragment_label(source_dir, path));
body.push_str(" ---\n");
body.push_str(fragment.trim_end());
body.push('\n');
}
harn_vm::llm_config::parse_config_toml(&body)
.map_err(|error| format!("generated provider config does not parse: {error}"))?;
Ok(GeneratedProviderConfig {
body,
fragment_count: fragments.len(),
})
}
fn generated_provider_capabilities(source_dir: &Path) -> Result<GeneratedProviderConfig, String> {
let mut fragments = Vec::new();
collect_toml_fragments(source_dir, &mut fragments)?;
fragments.sort();
if fragments.is_empty() {
return Err(format!(
"provider capability source directory has no TOML fragments: {}",
source_dir.display()
));
}
let mut body = String::from(PROVIDER_CAPABILITIES_GENERATED_HEADER);
for path in &fragments {
let fragment = fs::read_to_string(path)
.map_err(|error| format!("failed to read {}: {error}", path.display()))?;
body.push_str("\n# --- source: ");
body.push_str(&fragment_label(source_dir, path));
body.push_str(" ---\n");
body.push_str(fragment.trim_end());
body.push('\n');
}
toml::from_str::<harn_vm::llm::capabilities::CapabilitiesFile>(&body)
.map_err(|error| format!("generated provider capabilities do not parse: {error}"))?;
Ok(GeneratedProviderConfig {
body,
fragment_count: fragments.len(),
})
}
fn collect_toml_fragments(dir: &Path, fragments: &mut Vec<PathBuf>) -> Result<(), String> {
let mut entries = Vec::new();
let read_dir = fs::read_dir(dir).map_err(|error| {
format!(
"failed to read provider catalog source dir {}: {error}",
dir.display()
)
})?;
for entry in read_dir {
let entry = entry.map_err(|error| {
format!(
"failed to read provider catalog source dir entry in {}: {error}",
dir.display()
)
})?;
entries.push(entry.path());
}
entries.sort();
for path in entries {
if path.is_dir() {
collect_toml_fragments(&path, fragments)?;
} else if path.extension().and_then(|extension| extension.to_str()) == Some("toml") {
fragments.push(path);
}
}
Ok(())
}
fn fragment_label(source_dir: &Path, path: &Path) -> String {
path.strip_prefix(source_dir)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/")
}