use crate::agent::document::DocumentTraits;
use crate::shared::save_document;
use tracing::error;
use crate::agent::Agent;
use crate::agent::loaders::FileLoader;
use crate::schema::action_crud::create_minimal_action;
use crate::schema::agent_crud::create_minimal_agent;
use crate::schema::service_crud::create_minimal_service;
use crate::schema::task_crud::create_minimal_task;
use serde_json::Value;
use std::error::Error;
use std::path::Path;
use tracing::debug;
pub mod a2a;
pub mod agent;
pub mod config;
pub mod crypt;
pub mod dns;
pub mod error;
pub mod health;
pub mod keystore;
pub mod mime;
pub mod observability;
pub mod paths;
pub mod rate_limit;
pub mod schema;
pub mod shared;
pub mod shutdown;
pub mod simple;
pub mod storage;
pub mod time_utils;
pub mod trust;
pub mod validation;
pub mod cli_utils;
pub use error::JacsError;
pub use health::{
health_check, network_health_check, ComponentHealth, HealthCheckResult, HealthStatus,
};
pub use shutdown::{ShutdownGuard, install_signal_handler, is_shutdown_requested, shutdown};
pub use rate_limit::{RateLimitConfig, RateLimiter};
pub use observability::{
LogConfig, LogDestination, MetricsConfig, MetricsDestination, ObservabilityConfig,
ResourceConfig, SamplingConfig, TracingConfig, TracingDestination, init_logging,
init_observability,
};
pub use validation::{
AgentId, are_valid_uuid_parts, format_agent_id, is_valid_agent_id, normalize_agent_id,
parse_agent_id, split_agent_id, validate_agent_id,
};
pub use time_utils::{
backup_timestamp_suffix, now_rfc3339, now_timestamp, now_utc, parse_rfc3339,
parse_rfc3339_to_timestamp, validate_signature_timestamp, validate_timestamp_not_expired,
validate_timestamp_not_future,
};
pub fn init_default_observability() -> Result<(), Box<dyn std::error::Error>> {
let config = ObservabilityConfig {
logs: LogConfig {
enabled: true,
level: "info".to_string(),
destination: LogDestination::File {
path: "./logs".to_string(),
},
headers: None,
},
metrics: MetricsConfig {
enabled: false,
destination: MetricsDestination::File {
path: "./metrics.txt".to_string(),
},
export_interval_seconds: Some(60),
headers: None,
},
tracing: None,
};
init_observability(config).map(|_| ())
}
pub fn init_custom_observability(
config: ObservabilityConfig,
) -> Result<(), Box<dyn std::error::Error>> {
init_observability(config).map(|_| ())
}
pub fn get_empty_agent() -> Agent {
Agent::new(
config::constants::JACS_AGENT_SCHEMA_VERSION,
config::constants::JACS_HEADER_SCHEMA_VERSION,
config::constants::JACS_SIGNATURE_SCHEMA_VERSION,
)
.expect("Failed to init Agent in get_empty_agent") }
fn load_path_agent(filepath: String) -> Agent {
debug!("[load_path_agent] Loading from path: {}", filepath);
let mut agent = get_empty_agent();
let agent_filename = Path::new(&filepath)
.file_name()
.and_then(|os_str| os_str.to_str())
.map(|s| s.to_string())
.expect("Could not extract filename from agent path");
let agent_id = agent_filename
.strip_suffix(".json")
.expect("Agent filename does not end with .json");
debug!("[load_path_agent] Extracted agent ID: {}", agent_id);
let agent_string = agent
.fs_agent_load(agent_id) .map_err(|e| format!("Agent file loading failed for ID '{}': {}", agent_id, e))
.expect("Agent file loading failed: could not load from storage");
agent
.load(&agent_string)
.expect("Agent loading failed: could not parse agent data");
debug!(
"[load_path_agent] Agent loaded and validated successfully using ID: {}",
agent_id
);
agent
}
pub fn load_agent(agentfile: Option<String>) -> Result<agent::Agent, Box<dyn Error>> {
debug!("load_agent agentfile = {:?}", agentfile);
if let Some(file) = agentfile {
Ok(load_path_agent(file.to_string()))
} else {
Err("No agent file provided: specify an agent file path".into())
}
}
pub fn load_agent_with_dns_strict(
agentfile: String,
dns_strict: bool,
) -> Result<agent::Agent, Box<dyn Error>> {
let mut agent = get_empty_agent();
agent.set_dns_strict(dns_strict);
let agent_filename = std::path::Path::new(&agentfile)
.file_name()
.and_then(|os_str| os_str.to_str())
.ok_or("Could not extract filename from agent path")?;
let agent_id = agent_filename
.strip_suffix(".json")
.ok_or("Agent filename does not end with .json")?;
let agent_string = agent
.fs_agent_load(agent_id)
.map_err(|e| format!("Agent file loading failed for ID '{}': {}", agent_id, e))?;
agent.load(&agent_string)?;
Ok(agent)
}
pub fn create_minimal_blank_agent(
agentype: String,
service_desc: Option<String>,
success_desc: Option<String>,
failure_desc: Option<String>,
) -> Result<String, Box<dyn Error>> {
let mut services: Vec<Value> = Vec::new();
let service_description =
service_desc.unwrap_or_else(|| "Describe a service the agent provides".to_string());
let success_description = success_desc
.unwrap_or_else(|| "Describe a success of the service the agent provides".to_string());
let failure_description = failure_desc.unwrap_or_else(|| {
"Describe what failure is of the service the agent provides".to_string()
});
let service = create_minimal_service(
&service_description,
&success_description,
&failure_description,
None,
None,
)
.map_err(|e| {
Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) as Box<dyn Error>
})?;
services.push(service);
let agent_value = create_minimal_agent(&agentype, Some(services), None)?;
Ok(agent_value.to_string())
}
pub fn create_task(
agent: &mut Agent,
name: String,
description: String,
) -> Result<String, Box<dyn Error>> {
let mut actions: Vec<Value> = Vec::new();
let action = create_minimal_action(&name, &description, None, None);
actions.push(action);
let mut task = create_minimal_task(Some(actions), None, None, None)?;
task["jacsTaskCustomer"] = agent.signing_procedure(&task, None, "jacsTaskCustomer")?;
let embed = None;
let docresult = agent.create_document_and_load(&task.to_string(), None, embed);
save_document(agent, docresult, None, None, None, None)?;
let task_value = agent.get_document(task["id"].as_str().unwrap())?.value;
let validation_result = agent.schema.taskschema.validate(&task_value);
match validation_result {
Ok(_) => Ok(task_value.to_string()),
Err(err) => {
let schema_name = task_value
.get("$schema")
.and_then(|v| v.as_str())
.unwrap_or("task.schema.json");
let error_message = format!(
"Task creation failed: {}",
schema::format_schema_validation_error(&err, schema_name, &task_value)
);
error!("{}", error_message);
Err(error_message.into())
}
}
}
pub fn update_task(_: String) -> Result<String, Box<dyn Error>> {
Ok("".to_string())
}