oxios_kernel/tools/kernel/
persona_tool.rs1use std::sync::Arc;
15
16use async_trait::async_trait;
17use oxi_sdk::{AgentTool, AgentToolResult, ToolContext};
18use serde_json::{json, Value};
19use tokio::sync::oneshot;
20
21use crate::kernel_handle::KernelHandle;
22use crate::persona_manager::PersonaManager;
23
24pub struct PersonaTool {
37 persona_manager: Arc<PersonaManager>,
38}
39
40impl PersonaTool {
41 pub fn from_kernel(kernel: &KernelHandle) -> Self {
45 Self {
46 persona_manager: kernel.persona.persona_manager.clone(),
47 }
48 }
49}
50
51impl std::fmt::Debug for PersonaTool {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("PersonaTool").finish()
54 }
55}
56
57#[async_trait]
58impl AgentTool for PersonaTool {
59 fn name(&self) -> &str {
60 "persona"
61 }
62
63 fn label(&self) -> &str {
64 "Persona"
65 }
66
67 fn description(&self) -> &'static str {
68 "Manage personas — list, inspect, or switch the active persona. \
69 Actions: list, get, set_active."
70 }
71
72 fn parameters_schema(&self) -> Value {
73 json!({
74 "type": "object",
75 "properties": {
76 "action": {
77 "type": "string",
78 "enum": ["list", "get", "set_active"],
79 "description": "Persona operation to perform"
80 },
81 "id": {
82 "type": "string",
83 "description": "Persona identifier (required for get and set_active)"
84 }
85 },
86 "required": ["action"]
87 })
88 }
89
90 async fn execute(
91 &self,
92 _tool_call_id: &str,
93 params: Value,
94 _signal: Option<oneshot::Receiver<()>>,
95 _ctx: &ToolContext,
96 ) -> Result<AgentToolResult, String> {
97 let action = params
98 .get("action")
99 .and_then(|v| v.as_str())
100 .ok_or_else(|| "Missing required parameter: action".to_string())?;
101
102 let api = crate::kernel_handle::PersonaApi::new(self.persona_manager.clone());
104
105 match action {
106 "list" => {
107 let personas = api.list();
108 if personas.is_empty() {
109 return Ok(AgentToolResult::success("No personas defined."));
110 }
111
112 let active_id = api.active().map(|p| p.id.clone());
114
115 let mut output = format!("Found {} persona(s):\n\n", personas.len());
116 for p in &personas {
117 let marker = if active_id.as_deref() == Some(&p.id) {
118 " ← active"
119 } else {
120 ""
121 };
122 output.push_str(&format!(
123 "- {} ({}) enabled={}{}\n",
124 p.name, p.id, p.enabled, marker,
125 ));
126 }
127 Ok(AgentToolResult::success(output))
128 }
129
130 "get" => {
131 let id = params
132 .get("id")
133 .and_then(|v| v.as_str())
134 .ok_or_else(|| "get requires 'id' parameter".to_string())?;
135
136 match api.get(id) {
137 Some(p) => Ok(AgentToolResult::success(
138 serde_json::to_string_pretty(&json!({
139 "id": p.id,
140 "name": p.name,
141 "description": p.description,
142 "enabled": p.enabled,
143 "system_prompt": p.system_prompt,
144 "traits": p.personality_traits,
145 }))
146 .unwrap_or_default(),
147 )),
148 None => Ok(AgentToolResult::error(format!(
149 "Persona '{}' not found",
150 id
151 ))),
152 }
153 }
154
155 "set_active" => {
156 let id = params
157 .get("id")
158 .and_then(|v| v.as_str())
159 .ok_or_else(|| "set_active requires 'id' parameter".to_string())?;
160
161 match api.set_active(id) {
162 Ok(()) => Ok(AgentToolResult::success(format!(
163 "Active persona set to '{}'.",
164 id
165 ))),
166 Err(e) => Ok(AgentToolResult::error(format!(
167 "Failed to set active persona: {}",
168 e
169 ))),
170 }
171 }
172
173 other => Err(format!(
174 "Unknown persona action '{}'. Valid: list, get, set_active",
175 other
176 )),
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_schema_structure() {
187 let schema = json!({
188 "type": "object",
189 "properties": {
190 "action": {
191 "type": "string",
192 "enum": ["list", "get", "set_active"]
193 },
194 "id": { "type": "string" }
195 },
196 "required": ["action"]
197 });
198
199 let actions = schema["properties"]["action"]["enum"].as_array().unwrap();
200 assert_eq!(actions.len(), 3);
201 assert!(actions.iter().any(|a| a == "list"));
202 assert!(actions.iter().any(|a| a == "get"));
203 assert!(actions.iter().any(|a| a == "set_active"));
204 }
205}