mod activity;
mod connector_setup;
mod constants;
mod external_action;
mod harn_records;
mod manifest;
mod plan_records;
mod prepared_session;
mod recap_records;
mod records;
mod schema_records;
mod session_recap;
mod session_update_payloads;
mod support;
mod values;
mod go;
mod python;
mod rust;
mod swift;
mod typescript;
#[cfg(test)]
mod tests;
use std::fs;
use std::path::Path;
use std::process;
use harn_vm::llm::plan::PLAN_DOCUMENT_SCHEMA_ARTIFACT;
use harn_vm::llm::receipts::TOOL_CALL_RECEIPT_SCHEMA_ARTIFACT;
use harn_vm::tool_registry::{
tool_catalog_json_schema, tool_catalog_typescript, TOOL_CATALOG_SCHEMA_ARTIFACT,
TOOL_CATALOG_TYPESCRIPT_ARTIFACT,
};
use activity::ActivityVocabulary;
use connector_setup::ConnectorSetupVocabulary;
use constants::*;
use external_action::ExternalActionVocabulary;
use go::*;
use manifest::*;
use python::*;
use rust::*;
use support::*;
use swift::*;
use typescript::*;
pub(crate) use manifest::{manifest_json, manifest_json_from};
#[derive(Debug)]
struct Artifact {
relative_path: String,
contents: String,
}
impl Artifact {
fn new(relative_path: impl Into<String>, contents: impl Into<String>) -> Self {
Self {
relative_path: relative_path.into(),
contents: ensure_trailing_newline(contents.into()),
}
}
}
pub(crate) fn run(output_dir: &str, check_only: bool) {
let source = ProtocolArtifactSource::discover().unwrap_or_else(|error| {
eprintln!("error: failed to locate protocol sources: {error}");
process::exit(1);
});
let artifacts = generate_artifacts(&source).unwrap_or_else(|error| {
eprintln!("error: failed to generate protocol artifacts: {error}");
process::exit(1);
});
let output_root = Path::new(output_dir);
if check_only {
let mut stale = Vec::new();
for artifact in &artifacts {
let path = output_root.join(&artifact.relative_path);
match fs::read_to_string(&path) {
Ok(existing)
if normalize_line_endings(&existing)
== normalize_line_endings(&artifact.contents) => {}
Ok(_) => stale.push(path),
Err(_) => stale.push(path),
}
}
if !stale.is_empty() {
eprintln!("error: protocol artifacts are stale or missing:");
for path in stale {
eprintln!(" {}", path.display());
}
eprintln!("hint: run `make gen-protocol-artifacts` to regenerate.");
process::exit(1);
}
return;
}
for artifact in artifacts {
let path = output_root.join(&artifact.relative_path);
if let Some(parent) = path.parent() {
if let Err(error) = fs::create_dir_all(parent) {
eprintln!("error: cannot create {}: {error}", parent.display());
process::exit(1);
}
}
if let Err(error) = fs::write(&path, artifact.contents) {
eprintln!("error: cannot write {}: {error}", path.display());
process::exit(1);
}
println!("wrote {}", path.display());
}
}
fn generate_artifacts(source: &ProtocolArtifactSource) -> Result<Vec<Artifact>, String> {
let external_actions = ExternalActionVocabulary::load(source)?;
let connector_setup = ConnectorSetupVocabulary::load(source)?;
let activity = ActivityVocabulary::load(source)?;
let go_artifact = generate_go_artifact()?;
let mut artifacts = vec![
Artifact::new("README.md", generate_readme()),
Artifact::new(
"manifest.json",
generate_manifest_with_vocabularies(
source,
&external_actions,
&connector_setup,
&activity,
)?,
),
Artifact::new(
"harn-protocol.ts",
generate_typescript(&external_actions, &connector_setup, &activity),
),
Artifact::new(
"HarnProtocol.swift",
generate_swift(&external_actions, &connector_setup, &activity),
),
Artifact::new(
"harn-protocol.rs",
format_rust_source(
generate_rust(&external_actions, &connector_setup, &activity),
source.repo_root(),
)?,
),
Artifact::new("python/harn_protocol.py", generate_python()),
Artifact::new("python/__init__.py", PYTHON_INIT_STUB.to_string()),
Artifact::new("go/harnprotocol/harnprotocol.go", go_artifact),
Artifact::new("go/harnprotocol/go.mod", generate_go_mod()),
Artifact::new("fixtures/round_trip.json", generate_round_trip_fixture()?),
Artifact::new(
TOOL_CALL_RECEIPT_SCHEMA_ARTIFACT,
generate_tool_call_receipt_schema()?,
),
Artifact::new(
PLAN_DOCUMENT_SCHEMA_ARTIFACT,
serde_json::to_string_pretty(&harn_vm::llm::plan::plan_document_json_schema())
.map_err(|error| format!("failed to encode plan document schema: {error}"))?,
),
Artifact::new(
TOOL_CATALOG_SCHEMA_ARTIFACT,
serde_json::to_string_pretty(&tool_catalog_json_schema())
.map_err(|error| format!("failed to encode tool catalog schema: {error}"))?,
),
Artifact::new(TOOL_CATALOG_TYPESCRIPT_ARTIFACT, tool_catalog_typescript()),
Artifact::new(
harn_vm::session_recap::SESSION_RECAP_SCHEMA_ARTIFACT,
serde_json::to_string_pretty(&harn_vm::session_recap::session_recap_json_schema())
.map_err(|error| format!("failed to encode session recap schema: {error}"))?,
),
Artifact::new(
harn_vm::prepared_run::PREPARED_SESSION_SCHEMA_ARTIFACT,
harn_vm::prepared_run::PREPARED_SESSION_V1_SCHEMA_JSON,
),
];
for schema in SCHEMA_COPIES {
artifacts.push(Artifact::new(
schema.artifact,
source.read_text(schema.source)?,
));
}
artifacts.sort_by(|left, right| left.relative_path.cmp(&right.relative_path));
Ok(artifacts)
}