atomr_agents_coding_cli_core/
projection.rs1use std::collections::BTreeMap;
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20pub struct PersonaSnapshot {
21 pub identity: String,
22 #[serde(default, skip_serializing_if = "Vec::is_empty")]
23 pub salient_traits: Vec<String>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub style_tone: Option<String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct SkillSnapshot {
37 pub id: String,
38 pub name: String,
39 pub instruction_fragment: String,
40 #[serde(default)]
41 pub keywords: Vec<String>,
42 #[serde(default = "default_priority")]
43 pub priority: u8,
44 #[serde(default)]
48 pub tools: Vec<String>,
49}
50
51fn default_priority() -> u8 {
52 5
53}
54
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
60pub struct PolicySnapshot {
61 #[serde(default)]
64 pub allowed_tools: Vec<String>,
65 #[serde(default)]
68 pub allowed_models: Vec<String>,
69 #[serde(default)]
72 pub auto_approve_unrestricted: bool,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub max_tokens_per_call: Option<u32>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct McpServerSnapshot {
81 pub name: String,
82 pub command: String,
83 #[serde(default)]
84 pub args: Vec<String>,
85 #[serde(default)]
86 pub env: BTreeMap<String, String>,
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93pub struct ToolSetSnapshot {
94 pub id: String,
95 #[serde(default)]
96 pub mcp_servers: Vec<McpServerSnapshot>,
97 #[serde(default)]
98 pub tool_names: Vec<String>,
99}
100
101#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104pub struct ConceptProjection {
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub persona: Option<PersonaSnapshot>,
107 #[serde(default, skip_serializing_if = "Vec::is_empty")]
108 pub skills: Vec<SkillSnapshot>,
109 #[serde(default)]
110 pub policy: PolicySnapshot,
111 #[serde(default, skip_serializing_if = "Vec::is_empty")]
112 pub toolsets: Vec<ToolSetSnapshot>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub project_memory: Option<String>,
118}
119
120impl ConceptProjection {
121 pub fn is_empty(&self) -> bool {
122 self.persona.is_none()
123 && self.skills.is_empty()
124 && self.toolsets.is_empty()
125 && self.project_memory.is_none()
126 && self.policy.allowed_tools.is_empty()
127 && self.policy.allowed_models.is_empty()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn empty_projection_is_empty() {
137 assert!(ConceptProjection::default().is_empty());
138 }
139
140 #[test]
141 fn skill_round_trip() {
142 let s = SkillSnapshot {
143 id: "rag".into(),
144 name: "RAG".into(),
145 instruction_fragment: "use the index".into(),
146 keywords: vec!["search".into()],
147 priority: 7,
148 tools: vec!["WebSearch".into()],
149 };
150 let j = serde_json::to_string(&s).unwrap();
151 let back: SkillSnapshot = serde_json::from_str(&j).unwrap();
152 assert_eq!(back.id, "rag");
153 assert_eq!(back.priority, 7);
154 }
155}