use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS), ts(export))]
pub struct PersonaLocales {
pub available: Vec<String>,
pub snapshots: Vec<PersonaLocaleEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS), ts(export))]
pub struct PersonaLocaleEntry {
pub locale: String,
pub snapshot: PersonaSnapshot,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recommended_voice: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS), ts(export))]
pub struct PersonaSnapshot {
pub system_prompt: String,
pub identity: String,
pub soul: String,
pub user: String,
pub agents: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub present_files: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS), ts(export))]
pub struct PersonaSaveLocalizedRequest {
pub agent_id: String,
pub locale: String,
pub system_prompt: String,
pub identity: String,
pub soul: String,
pub user: String,
pub agents: String,
#[serde(default = "default_true")]
pub patch_yaml: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "ts-export", derive(ts_rs::TS), ts(export))]
pub struct PersonaSaveLocalizedResponse {
pub written_paths: Vec<String>,
pub persona_locales: PersonaLocales,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snapshot_round_trips() {
let s = PersonaSnapshot {
system_prompt: "top".into(),
identity: "i".into(),
soul: "s".into(),
user: "u".into(),
agents: "a".into(),
present_files: vec!["system_prompt".into(), "identity".into()],
};
let v = serde_json::to_value(&s).unwrap();
let back: PersonaSnapshot = serde_json::from_value(v).unwrap();
assert_eq!(s, back);
}
#[test]
fn locales_round_trip_preserves_order() {
let l = PersonaLocales {
available: vec!["es".into(), "en".into(), "pt-BR".into()],
snapshots: vec![
PersonaLocaleEntry {
locale: "es".into(),
recommended_voice: None,
snapshot: PersonaSnapshot {
system_prompt: "spanish".into(),
..Default::default()
},
},
PersonaLocaleEntry {
locale: "en".into(),
recommended_voice: None,
snapshot: PersonaSnapshot {
system_prompt: "english".into(),
..Default::default()
},
},
],
};
let v = serde_json::to_value(&l).unwrap();
let back: PersonaLocales = serde_json::from_value(v).unwrap();
assert_eq!(l, back);
assert_eq!(back.available[0], "es");
}
#[test]
fn save_request_round_trips_with_default_patch_yaml() {
let req = PersonaSaveLocalizedRequest {
agent_id: "cody".into(),
locale: "es".into(),
system_prompt: "p".into(),
identity: "i".into(),
soul: "s".into(),
user: "u".into(),
agents: "a".into(),
patch_yaml: true,
};
let v = serde_json::to_value(&req).unwrap();
let back: PersonaSaveLocalizedRequest = serde_json::from_value(v).unwrap();
assert_eq!(req, back);
let raw = serde_json::json!({
"agent_id": "x", "locale": "en",
"system_prompt": "", "identity": "", "soul": "", "user": "", "agents": ""
});
let parsed: PersonaSaveLocalizedRequest = serde_json::from_value(raw).unwrap();
assert!(parsed.patch_yaml);
}
#[test]
fn save_response_skips_empty_paths() {
let r = PersonaSaveLocalizedResponse::default();
let v = serde_json::to_value(&r).unwrap();
let back: PersonaSaveLocalizedResponse = serde_json::from_value(v).unwrap();
assert_eq!(r, back);
}
}