Skip to main content

plexus_substrate/activations/cone/
types.rs

1use crate::activations::arbor::{NodeId, TreeId};
2use plexus_macros::HandleEnum;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7use super::activation::Cone;
8
9/// Unique identifier for an cone configuration
10pub type ConeId = Uuid;
11
12// ============================================================================
13// Handle types for Cone activation
14// ============================================================================
15
16/// Type-safe handles for Cone activation data
17///
18/// Handles reference data stored in the Cone database and can be embedded
19/// in Arbor tree nodes for external resolution.
20#[derive(Debug, Clone, HandleEnum)]
21#[handle(plugin_id = "Cone::PLUGIN_ID", version = "1.0.0")]
22pub enum ConeHandle {
23    /// Handle to a message in the cone database
24    /// Format: `{plugin_id}@1.0.0::chat:msg-{uuid}:{role}:{name}`
25    #[handle(
26        method = "chat",
27        table = "messages",
28        key = "id",
29        key_field = "message_id",
30        strip_prefix = "msg-"
31    )]
32    Message {
33        /// Message ID with "msg-" prefix (e.g., "msg-550e8400-...")
34        message_id: String,
35        /// Role: "user", "assistant", or "system"
36        role: String,
37        /// Display name (cone name or "user")
38        name: String,
39    },
40}
41
42/// Unique identifier for a message
43pub type MessageId = Uuid;
44
45/// Role of a message sender
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum MessageRole {
49    User,
50    Assistant,
51    System,
52}
53
54impl MessageRole {
55    pub fn as_str(&self) -> &'static str {
56        match self {
57            MessageRole::User => "user",
58            MessageRole::Assistant => "assistant",
59            MessageRole::System => "system",
60        }
61    }
62
63    pub fn from_str(s: &str) -> Option<Self> {
64        match s {
65            "user" => Some(MessageRole::User),
66            "assistant" => Some(MessageRole::Assistant),
67            "system" => Some(MessageRole::System),
68            _ => None,
69        }
70    }
71}
72
73/// A message stored in the cone database
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Message {
76    pub id: MessageId,
77    pub cone_id: ConeId,
78    pub role: MessageRole,
79    pub content: String,
80    pub created_at: i64,
81    /// Model used (for assistant messages)
82    pub model_id: Option<String>,
83    /// Token usage (for assistant messages)
84    pub input_tokens: Option<i64>,
85    pub output_tokens: Option<i64>,
86}
87
88/// A position in the context tree - couples tree_id and node_id together.
89/// This ensures we always have a valid reference into a specific tree.
90#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
91pub struct Position {
92    /// The tree containing this position
93    pub tree_id: TreeId,
94    /// The specific node within the tree
95    pub node_id: NodeId,
96}
97
98impl Position {
99    /// Create a new position
100    pub fn new(tree_id: TreeId, node_id: NodeId) -> Self {
101        Self { tree_id, node_id }
102    }
103
104    /// Advance to a new node in the same tree
105    pub fn advance(&self, new_node_id: NodeId) -> Self {
106        Self {
107            tree_id: self.tree_id,
108            node_id: new_node_id,
109        }
110    }
111}
112
113/// Cone configuration - defines an cone's identity and behavior
114#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
115pub struct ConeConfig {
116    /// Unique identifier for this cone
117    pub id: ConeId,
118    /// Human-readable name
119    pub name: String,
120    /// Model ID to use (e.g., "gpt-4o-mini", "claude-3-haiku-20240307")
121    pub model_id: String,
122    /// System prompt / instructions for the cone
123    pub system_prompt: Option<String>,
124    /// The canonical head - current position in conversation tree
125    /// This couples tree_id and node_id together
126    pub head: Position,
127    /// Additional configuration metadata
128    pub metadata: Option<Value>,
129    /// Created timestamp
130    pub created_at: i64,
131    /// Last updated timestamp
132    pub updated_at: i64,
133}
134
135impl ConeConfig {
136    /// Get the tree ID (convenience accessor)
137    pub fn tree_id(&self) -> TreeId {
138        self.head.tree_id
139    }
140
141    /// Get the current node ID (convenience accessor)
142    pub fn node_id(&self) -> NodeId {
143        self.head.node_id
144    }
145}
146
147/// Lightweight cone info (for listing)
148#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
149pub struct ConeInfo {
150    pub id: ConeId,
151    pub name: String,
152    pub model_id: String,
153    pub head: Position,
154    pub created_at: i64,
155}
156
157impl From<&ConeConfig> for ConeInfo {
158    fn from(config: &ConeConfig) -> Self {
159        Self {
160            id: config.id,
161            name: config.name.clone(),
162            model_id: config.model_id.clone(),
163            head: config.head,
164            created_at: config.created_at,
165        }
166    }
167}
168
169// ============================================================================
170// Method-specific return types
171// Each method returns only its valid variants, making the API clearer
172// ============================================================================
173
174/// Result of cone.create
175#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
176#[serde(tag = "type")]
177pub enum CreateResult {
178    #[serde(rename = "cone_created")]
179    Created {
180        cone_id: ConeId,
181        /// Initial position (tree + root node)
182        head: Position,
183    },
184    #[serde(rename = "error")]
185    Error { message: String },
186}
187
188/// Result of cone.get
189#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
190#[serde(tag = "type")]
191pub enum GetResult {
192    #[serde(rename = "cone_data")]
193    Data { cone: ConeConfig },
194    #[serde(rename = "error")]
195    Error { message: String },
196}
197
198/// Result of cone.list
199#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
200#[serde(tag = "type")]
201pub enum ListResult {
202    #[serde(rename = "cone_list")]
203    List { cones: Vec<ConeInfo> },
204    #[serde(rename = "error")]
205    Error { message: String },
206}
207
208/// Result of cone.delete
209#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
210#[serde(tag = "type")]
211pub enum DeleteResult {
212    #[serde(rename = "cone_deleted")]
213    Deleted { cone_id: ConeId },
214    #[serde(rename = "error")]
215    Error { message: String },
216}
217
218/// Events emitted during cone.chat (streaming)
219#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
220#[serde(tag = "type")]
221pub enum ChatEvent {
222    /// Chat response started
223    #[serde(rename = "chat_start")]
224    Start {
225        cone_id: ConeId,
226        /// Position of the user message node
227        user_position: Position,
228    },
229    /// Chat content chunk (streaming)
230    #[serde(rename = "chat_content")]
231    Content {
232        cone_id: ConeId,
233        content: String,
234    },
235    /// Chat response complete
236    #[serde(rename = "chat_complete")]
237    Complete {
238        cone_id: ConeId,
239        /// The new head position (tree + response node)
240        new_head: Position,
241        /// Total tokens used (if available)
242        usage: Option<ChatUsage>,
243    },
244    #[serde(rename = "error")]
245    Error { message: String },
246}
247
248/// Result of cone.set_head
249#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
250#[serde(tag = "type")]
251pub enum SetHeadResult {
252    #[serde(rename = "head_updated")]
253    Updated {
254        cone_id: ConeId,
255        old_head: Position,
256        new_head: Position,
257    },
258    #[serde(rename = "error")]
259    Error { message: String },
260}
261
262/// Result of cone.registry
263#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
264#[serde(tag = "type")]
265pub enum RegistryResult {
266    #[serde(rename = "registry")]
267    Registry(cllient::RegistryExport),
268}
269
270/// Resolved message from handle resolution
271#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
272#[serde(tag = "type")]
273pub enum ResolveResult {
274    #[serde(rename = "resolved_message")]
275    Message {
276        id: String,
277        role: String,
278        content: String,
279        model: Option<String>,
280        name: String,
281    },
282    #[serde(rename = "error")]
283    Error { message: String },
284}
285
286/// Token usage information
287#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
288pub struct ChatUsage {
289    pub input_tokens: Option<u64>,
290    pub output_tokens: Option<u64>,
291    pub total_tokens: Option<u64>,
292}
293
294/// Error type for cone operations
295#[derive(Debug, Clone)]
296pub struct ConeError {
297    pub message: String,
298}
299
300impl std::fmt::Display for ConeError {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        write!(f, "{}", self.message)
303    }
304}
305
306impl std::error::Error for ConeError {}
307
308impl From<String> for ConeError {
309    fn from(s: String) -> Self {
310        Self { message: s }
311    }
312}
313
314impl From<&str> for ConeError {
315    fn from(s: &str) -> Self {
316        Self { message: s.to_string() }
317    }
318}