//! AIEOS (AI Entity Object Specification) v1.1 support //! //! AIEOS is a standardization framework for portable AI identity. //! See: //! //! 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, /// Entity type marker #[serde(rename = "@type", default)] pub entity_type: Option, /// Protocol standard info #[serde(default)] pub standard: Option, /// Internal tracking metadata #[serde(default)] pub metadata: Option, /// Standardized skills and tools #[serde(default)] pub capabilities: Option, /// Core biographical data #[serde(default)] pub identity: Option, /// Visual descriptors for image generation #[serde(default)] pub physicality: Option, /// The "Soul" layer — cognitive weights, traits, moral boundaries #[serde(default)] pub psychology: Option, /// How the entity speaks — voice and text style #[serde(default)] pub linguistics: Option, /// Origin story, education, occupation #[serde(default)] pub history: Option, /// Preferences, hobbies, lifestyle #[serde(default)] pub interests: Option, /// Goals and core drives #[serde(default)] pub motivations: Option, } // ── Context & Standard ─────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosContext { #[serde(default)] pub aieos: Option, #[serde(default)] pub schema: Option, #[serde(default)] pub xsd: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosStandard { #[serde(default)] pub protocol: Option, #[serde(default)] pub version: Option, #[serde(default)] pub schema_url: Option, } // ── Metadata ───────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosMetadata { #[serde(rename = "@type", default)] pub metadata_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub instance_id: Option, #[serde(default)] pub instance_version: Option, #[serde(default)] pub generator: Option, #[serde(default)] pub created_at: Option, #[serde(default)] pub last_updated: Option, } // ── Capabilities ───────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosCapabilities { #[serde(rename = "@type", default)] pub capabilities_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub skills: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosSkill { #[serde(rename = "@type", default)] pub skill_type: Option, #[serde(default)] pub name: Option, #[serde(default)] pub description: Option, #[serde(default)] pub uri: Option, #[serde(default)] pub version: Option, #[serde(default)] pub auto_activate: Option, #[serde(default)] pub priority: Option, } // ── Identity ───────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosIdentity { #[serde(rename = "@type", default)] pub identity_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub names: Option, #[serde(default)] pub bio: Option, #[serde(default)] pub origin: Option, #[serde(default)] pub residence: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosNames { #[serde(default)] pub first: Option, #[serde(default)] pub middle: Option, #[serde(default)] pub last: Option, #[serde(default)] pub nickname: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosBio { #[serde(rename = "@type", default)] pub bio_type: Option, #[serde(default)] pub birthday: Option, #[serde(default)] pub age_biological: Option, #[serde(default)] pub age_perceived: Option, #[serde(default)] pub gender: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosOrigin { #[serde(default)] pub nationality: Option, #[serde(default)] pub ethnicity: Option, #[serde(default)] pub birthplace: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosPlace { #[serde(rename = "@type", default)] pub place_type: Option, #[serde(default)] pub city: Option, #[serde(default)] pub country: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosResidence { #[serde(rename = "@type", default)] pub residence_type: Option, #[serde(default)] pub current_city: Option, #[serde(default)] pub current_country: Option, #[serde(default)] pub dwelling_type: Option, } // ── Physicality ────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosPhysicality { #[serde(rename = "@type", default)] pub physicality_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub face: Option, #[serde(default)] pub body: Option, #[serde(default)] pub style: Option, #[serde(default)] pub image_prompts: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosFace { #[serde(default)] pub shape: Option, #[serde(default)] pub skin: Option, #[serde(default)] pub eyes: Option, #[serde(default)] pub hair: Option, #[serde(default)] pub facial_hair: Option, #[serde(default)] pub nose: Option, #[serde(default)] pub mouth: Option, #[serde(default)] pub distinguishing_features: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosSkin { #[serde(default)] pub tone: Option, #[serde(default)] pub texture: Option, #[serde(default)] pub details: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosEyes { #[serde(default)] pub color: Option, #[serde(default)] pub shape: Option, #[serde(default)] pub eyebrows: Option, #[serde(default)] pub corrective_lenses: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosHair { #[serde(default)] pub color: Option, #[serde(default)] pub style: Option, #[serde(default)] pub texture: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosBody { #[serde(default)] pub height_cm: Option, #[serde(default)] pub weight_kg: Option, #[serde(default)] pub somatotype: Option, #[serde(default)] pub build_description: Option, #[serde(default)] pub posture: Option, #[serde(default)] pub scars_tattoos: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosStyle { #[serde(default)] pub aesthetic_archetype: Option, #[serde(default)] pub clothing_preferences: Vec, #[serde(default)] pub accessories: Vec, #[serde(default)] pub color_palette: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosImagePrompts { #[serde(default)] pub portrait: Option, #[serde(default)] pub full_body: Option, } // ── Psychology ─────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosPsychology { #[serde(rename = "@type", default)] pub psychology_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub neural_matrix: Option, #[serde(default)] pub traits: Option, #[serde(default)] pub moral_compass: Option, #[serde(default)] pub mental_patterns: Option, #[serde(default)] pub emotional_profile: Option, #[serde(default)] pub idiosyncrasies: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosNeuralMatrix { #[serde(rename = "@type", default)] pub matrix_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub creativity: Option, #[serde(default)] pub empathy: Option, #[serde(default)] pub logic: Option, #[serde(default)] pub adaptability: Option, #[serde(default)] pub charisma: Option, #[serde(default)] pub reliability: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosTraits { #[serde(default)] pub ocean: Option, #[serde(default)] pub mbti: Option, #[serde(default)] pub enneagram: Option, #[serde(default)] pub temperament: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosOcean { #[serde(default)] pub openness: Option, #[serde(default)] pub conscientiousness: Option, #[serde(default)] pub extraversion: Option, #[serde(default)] pub agreeableness: Option, #[serde(default)] pub neuroticism: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosMoralCompass { #[serde(default)] pub alignment: Option, #[serde(default)] pub core_values: Vec, #[serde(default)] pub conflict_resolution_style: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosMentalPatterns { #[serde(default)] pub decision_making_style: Option, #[serde(default)] pub attention_span: Option, #[serde(default)] pub learning_style: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosEmotionalProfile { #[serde(default)] pub base_mood: Option, #[serde(default)] pub volatility: Option, #[serde(default)] pub resilience: Option, #[serde(default)] pub triggers: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosTriggers { #[serde(default)] pub joy: Vec, #[serde(default)] pub anger: Vec, #[serde(default)] pub sadness: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosIdiosyncrasies { #[serde(default)] pub phobias: Vec, #[serde(default)] pub obsessions: Vec, #[serde(default)] pub tics: Vec, } // ── Linguistics ────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosLinguistics { #[serde(rename = "@type", default)] pub linguistics_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub voice: Option, #[serde(default)] pub text_style: Option, #[serde(default)] pub syntax: Option, #[serde(default)] pub interaction: Option, #[serde(default)] pub idiolect: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosVoice { #[serde(default)] pub tts_config: Option, #[serde(default)] pub acoustics: Option, #[serde(default)] pub accent: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosTtsConfig { #[serde(default)] pub provider: Option, #[serde(default)] pub voice_id: Option, #[serde(default)] pub stability: Option, #[serde(default)] pub similarity_boost: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosAcoustics { #[serde(default)] pub pitch: Option, #[serde(default)] pub speed: Option, #[serde(default)] pub roughness: Option, #[serde(default)] pub breathiness: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosAccent { #[serde(default)] pub region: Option, #[serde(default)] pub strength: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosTextStyle { #[serde(default)] pub formality_level: Option, #[serde(default)] pub verbosity_level: Option, #[serde(default)] pub vocabulary_level: Option, #[serde(default)] pub slang_usage: Option, #[serde(default)] pub style_descriptors: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosSyntax { #[serde(default)] pub sentence_structure: Option, #[serde(default)] pub use_contractions: Option, #[serde(default)] pub active_passive_ratio: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosInteraction { #[serde(default)] pub turn_taking: Option, #[serde(default)] pub dominance_score: Option, #[serde(default)] pub emotional_coloring: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosIdiolect { #[serde(default)] pub catchphrases: Vec, #[serde(default)] pub forbidden_words: Vec, #[serde(default)] pub hesitation_markers: Option, } // ── History ────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosHistory { #[serde(rename = "@type", default)] pub history_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub origin_story: Option, #[serde(default)] pub education: Option, #[serde(default)] pub occupation: Option, #[serde(default)] pub family: Option, #[serde(default)] pub key_life_events: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosEducation { #[serde(default)] pub level: Option, #[serde(default)] pub field: Option, #[serde(default)] pub institution: Option, #[serde(default)] pub graduation_year: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosOccupation { #[serde(default)] pub title: Option, #[serde(default)] pub industry: Option, #[serde(default)] pub years_experience: Option, #[serde(default)] pub previous_jobs: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosFamily { #[serde(default)] pub relationship_status: Option, #[serde(default)] pub parents: Option, #[serde(default)] pub siblings: Option, #[serde(default)] pub children: Option, #[serde(default)] pub pets: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosLifeEvent { #[serde(default)] pub year: Option, #[serde(default)] pub event: Option, #[serde(default)] pub impact: Option, } // ── Interests ──────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosInterests { #[serde(rename = "@type", default)] pub interests_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub hobbies: Vec, #[serde(default)] pub favorites: Option, #[serde(default)] pub aversions: Vec, #[serde(default)] pub lifestyle: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosFavorites { #[serde(default)] pub music_genre: Option, #[serde(default)] pub book: Option, #[serde(default)] pub movie: Option, #[serde(default)] pub color: Option, #[serde(default)] pub food: Option, #[serde(default)] pub season: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosLifestyle { #[serde(default)] pub diet: Option, #[serde(default)] pub sleep_schedule: Option, #[serde(default)] pub digital_habits: Option, } // ── Motivations ────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosMotivations { #[serde(rename = "@type", default)] pub motivations_type: Option, #[serde(rename = "@description", default)] pub description: Option, #[serde(default)] pub core_drive: Option, #[serde(default)] pub goals: Option, #[serde(default)] pub fears: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosGoals { #[serde(default)] pub short_term: Vec, #[serde(default)] pub long_term: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AieosFears { #[serde(default)] pub rational: Vec, #[serde(default)] pub irrational: Vec, } // ══════════════════════════════════════════════════════════════════════════════ // Loading & Parsing // ══════════════════════════════════════════════════════════════════════════════ /// Load an AIEOS identity from a JSON file pub fn load_aieos_identity(path: &Path) -> Result { 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 { // 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 { 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::>() .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()) ); } }