use harn_serve::adapters::acp::{
ACP_SCHEMA_COMPATIBILITY, HARN_AGENT_EVENT_KINDS, HARN_AGENT_EVENT_METHOD,
HARN_CONTENT_EXTENSION_FIELDS, HARN_PROVIDER_CATALOG_METHOD, HARN_SESSION_UPDATE_EXTENSIONS,
HARN_TOOL_LIFECYCLE_EXTENSION_FIELDS,
};
use super::constants::*;
use super::support::*;
use super::values::*;
pub(super) fn generate_rust() -> String {
let mut out = String::new();
out.push_str("// GENERATED by `harn dump-protocol-artifacts` - do not edit by hand.\n");
out.push_str("// Source: Harn adapter schemas and Rust wire vocabulary.\n\n");
out.push_str(
"//! Dependency-free Rust bindings for Harn's host/integrator protocol surface.\n",
);
out.push_str("//!\n");
out.push_str("//! Mirrors the TypeScript, Swift, Python, and Go artifacts generated\n");
out.push_str("//! alongside this module. Every value is the literal JSON wire string Harn\n");
out.push_str("//! emits, so downstream Rust hosts can route and match on these constants\n");
out.push_str("//! instead of hand-maintaining a parallel method list. Adding a wire value\n");
out.push_str("//! is additive and minor-version compatible.\n");
out.push_str("#![allow(dead_code)]\n\n");
out.push_str("/// Harn release that generated this binding.\n");
out.push_str(&format!(
"pub const HARN_PROTOCOL_ARTIFACT_VERSION: &str = {};\n\n",
json_string_literal(env!("CARGO_PKG_VERSION"))
));
out.push_str("/// Upstream ACP schema version Harn tracks.\n");
out.push_str(&format!(
"pub const ACP_SCHEMA_COMPATIBILITY: &str = {};\n\n",
json_string_literal(ACP_SCHEMA_COMPATIBILITY)
));
out.push_str("/// JSON-RPC method for `_harn/agentEvent` extension notifications.\n");
out.push_str(&format!(
"pub const HARN_AGENT_EVENT_METHOD: &str = {};\n\n",
json_string_literal(HARN_AGENT_EVENT_METHOD)
));
out.push_str("/// JSON-RPC method for Harn's provider catalog extension.\n");
out.push_str(&format!(
"pub const HARN_PROVIDER_CATALOG_METHOD: &str = {};\n\n",
json_string_literal(HARN_PROVIDER_CATALOG_METHOD)
));
out.push_str(&rust_const_group(
"ACP_AGENT_METHOD",
"ACP_AGENT_METHODS",
"Stable host-facing ACP agent methods (matches the TypeScript/Swift/Python/Go bindings).",
ACP_AGENT_METHODS,
));
out.push_str(&rust_const_group(
"ACP_DISPATCHED_METHOD",
"ACP_DISPATCHED_METHODS",
"Every JSON-RPC method the ACP adapter actually dispatches, including the \
workspace-management, workflow-control, and HITL methods the stable \
bindings do not yet expose as typed enums. Reconciled against the \
`match` arms in `harn-serve`'s ACP adapter.",
ACP_DISPATCHED_METHODS,
));
out.push_str(&rust_const_group(
"ACP_CLIENT_METHOD",
"ACP_CLIENT_METHODS",
"ACP client methods the agent calls back into the host for.",
ACP_CLIENT_METHODS,
));
out.push_str(&rust_const_group(
"ACP_AGENT_NOTIFICATION",
"ACP_AGENT_NOTIFICATIONS",
"ACP notifications the agent emits to the host.",
ACP_AGENT_NOTIFICATIONS,
));
let session_updates = all_acp_session_updates();
out.push_str(&rust_const_group_owned(
"ACP_SESSION_UPDATE",
"ACP_SESSION_UPDATES",
"Every `session/update` discriminator Harn emits (canonical ACP variants \
plus Harn extensions), the union the Swift binding exposes as \
`acpSessionUpdateExtensions` merged with the base ACP set.",
&session_updates,
));
out.push_str(&rust_const_group(
"HARN_ACP_SESSION_UPDATE_EXTENSION",
"HARN_ACP_SESSION_UPDATE_EXTENSIONS",
"Harn-specific `session/update` discriminators beyond the canonical ACP \
set (the values Swift publishes as `acpSessionUpdateExtensions`).",
HARN_SESSION_UPDATE_EXTENSIONS,
));
out.push_str(&rust_const_group(
"HARN_AGENT_EVENT_KIND",
"HARN_AGENT_EVENT_KINDS",
"Pipeline-loop milestone kinds emitted via `_harn/agentEvent`.",
HARN_AGENT_EVENT_KINDS,
));
out.push_str(&rust_const_group(
"HARN_CONTENT_EXTENSION_FIELD",
"HARN_CONTENT_EXTENSION_FIELDS",
"`_meta.harn` content-block extension keys (`visible_text` / \
`visible_delta`) Harn attaches to streamed content.",
HARN_CONTENT_EXTENSION_FIELDS,
));
out.push_str(&rust_const_group(
"HARN_TOOL_LIFECYCLE_EXTENSION_FIELD",
"HARN_TOOL_LIFECYCLE_EXTENSION_FIELDS",
"`_meta.harn` tool-lifecycle extension keys on tool_call / \
tool_call_update notifications.",
HARN_TOOL_LIFECYCLE_EXTENSION_FIELDS,
));
out
}
pub(super) fn rust_const_group(
const_prefix: &str,
slice_name: &str,
doc: &str,
values: &[&str],
) -> String {
rust_const_group_owned(const_prefix, slice_name, doc, &strs_to_strings(values))
}
pub(super) fn rust_const_group_owned(
const_prefix: &str,
slice_name: &str,
doc: &str,
values: &[String],
) -> String {
let mut out = String::new();
for value in values {
out.push_str(&format!(
"pub const {}: &str = {};\n",
rust_const_name(const_prefix, value),
json_string_literal(value)
));
}
out.push('\n');
out.push_str(&rust_doc_comment(doc));
out.push_str(&format!("pub const {slice_name}: &[&str] = &[\n"));
for value in values {
out.push_str(" ");
out.push_str(&json_string_literal(value));
out.push_str(",\n");
}
out.push_str("];\n\n");
out
}
pub(super) fn rust_const_name(prefix: &str, value: &str) -> String {
let mut suffix = String::with_capacity(value.len());
for ch in value.chars() {
if ch.is_ascii_alphanumeric() {
suffix.extend(ch.to_uppercase());
} else {
suffix.push('_');
}
}
let suffix = collapse_repeated_underscores(suffix.trim_matches('_'));
let name = if suffix.is_empty() {
prefix.to_string()
} else {
format!("{prefix}_{suffix}")
};
if name.chars().next().is_some_and(|ch| ch.is_ascii_digit()) {
format!("_{name}")
} else {
name
}
}
pub(super) fn rust_doc_comment(doc: &str) -> String {
let mut out = String::new();
let normalized = doc.split_whitespace().collect::<Vec<_>>().join(" ");
out.push_str("/// ");
out.push_str(&normalized);
out.push('\n');
out
}