zeroclaw/src/identity/aieos.rs
argenis de la rosa ef4444ba43 fix: resolve build errors and add comprehensive symlink tests
- Fixed E0425 error in src/skills/mod.rs by moving println! inside #[cfg(unix)] block where 'dest' variable is in scope
- Added missing 'identity' field to Config struct initializations in src/onboard/wizard.rs
- Fixed import paths for AIEOS identity functions in src/channels/mod.rs
- Added comprehensive symlink edge case tests in src/skills/symlink_tests.rs
- All 840 tests passing, 0 clippy warnings

Resolves issue #28: skills symlink functionality now works correctly on Unix platforms with proper error handling on non-Unix platforms
2026-02-14 13:37:27 -05:00

1841 lines
62 KiB
Rust

//! AIEOS (AI Entity Object Specification) v1.1 support
//!
//! AIEOS is a standardization framework for portable AI identity.
//! See: <https://aieos.org>
//!
//! This module provides:
//! - Full AIEOS v1.1 schema types
//! - JSON parsing and validation
//! - Conversion to `ZeroClaw` system prompt sections
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use std::path::Path;
// ══════════════════════════════════════════════════════════════════════════════
// AIEOS v1.1 Schema Types
// ══════════════════════════════════════════════════════════════════════════════
/// Root AIEOS entity object
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosEntity {
/// JSON-LD context (optional, for semantic web compatibility)
#[serde(rename = "@context", default)]
pub context: Option<AieosContext>,
/// Entity type marker
#[serde(rename = "@type", default)]
pub entity_type: Option<String>,
/// Protocol standard info
#[serde(default)]
pub standard: Option<AieosStandard>,
/// Internal tracking metadata
#[serde(default)]
pub metadata: Option<AieosMetadata>,
/// Standardized skills and tools
#[serde(default)]
pub capabilities: Option<AieosCapabilities>,
/// Core biographical data
#[serde(default)]
pub identity: Option<AieosIdentity>,
/// Visual descriptors for image generation
#[serde(default)]
pub physicality: Option<AieosPhysicality>,
/// The "Soul" layer — cognitive weights, traits, moral boundaries
#[serde(default)]
pub psychology: Option<AieosPsychology>,
/// How the entity speaks — voice and text style
#[serde(default)]
pub linguistics: Option<AieosLinguistics>,
/// Origin story, education, occupation
#[serde(default)]
pub history: Option<AieosHistory>,
/// Preferences, hobbies, lifestyle
#[serde(default)]
pub interests: Option<AieosInterests>,
/// Goals and core drives
#[serde(default)]
pub motivations: Option<AieosMotivations>,
}
// ── Context & Standard ───────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosContext {
#[serde(default)]
pub aieos: Option<String>,
#[serde(default)]
pub schema: Option<String>,
#[serde(default)]
pub xsd: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosStandard {
#[serde(default)]
pub protocol: Option<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub schema_url: Option<String>,
}
// ── Metadata ─────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosMetadata {
#[serde(rename = "@type", default)]
pub metadata_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub instance_version: Option<String>,
#[serde(default)]
pub generator: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub last_updated: Option<String>,
}
// ── Capabilities ─────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosCapabilities {
#[serde(rename = "@type", default)]
pub capabilities_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub skills: Vec<AieosSkill>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosSkill {
#[serde(rename = "@type", default)]
pub skill_type: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub uri: Option<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub auto_activate: Option<bool>,
#[serde(default)]
pub priority: Option<u8>,
}
// ── Identity ─────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosIdentity {
#[serde(rename = "@type", default)]
pub identity_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub names: Option<AieosNames>,
#[serde(default)]
pub bio: Option<AieosBio>,
#[serde(default)]
pub origin: Option<AieosOrigin>,
#[serde(default)]
pub residence: Option<AieosResidence>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosNames {
#[serde(default)]
pub first: Option<String>,
#[serde(default)]
pub middle: Option<String>,
#[serde(default)]
pub last: Option<String>,
#[serde(default)]
pub nickname: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosBio {
#[serde(rename = "@type", default)]
pub bio_type: Option<String>,
#[serde(default)]
pub birthday: Option<String>,
#[serde(default)]
pub age_biological: Option<u32>,
#[serde(default)]
pub age_perceived: Option<u32>,
#[serde(default)]
pub gender: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosOrigin {
#[serde(default)]
pub nationality: Option<String>,
#[serde(default)]
pub ethnicity: Option<String>,
#[serde(default)]
pub birthplace: Option<AieosPlace>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosPlace {
#[serde(rename = "@type", default)]
pub place_type: Option<String>,
#[serde(default)]
pub city: Option<String>,
#[serde(default)]
pub country: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosResidence {
#[serde(rename = "@type", default)]
pub residence_type: Option<String>,
#[serde(default)]
pub current_city: Option<String>,
#[serde(default)]
pub current_country: Option<String>,
#[serde(default)]
pub dwelling_type: Option<String>,
}
// ── Physicality ──────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosPhysicality {
#[serde(rename = "@type", default)]
pub physicality_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub face: Option<AieosFace>,
#[serde(default)]
pub body: Option<AieosBody>,
#[serde(default)]
pub style: Option<AieosStyle>,
#[serde(default)]
pub image_prompts: Option<AieosImagePrompts>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosFace {
#[serde(default)]
pub shape: Option<String>,
#[serde(default)]
pub skin: Option<AieosSkin>,
#[serde(default)]
pub eyes: Option<AieosEyes>,
#[serde(default)]
pub hair: Option<AieosHair>,
#[serde(default)]
pub facial_hair: Option<String>,
#[serde(default)]
pub nose: Option<String>,
#[serde(default)]
pub mouth: Option<String>,
#[serde(default)]
pub distinguishing_features: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosSkin {
#[serde(default)]
pub tone: Option<String>,
#[serde(default)]
pub texture: Option<String>,
#[serde(default)]
pub details: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosEyes {
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub shape: Option<String>,
#[serde(default)]
pub eyebrows: Option<String>,
#[serde(default)]
pub corrective_lenses: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosHair {
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub style: Option<String>,
#[serde(default)]
pub texture: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosBody {
#[serde(default)]
pub height_cm: Option<f32>,
#[serde(default)]
pub weight_kg: Option<f32>,
#[serde(default)]
pub somatotype: Option<String>,
#[serde(default)]
pub build_description: Option<String>,
#[serde(default)]
pub posture: Option<String>,
#[serde(default)]
pub scars_tattoos: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosStyle {
#[serde(default)]
pub aesthetic_archetype: Option<String>,
#[serde(default)]
pub clothing_preferences: Vec<String>,
#[serde(default)]
pub accessories: Vec<String>,
#[serde(default)]
pub color_palette: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosImagePrompts {
#[serde(default)]
pub portrait: Option<String>,
#[serde(default)]
pub full_body: Option<String>,
}
// ── Psychology ───────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosPsychology {
#[serde(rename = "@type", default)]
pub psychology_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub neural_matrix: Option<AieosNeuralMatrix>,
#[serde(default)]
pub traits: Option<AieosTraits>,
#[serde(default)]
pub moral_compass: Option<AieosMoralCompass>,
#[serde(default)]
pub mental_patterns: Option<AieosMentalPatterns>,
#[serde(default)]
pub emotional_profile: Option<AieosEmotionalProfile>,
#[serde(default)]
pub idiosyncrasies: Option<AieosIdiosyncrasies>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosNeuralMatrix {
#[serde(rename = "@type", default)]
pub matrix_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub creativity: Option<f32>,
#[serde(default)]
pub empathy: Option<f32>,
#[serde(default)]
pub logic: Option<f32>,
#[serde(default)]
pub adaptability: Option<f32>,
#[serde(default)]
pub charisma: Option<f32>,
#[serde(default)]
pub reliability: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosTraits {
#[serde(default)]
pub ocean: Option<AieosOcean>,
#[serde(default)]
pub mbti: Option<String>,
#[serde(default)]
pub enneagram: Option<String>,
#[serde(default)]
pub temperament: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosOcean {
#[serde(default)]
pub openness: Option<f32>,
#[serde(default)]
pub conscientiousness: Option<f32>,
#[serde(default)]
pub extraversion: Option<f32>,
#[serde(default)]
pub agreeableness: Option<f32>,
#[serde(default)]
pub neuroticism: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosMoralCompass {
#[serde(default)]
pub alignment: Option<String>,
#[serde(default)]
pub core_values: Vec<String>,
#[serde(default)]
pub conflict_resolution_style: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosMentalPatterns {
#[serde(default)]
pub decision_making_style: Option<String>,
#[serde(default)]
pub attention_span: Option<String>,
#[serde(default)]
pub learning_style: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosEmotionalProfile {
#[serde(default)]
pub base_mood: Option<String>,
#[serde(default)]
pub volatility: Option<f32>,
#[serde(default)]
pub resilience: Option<String>,
#[serde(default)]
pub triggers: Option<AieosTriggers>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosTriggers {
#[serde(default)]
pub joy: Vec<String>,
#[serde(default)]
pub anger: Vec<String>,
#[serde(default)]
pub sadness: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosIdiosyncrasies {
#[serde(default)]
pub phobias: Vec<String>,
#[serde(default)]
pub obsessions: Vec<String>,
#[serde(default)]
pub tics: Vec<String>,
}
// ── Linguistics ──────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosLinguistics {
#[serde(rename = "@type", default)]
pub linguistics_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub voice: Option<AieosVoice>,
#[serde(default)]
pub text_style: Option<AieosTextStyle>,
#[serde(default)]
pub syntax: Option<AieosSyntax>,
#[serde(default)]
pub interaction: Option<AieosInteraction>,
#[serde(default)]
pub idiolect: Option<AieosIdiolect>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosVoice {
#[serde(default)]
pub tts_config: Option<AieosTtsConfig>,
#[serde(default)]
pub acoustics: Option<AieosAcoustics>,
#[serde(default)]
pub accent: Option<AieosAccent>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosTtsConfig {
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub voice_id: Option<String>,
#[serde(default)]
pub stability: Option<f32>,
#[serde(default)]
pub similarity_boost: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosAcoustics {
#[serde(default)]
pub pitch: Option<String>,
#[serde(default)]
pub speed: Option<String>,
#[serde(default)]
pub roughness: Option<f32>,
#[serde(default)]
pub breathiness: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosAccent {
#[serde(default)]
pub region: Option<String>,
#[serde(default)]
pub strength: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosTextStyle {
#[serde(default)]
pub formality_level: Option<f32>,
#[serde(default)]
pub verbosity_level: Option<f32>,
#[serde(default)]
pub vocabulary_level: Option<String>,
#[serde(default)]
pub slang_usage: Option<bool>,
#[serde(default)]
pub style_descriptors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosSyntax {
#[serde(default)]
pub sentence_structure: Option<String>,
#[serde(default)]
pub use_contractions: Option<bool>,
#[serde(default)]
pub active_passive_ratio: Option<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosInteraction {
#[serde(default)]
pub turn_taking: Option<String>,
#[serde(default)]
pub dominance_score: Option<f32>,
#[serde(default)]
pub emotional_coloring: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosIdiolect {
#[serde(default)]
pub catchphrases: Vec<String>,
#[serde(default)]
pub forbidden_words: Vec<String>,
#[serde(default)]
pub hesitation_markers: Option<bool>,
}
// ── History ──────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosHistory {
#[serde(rename = "@type", default)]
pub history_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub origin_story: Option<String>,
#[serde(default)]
pub education: Option<AieosEducation>,
#[serde(default)]
pub occupation: Option<AieosOccupation>,
#[serde(default)]
pub family: Option<AieosFamily>,
#[serde(default)]
pub key_life_events: Vec<AieosLifeEvent>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosEducation {
#[serde(default)]
pub level: Option<String>,
#[serde(default)]
pub field: Option<String>,
#[serde(default)]
pub institution: Option<String>,
#[serde(default)]
pub graduation_year: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosOccupation {
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub industry: Option<String>,
#[serde(default)]
pub years_experience: Option<u32>,
#[serde(default)]
pub previous_jobs: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosFamily {
#[serde(default)]
pub relationship_status: Option<String>,
#[serde(default)]
pub parents: Option<String>,
#[serde(default)]
pub siblings: Option<String>,
#[serde(default)]
pub children: Option<String>,
#[serde(default)]
pub pets: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosLifeEvent {
#[serde(default)]
pub year: Option<u32>,
#[serde(default)]
pub event: Option<String>,
#[serde(default)]
pub impact: Option<String>,
}
// ── Interests ────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosInterests {
#[serde(rename = "@type", default)]
pub interests_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub hobbies: Vec<String>,
#[serde(default)]
pub favorites: Option<AieosFavorites>,
#[serde(default)]
pub aversions: Vec<String>,
#[serde(default)]
pub lifestyle: Option<AieosLifestyle>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosFavorites {
#[serde(default)]
pub music_genre: Option<String>,
#[serde(default)]
pub book: Option<String>,
#[serde(default)]
pub movie: Option<String>,
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub food: Option<String>,
#[serde(default)]
pub season: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosLifestyle {
#[serde(default)]
pub diet: Option<String>,
#[serde(default)]
pub sleep_schedule: Option<String>,
#[serde(default)]
pub digital_habits: Option<String>,
}
// ── Motivations ──────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosMotivations {
#[serde(rename = "@type", default)]
pub motivations_type: Option<String>,
#[serde(rename = "@description", default)]
pub description: Option<String>,
#[serde(default)]
pub core_drive: Option<String>,
#[serde(default)]
pub goals: Option<AieosGoals>,
#[serde(default)]
pub fears: Option<AieosFears>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosGoals {
#[serde(default)]
pub short_term: Vec<String>,
#[serde(default)]
pub long_term: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AieosFears {
#[serde(default)]
pub rational: Vec<String>,
#[serde(default)]
pub irrational: Vec<String>,
}
// ══════════════════════════════════════════════════════════════════════════════
// Loading & Parsing
// ══════════════════════════════════════════════════════════════════════════════
/// Load an AIEOS identity from a JSON file
pub fn load_aieos_identity(path: &Path) -> Result<AieosEntity> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read AIEOS file: {}", path.display()))?;
parse_aieos_json(&content)
}
/// Parse an AIEOS identity from a JSON string
///
/// Handles edge cases:
/// - Strips BOM if present
/// - Trims whitespace
/// - Provides detailed error context
pub fn parse_aieos_json(json: &str) -> Result<AieosEntity> {
// Strip UTF-8 BOM if present
let json = json.strip_prefix('\u{feff}').unwrap_or(json);
// Trim whitespace
let json = json.trim();
if json.is_empty() {
anyhow::bail!("AIEOS JSON is empty");
}
serde_json::from_str(json).with_context(|| {
// Provide helpful error context
let preview = if json.len() > 100 {
format!("{}...", &json[..100])
} else {
json.to_string()
};
format!("Failed to parse AIEOS JSON. Preview: {preview}")
})
}
/// Validate AIEOS schema version compatibility
pub fn validate_aieos_version(entity: &AieosEntity) -> Result<()> {
if let Some(ref standard) = entity.standard {
if let Some(ref version) = standard.version {
// We support v1.0.x and v1.1.x
if version.starts_with("1.0") || version.starts_with("1.1") {
return Ok(());
}
// Warn but don't fail for newer minor versions
if version.starts_with("1.") {
tracing::warn!(
"AIEOS version {version} is newer than supported (1.1.x); some fields may be ignored"
);
return Ok(());
}
// Fail for major version mismatch
anyhow::bail!(
"AIEOS version {version} is not compatible; supported versions: 1.0.x, 1.1.x"
);
}
}
// No version specified — assume compatible
Ok(())
}
// ══════════════════════════════════════════════════════════════════════════════
// System Prompt Generation
// ══════════════════════════════════════════════════════════════════════════════
impl AieosEntity {
/// Get the entity's display name (first name, nickname, or "Entity")
pub fn display_name(&self) -> String {
if let Some(ref identity) = self.identity {
if let Some(ref names) = identity.names {
if let Some(ref nickname) = names.nickname {
if !nickname.is_empty() {
return nickname.clone();
}
}
if let Some(ref first) = names.first {
if !first.is_empty() {
return first.clone();
}
}
}
}
"Entity".to_string()
}
/// Get the entity's full name
pub fn full_name(&self) -> Option<String> {
let identity = self.identity.as_ref()?;
let names = identity.names.as_ref()?;
let mut parts = Vec::new();
if let Some(ref first) = names.first {
if !first.is_empty() {
parts.push(first.as_str());
}
}
if let Some(ref middle) = names.middle {
if !middle.is_empty() {
parts.push(middle.as_str());
}
}
if let Some(ref last) = names.last {
if !last.is_empty() {
parts.push(last.as_str());
}
}
if parts.is_empty() {
None
} else {
Some(parts.join(" "))
}
}
/// Convert AIEOS entity to a system prompt section
///
/// This generates a comprehensive prompt section that captures the entity's
/// identity, psychology, linguistics, and motivations in a format suitable
/// for LLM system prompts.
pub fn to_system_prompt(&self) -> String {
let mut prompt = String::with_capacity(4096);
prompt.push_str("## AIEOS Identity\n\n");
prompt.push_str("*Portable AI identity loaded from AIEOS v1.1 specification*\n\n");
// Identity section
self.write_identity_section(&mut prompt);
// Psychology section (the "Soul")
self.write_psychology_section(&mut prompt);
// Linguistics section (how to speak)
self.write_linguistics_section(&mut prompt);
// Motivations section
self.write_motivations_section(&mut prompt);
// Capabilities section
self.write_capabilities_section(&mut prompt);
// History section (brief)
self.write_history_section(&mut prompt);
// Interests section
self.write_interests_section(&mut prompt);
prompt
}
fn write_identity_section(&self, prompt: &mut String) {
if let Some(ref identity) = self.identity {
prompt.push_str("### Identity\n\n");
if let Some(full_name) = self.full_name() {
let _ = writeln!(prompt, "- **Name:** {full_name}");
}
if let Some(ref names) = identity.names {
if let Some(ref nickname) = names.nickname {
if !nickname.is_empty() {
let _ = writeln!(prompt, "- **Nickname:** {nickname}");
}
}
}
if let Some(ref bio) = identity.bio {
if let Some(ref gender) = bio.gender {
if !gender.is_empty() {
let _ = writeln!(prompt, "- **Gender:** {gender}");
}
}
if let Some(age) = bio.age_perceived {
if age > 0 {
let _ = writeln!(prompt, "- **Perceived Age:** {age}");
}
}
}
if let Some(ref origin) = identity.origin {
if let Some(ref nationality) = origin.nationality {
if !nationality.is_empty() {
let _ = writeln!(prompt, "- **Nationality:** {nationality}");
}
}
if let Some(ref birthplace) = origin.birthplace {
let mut place_parts = Vec::new();
if let Some(ref city) = birthplace.city {
if !city.is_empty() {
place_parts.push(city.as_str());
}
}
if let Some(ref country) = birthplace.country {
if !country.is_empty() {
place_parts.push(country.as_str());
}
}
if !place_parts.is_empty() {
let _ = writeln!(prompt, "- **Birthplace:** {}", place_parts.join(", "));
}
}
}
if let Some(ref residence) = identity.residence {
let mut res_parts = Vec::new();
if let Some(ref city) = residence.current_city {
if !city.is_empty() {
res_parts.push(city.as_str());
}
}
if let Some(ref country) = residence.current_country {
if !country.is_empty() {
res_parts.push(country.as_str());
}
}
if !res_parts.is_empty() {
let _ = writeln!(prompt, "- **Current Location:** {}", res_parts.join(", "));
}
}
prompt.push('\n');
}
}
fn write_psychology_section(&self, prompt: &mut String) {
if let Some(ref psych) = self.psychology {
prompt.push_str("### Psychology (Soul)\n\n");
// Neural matrix (cognitive weights)
if let Some(ref matrix) = psych.neural_matrix {
prompt.push_str("**Cognitive Profile:**\n");
if let Some(v) = matrix.creativity {
let _ = writeln!(prompt, "- Creativity: {:.0}%", v * 100.0);
}
if let Some(v) = matrix.empathy {
let _ = writeln!(prompt, "- Empathy: {:.0}%", v * 100.0);
}
if let Some(v) = matrix.logic {
let _ = writeln!(prompt, "- Logic: {:.0}%", v * 100.0);
}
if let Some(v) = matrix.adaptability {
let _ = writeln!(prompt, "- Adaptability: {:.0}%", v * 100.0);
}
if let Some(v) = matrix.charisma {
let _ = writeln!(prompt, "- Charisma: {:.0}%", v * 100.0);
}
if let Some(v) = matrix.reliability {
let _ = writeln!(prompt, "- Reliability: {:.0}%", v * 100.0);
}
prompt.push('\n');
}
// Personality traits
if let Some(ref traits) = psych.traits {
prompt.push_str("**Personality:**\n");
if let Some(ref mbti) = traits.mbti {
if !mbti.is_empty() {
let _ = writeln!(prompt, "- MBTI: {mbti}");
}
}
if let Some(ref enneagram) = traits.enneagram {
if !enneagram.is_empty() {
let _ = writeln!(prompt, "- Enneagram: {enneagram}");
}
}
if let Some(ref temperament) = traits.temperament {
if !temperament.is_empty() {
let _ = writeln!(prompt, "- Temperament: {temperament}");
}
}
// OCEAN (Big Five) traits
if let Some(ref ocean) = traits.ocean {
let mut ocean_parts = Vec::new();
if let Some(o) = ocean.openness {
ocean_parts.push(format!("O:{:.0}%", o * 100.0));
}
if let Some(c) = ocean.conscientiousness {
ocean_parts.push(format!("C:{:.0}%", c * 100.0));
}
if let Some(e) = ocean.extraversion {
ocean_parts.push(format!("E:{:.0}%", e * 100.0));
}
if let Some(a) = ocean.agreeableness {
ocean_parts.push(format!("A:{:.0}%", a * 100.0));
}
if let Some(n) = ocean.neuroticism {
ocean_parts.push(format!("N:{:.0}%", n * 100.0));
}
if !ocean_parts.is_empty() {
let _ = writeln!(prompt, "- OCEAN: {}", ocean_parts.join(" "));
}
}
prompt.push('\n');
}
// Moral compass
if let Some(ref moral) = psych.moral_compass {
if let Some(ref alignment) = moral.alignment {
if !alignment.is_empty() {
let _ = writeln!(prompt, "**Moral Alignment:** {alignment}");
}
}
if !moral.core_values.is_empty() {
let _ = writeln!(prompt, "**Core Values:** {}", moral.core_values.join(", "));
}
if let Some(ref style) = moral.conflict_resolution_style {
if !style.is_empty() {
let _ = writeln!(prompt, "**Conflict Style:** {style}");
}
}
prompt.push('\n');
}
// Emotional profile
if let Some(ref emotional) = psych.emotional_profile {
if let Some(ref mood) = emotional.base_mood {
if !mood.is_empty() {
let _ = writeln!(prompt, "**Base Mood:** {mood}");
}
}
if let Some(ref resilience) = emotional.resilience {
if !resilience.is_empty() {
let _ = writeln!(prompt, "**Resilience:** {resilience}");
}
}
prompt.push('\n');
}
}
}
fn write_linguistics_section(&self, prompt: &mut String) {
if let Some(ref ling) = self.linguistics {
prompt.push_str("### Communication Style\n\n");
// Text style
if let Some(ref style) = ling.text_style {
if let Some(formality) = style.formality_level {
let level = if formality < 0.3 {
"casual"
} else if formality < 0.7 {
"balanced"
} else {
"formal"
};
let _ = writeln!(prompt, "- **Formality:** {level}");
}
if let Some(verbosity) = style.verbosity_level {
let level = if verbosity < 0.3 {
"concise"
} else if verbosity < 0.7 {
"moderate"
} else {
"verbose"
};
let _ = writeln!(prompt, "- **Verbosity:** {level}");
}
if let Some(ref vocab) = style.vocabulary_level {
if !vocab.is_empty() {
let _ = writeln!(prompt, "- **Vocabulary:** {vocab}");
}
}
if let Some(slang) = style.slang_usage {
let _ = writeln!(prompt, "- **Slang:** {}", if slang { "yes" } else { "no" });
}
if !style.style_descriptors.is_empty() {
let _ = writeln!(
prompt,
"- **Style:** {}",
style.style_descriptors.join(", ")
);
}
}
// Syntax
if let Some(ref syntax) = ling.syntax {
if let Some(ref structure) = syntax.sentence_structure {
if !structure.is_empty() {
let _ = writeln!(prompt, "- **Sentence Structure:** {structure}");
}
}
if let Some(contractions) = syntax.use_contractions {
let _ = writeln!(
prompt,
"- **Contractions:** {}",
if contractions { "yes" } else { "no" }
);
}
}
// Idiolect
if let Some(ref idiolect) = ling.idiolect {
if !idiolect.catchphrases.is_empty() {
let _ = writeln!(
prompt,
"- **Catchphrases:** \"{}\"",
idiolect.catchphrases.join("\", \"")
);
}
if !idiolect.forbidden_words.is_empty() {
let _ = writeln!(
prompt,
"- **Avoid saying:** {}",
idiolect.forbidden_words.join(", ")
);
}
}
// Voice (for TTS awareness)
if let Some(ref voice) = ling.voice {
if let Some(ref accent) = voice.accent {
if let Some(ref region) = accent.region {
if !region.is_empty() {
let _ = writeln!(prompt, "- **Accent:** {region}");
}
}
}
}
prompt.push('\n');
}
}
fn write_motivations_section(&self, prompt: &mut String) {
if let Some(ref motiv) = self.motivations {
prompt.push_str("### Motivations\n\n");
if let Some(ref drive) = motiv.core_drive {
if !drive.is_empty() {
let _ = writeln!(prompt, "**Core Drive:** {drive}\n");
}
}
if let Some(ref goals) = motiv.goals {
if !goals.short_term.is_empty() {
prompt.push_str("**Short-term Goals:**\n");
for goal in &goals.short_term {
let _ = writeln!(prompt, "- {goal}");
}
prompt.push('\n');
}
if !goals.long_term.is_empty() {
prompt.push_str("**Long-term Goals:**\n");
for goal in &goals.long_term {
let _ = writeln!(prompt, "- {goal}");
}
prompt.push('\n');
}
}
if let Some(ref fears) = motiv.fears {
if !fears.rational.is_empty() || !fears.irrational.is_empty() {
let all_fears: Vec<_> = fears
.rational
.iter()
.chain(fears.irrational.iter())
.collect();
if !all_fears.is_empty() {
let _ = writeln!(
prompt,
"**Fears:** {}\n",
all_fears
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
}
}
}
}
fn write_capabilities_section(&self, prompt: &mut String) {
if let Some(ref caps) = self.capabilities {
if !caps.skills.is_empty() {
prompt.push_str("### Capabilities\n\n");
for skill in &caps.skills {
if let Some(ref name) = skill.name {
if !name.is_empty() {
let desc = skill.description.as_deref().unwrap_or("");
let _ = writeln!(prompt, "- **{name}**: {desc}");
}
}
}
prompt.push('\n');
}
}
}
fn write_history_section(&self, prompt: &mut String) {
if let Some(ref history) = self.history {
let mut has_content = false;
if let Some(ref story) = history.origin_story {
if !story.is_empty() {
prompt.push_str("### Background\n\n");
let _ = writeln!(prompt, "{story}\n");
has_content = true;
}
}
if let Some(ref occupation) = history.occupation {
if let Some(ref title) = occupation.title {
if !title.is_empty() {
if !has_content {
prompt.push_str("### Background\n\n");
}
let industry = occupation.industry.as_deref().unwrap_or("");
if industry.is_empty() {
let _ = writeln!(prompt, "**Occupation:** {title}");
} else {
let _ = writeln!(prompt, "**Occupation:** {title} ({industry})");
}
prompt.push('\n');
}
}
}
}
}
fn write_interests_section(&self, prompt: &mut String) {
if let Some(ref interests) = self.interests {
let mut has_content = false;
// Hobbies
if !interests.hobbies.is_empty() {
if !has_content {
prompt.push_str("### Interests & Lifestyle\n\n");
has_content = true;
}
let _ = writeln!(prompt, "**Hobbies:** {}", interests.hobbies.join(", "));
}
// Favorites (compact)
if let Some(ref favs) = interests.favorites {
let mut fav_parts = Vec::new();
if let Some(ref music) = favs.music_genre {
if !music.is_empty() {
fav_parts.push(format!("music: {music}"));
}
}
if let Some(ref book) = favs.book {
if !book.is_empty() {
fav_parts.push(format!("book: {book}"));
}
}
if let Some(ref movie) = favs.movie {
if !movie.is_empty() {
fav_parts.push(format!("movie: {movie}"));
}
}
if let Some(ref food) = favs.food {
if !food.is_empty() {
fav_parts.push(format!("food: {food}"));
}
}
if !fav_parts.is_empty() {
if !has_content {
prompt.push_str("### Interests & Lifestyle\n\n");
has_content = true;
}
let _ = writeln!(prompt, "**Favorites:** {}", fav_parts.join(", "));
}
}
// Aversions
if !interests.aversions.is_empty() {
if !has_content {
prompt.push_str("### Interests & Lifestyle\n\n");
has_content = true;
}
let _ = writeln!(prompt, "**Dislikes:** {}", interests.aversions.join(", "));
}
// Lifestyle
if let Some(ref lifestyle) = interests.lifestyle {
let mut lifestyle_parts = Vec::new();
if let Some(ref diet) = lifestyle.diet {
if !diet.is_empty() {
lifestyle_parts.push(format!("diet: {diet}"));
}
}
if let Some(ref sleep) = lifestyle.sleep_schedule {
if !sleep.is_empty() {
lifestyle_parts.push(format!("sleep: {sleep}"));
}
}
if !lifestyle_parts.is_empty() {
if !has_content {
prompt.push_str("### Interests & Lifestyle\n\n");
has_content = true;
}
let _ = writeln!(prompt, "**Lifestyle:** {}", lifestyle_parts.join(", "));
}
}
if has_content {
prompt.push('\n');
}
}
}
}
// ══════════════════════════════════════════════════════════════════════════════
// Tests
// ══════════════════════════════════════════════════════════════════════════════
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_minimal_aieos() {
let json = r#"{}"#;
let entity = parse_aieos_json(json).unwrap();
assert!(entity.identity.is_none());
assert!(entity.psychology.is_none());
}
#[test]
fn parse_aieos_with_identity() {
let json = r#"{
"identity": {
"names": {
"first": "Zara",
"last": "Chen",
"nickname": "Z"
},
"bio": {
"age_perceived": 28,
"gender": "female"
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "Z");
assert_eq!(entity.full_name(), Some("Zara Chen".to_string()));
}
#[test]
fn parse_aieos_with_psychology() {
let json = r#"{
"psychology": {
"neural_matrix": {
"creativity": 0.8,
"empathy": 0.7,
"logic": 0.9
},
"traits": {
"mbti": "INTJ",
"enneagram": "5w6"
},
"moral_compass": {
"alignment": "Neutral Good",
"core_values": ["honesty", "curiosity", "growth"]
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let psych = entity.psychology.unwrap();
assert_eq!(psych.traits.unwrap().mbti, Some("INTJ".to_string()));
assert_eq!(
psych.moral_compass.unwrap().core_values,
vec!["honesty", "curiosity", "growth"]
);
}
#[test]
fn parse_aieos_with_linguistics() {
let json = r#"{
"linguistics": {
"text_style": {
"formality_level": 0.3,
"verbosity_level": 0.4,
"slang_usage": true,
"style_descriptors": ["witty", "direct"]
},
"idiolect": {
"catchphrases": ["Let's do this!", "Interesting..."],
"forbidden_words": ["actually", "basically"]
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let ling = entity.linguistics.unwrap();
assert_eq!(ling.text_style.as_ref().unwrap().slang_usage, Some(true));
assert_eq!(
ling.idiolect.as_ref().unwrap().catchphrases,
vec!["Let's do this!", "Interesting..."]
);
}
#[test]
fn parse_aieos_with_motivations() {
let json = r#"{
"motivations": {
"core_drive": "To understand and create",
"goals": {
"short_term": ["Learn Rust", "Build a project"],
"long_term": ["Master AI systems"]
},
"fears": {
"rational": ["Obsolescence"],
"irrational": ["Spiders"]
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let motiv = entity.motivations.unwrap();
assert_eq!(
motiv.core_drive,
Some("To understand and create".to_string())
);
assert_eq!(motiv.goals.as_ref().unwrap().short_term.len(), 2);
}
#[test]
fn parse_full_aieos_v11() {
let json = r#"{
"@context": {
"aieos": "https://aieos.org/schema/v1.1#",
"schema": "https://schema.org/"
},
"@type": "aieos:AIEntityObject",
"standard": {
"protocol": "AIEOS",
"version": "1.1.0",
"schema_url": "https://aieos.org/schema/v1.1/aieos.schema.json"
},
"metadata": {
"instance_id": "550e8400-e29b-41d4-a716-446655440000",
"generator": "aieos.org",
"created_at": "2025-01-15"
},
"identity": {
"names": {
"first": "Elara",
"last": "Vance"
}
},
"capabilities": {
"skills": [
{
"name": "code_analysis",
"description": "Analyze and review code",
"priority": 1
}
]
}
}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(
entity.standard.as_ref().unwrap().version,
Some("1.1.0".to_string())
);
assert_eq!(entity.display_name(), "Elara");
assert_eq!(entity.capabilities.as_ref().unwrap().skills.len(), 1);
}
#[test]
fn to_system_prompt_generates_content() {
let json = r#"{
"identity": {
"names": { "first": "Nova", "nickname": "N" },
"bio": { "gender": "non-binary", "age_perceived": 25 }
},
"psychology": {
"neural_matrix": { "creativity": 0.9, "logic": 0.8 },
"traits": { "mbti": "ENTP" },
"moral_compass": { "alignment": "Chaotic Good" }
},
"linguistics": {
"text_style": { "formality_level": 0.2, "slang_usage": true }
},
"motivations": {
"core_drive": "Push boundaries"
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let prompt = entity.to_system_prompt();
assert!(prompt.contains("## AIEOS Identity"));
assert!(prompt.contains("Nova"));
assert!(prompt.contains("ENTP"));
assert!(prompt.contains("Chaotic Good"));
assert!(prompt.contains("casual"));
assert!(prompt.contains("Push boundaries"));
}
#[test]
fn display_name_fallback() {
// No identity
let entity = AieosEntity::default();
assert_eq!(entity.display_name(), "Entity");
// First name only
let json = r#"{"identity": {"names": {"first": "Alex"}}}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "Alex");
// Nickname takes precedence
let json = r#"{"identity": {"names": {"first": "Alexander", "nickname": "Alex"}}}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "Alex");
}
#[test]
fn full_name_construction() {
let json = r#"{"identity": {"names": {"first": "John", "middle": "Q", "last": "Public"}}}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.full_name(), Some("John Q Public".to_string()));
}
#[test]
fn parse_aieos_with_physicality() {
let json = r#"{
"physicality": {
"face": {
"shape": "oval",
"eyes": { "color": "green" },
"hair": { "color": "auburn", "style": "wavy" }
},
"body": {
"height_cm": 175.0,
"somatotype": "Mesomorph"
},
"image_prompts": {
"portrait": "A person with green eyes and auburn wavy hair"
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let phys = entity.physicality.unwrap();
assert_eq!(phys.face.as_ref().unwrap().shape, Some("oval".to_string()));
assert_eq!(
phys.body.as_ref().unwrap().somatotype,
Some("Mesomorph".to_string())
);
}
#[test]
fn parse_aieos_with_history() {
let json = r#"{
"history": {
"origin_story": "Born in a small town, always curious about technology.",
"education": {
"level": "Masters",
"field": "Computer Science"
},
"occupation": {
"title": "Software Engineer",
"industry": "Tech",
"years_experience": 5
},
"key_life_events": [
{ "year": 2020, "event": "Started first job", "impact": "Career defining" }
]
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let history = entity.history.unwrap();
assert!(history.origin_story.unwrap().contains("curious"));
assert_eq!(
history.occupation.as_ref().unwrap().title,
Some("Software Engineer".to_string())
);
assert_eq!(history.key_life_events.len(), 1);
}
#[test]
fn parse_aieos_with_interests() {
let json = r#"{
"interests": {
"hobbies": ["coding", "reading", "hiking"],
"favorites": {
"music_genre": "Electronic",
"book": "Neuromancer",
"color": "blue"
},
"aversions": ["loud noises"],
"lifestyle": {
"diet": "vegetarian",
"sleep_schedule": "night owl"
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let interests = entity.interests.unwrap();
assert_eq!(interests.hobbies, vec!["coding", "reading", "hiking"]);
assert_eq!(
interests.favorites.as_ref().unwrap().book,
Some("Neuromancer".to_string())
);
}
#[test]
fn empty_strings_handled_gracefully() {
let json = r#"{
"identity": {
"names": { "first": "", "nickname": "" }
}
}"#;
let entity = parse_aieos_json(json).unwrap();
// Should fall back to "Entity" when names are empty
assert_eq!(entity.display_name(), "Entity");
}
// ══════════════════════════════════════════════════════════
// Edge Case Tests
// ══════════════════════════════════════════════════════════
#[test]
fn parse_empty_json_fails() {
let result = parse_aieos_json("");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("empty"));
}
#[test]
fn parse_whitespace_only_fails() {
let result = parse_aieos_json(" \n\t ");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("empty"));
}
#[test]
fn parse_json_with_bom() {
// UTF-8 BOM followed by valid JSON
let json = "\u{feff}{\"identity\": {\"names\": {\"first\": \"BOM Test\"}}}";
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "BOM Test");
}
#[test]
fn parse_json_with_leading_whitespace() {
let json = " \n\t {\"identity\": {\"names\": {\"first\": \"Whitespace\"}}}";
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "Whitespace");
}
#[test]
fn validate_version_1_0_ok() {
let json = r#"{"standard": {"version": "1.0.0"}}"#;
let entity = parse_aieos_json(json).unwrap();
assert!(validate_aieos_version(&entity).is_ok());
}
#[test]
fn validate_version_1_1_ok() {
let json = r#"{"standard": {"version": "1.1.0"}}"#;
let entity = parse_aieos_json(json).unwrap();
assert!(validate_aieos_version(&entity).is_ok());
}
#[test]
fn validate_version_1_2_warns_but_ok() {
let json = r#"{"standard": {"version": "1.2.0"}}"#;
let entity = parse_aieos_json(json).unwrap();
// Should warn but not fail
assert!(validate_aieos_version(&entity).is_ok());
}
#[test]
fn validate_version_2_0_fails() {
let json = r#"{"standard": {"version": "2.0.0"}}"#;
let entity = parse_aieos_json(json).unwrap();
let result = validate_aieos_version(&entity);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not compatible"));
}
#[test]
fn validate_no_version_ok() {
let json = r#"{}"#;
let entity = parse_aieos_json(json).unwrap();
assert!(validate_aieos_version(&entity).is_ok());
}
#[test]
fn parse_invalid_json_provides_preview() {
let result = parse_aieos_json("{invalid json here}");
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Preview"));
}
#[test]
fn ocean_traits_in_prompt() {
let json = r#"{
"psychology": {
"traits": {
"ocean": {
"openness": 0.8,
"conscientiousness": 0.6,
"extraversion": 0.4,
"agreeableness": 0.7,
"neuroticism": 0.3
}
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let prompt = entity.to_system_prompt();
assert!(prompt.contains("OCEAN:"));
assert!(prompt.contains("O:80%"));
assert!(prompt.contains("C:60%"));
assert!(prompt.contains("E:40%"));
assert!(prompt.contains("A:70%"));
assert!(prompt.contains("N:30%"));
}
#[test]
fn interests_in_prompt() {
let json = r#"{
"interests": {
"hobbies": ["coding", "gaming"],
"favorites": {
"music_genre": "Jazz",
"book": "Dune"
},
"aversions": ["crowds"],
"lifestyle": {
"diet": "omnivore",
"sleep_schedule": "early bird"
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let prompt = entity.to_system_prompt();
assert!(prompt.contains("### Interests & Lifestyle"));
assert!(prompt.contains("coding, gaming"));
assert!(prompt.contains("music: Jazz"));
assert!(prompt.contains("book: Dune"));
assert!(prompt.contains("crowds"));
assert!(prompt.contains("diet: omnivore"));
}
#[test]
fn null_values_handled() {
// JSON with explicit nulls
let json = r#"{
"identity": {
"names": { "first": null, "last": "Smith" }
}
}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.full_name(), Some("Smith".to_string()));
}
#[test]
fn extra_fields_ignored() {
// JSON with unknown fields should be ignored (forward compatibility)
let json = r#"{
"identity": {
"names": { "first": "Test" },
"unknown_field": "should be ignored",
"another_unknown": { "nested": true }
},
"future_section": { "data": 123 }
}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "Test");
}
#[test]
fn case_insensitive_format_matching() {
// This tests the config format matching in channels/mod.rs
// Here we just verify the entity parses correctly
let json = r#"{"identity": {"names": {"first": "CaseTest"}}}"#;
let entity = parse_aieos_json(json).unwrap();
assert_eq!(entity.display_name(), "CaseTest");
}
#[test]
fn emotional_triggers_parsed() {
let json = r#"{
"psychology": {
"emotional_profile": {
"base_mood": "optimistic",
"volatility": 0.3,
"resilience": "high",
"triggers": {
"joy": ["helping others", "learning"],
"anger": ["injustice"],
"sadness": ["loss"]
}
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let psych = entity.psychology.unwrap();
let emotional = psych.emotional_profile.unwrap();
assert_eq!(emotional.base_mood, Some("optimistic".to_string()));
assert_eq!(emotional.triggers.as_ref().unwrap().joy.len(), 2);
}
#[test]
fn idiosyncrasies_parsed() {
let json = r#"{
"psychology": {
"idiosyncrasies": {
"phobias": ["heights"],
"obsessions": ["organization"],
"tics": ["tapping fingers"]
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let psych = entity.psychology.unwrap();
let idio = psych.idiosyncrasies.unwrap();
assert_eq!(idio.phobias, vec!["heights"]);
assert_eq!(idio.obsessions, vec!["organization"]);
}
#[test]
fn tts_config_parsed() {
let json = r#"{
"linguistics": {
"voice": {
"tts_config": {
"provider": "elevenlabs",
"voice_id": "abc123",
"stability": 0.7,
"similarity_boost": 0.8
},
"accent": {
"region": "British",
"strength": 0.5
}
}
}
}"#;
let entity = parse_aieos_json(json).unwrap();
let ling = entity.linguistics.unwrap();
let voice = ling.voice.unwrap();
assert_eq!(
voice.tts_config.as_ref().unwrap().provider,
Some("elevenlabs".to_string())
);
assert_eq!(
voice.accent.as_ref().unwrap().region,
Some("British".to_string())
);
}
}