Skip to main content

cpex_core/cmf/
enums.rs

1// Location: ./crates/cpex-core/src/cmf/enums.rs
2// Copyright 2025
3// SPDX-License-Identifier: Apache-2.0
4// Authors: Teryl Taylor
5//
6// CMF enums — Role, Channel, ContentType, ResourceType.
7//
8// Mirrors the Python enums in cpex/framework/cmf/message.py.
9// All use snake_case serialization to match Python string values.
10
11use serde::{Deserialize, Serialize};
12
13// ---------------------------------------------------------------------------
14// Role
15// ---------------------------------------------------------------------------
16
17/// Identifies WHO is speaking in a conversation turn.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum Role {
21    /// System-level instructions.
22    System,
23    /// Developer-provided instructions.
24    Developer,
25    /// Human user input.
26    User,
27    /// LLM/agent response.
28    Assistant,
29    /// Tool execution result.
30    Tool,
31}
32
33// ---------------------------------------------------------------------------
34// Channel
35// ---------------------------------------------------------------------------
36
37/// Classifies the kind of output a message represents.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40pub enum Channel {
41    /// Intermediate analytical output (chain-of-thought).
42    Analysis,
43    /// Meta-level observations about the task.
44    Commentary,
45    /// Terminal response intended for delivery.
46    Final,
47}
48
49// ---------------------------------------------------------------------------
50// ContentType
51// ---------------------------------------------------------------------------
52
53/// Discriminator for the typed ContentPart hierarchy.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
55#[serde(rename_all = "snake_case")]
56pub enum ContentType {
57    /// Plain text content.
58    Text,
59    /// Chain-of-thought reasoning.
60    Thinking,
61    /// Tool/function invocation request.
62    ToolCall,
63    /// Result from tool execution.
64    ToolResult,
65    /// Embedded resource with content (MCP).
66    Resource,
67    /// Lightweight resource reference without embedded content.
68    ResourceRef,
69    /// Prompt template invocation request (MCP).
70    PromptRequest,
71    /// Rendered prompt template result.
72    PromptResult,
73    /// Image content (URL or base64).
74    Image,
75    /// Video content (URL or base64).
76    Video,
77    /// Audio content (URL or base64).
78    Audio,
79    /// Document content (PDF, Word, etc.).
80    Document,
81}
82
83// ---------------------------------------------------------------------------
84// ResourceType
85// ---------------------------------------------------------------------------
86
87/// Type of resource being referenced.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum ResourceType {
91    /// File-system resource.
92    #[default]
93    File,
94    /// Binary large object.
95    Blob,
96    /// Generic URI-addressable resource.
97    Uri,
98    /// Database entity.
99    Database,
100    /// API endpoint.
101    Api,
102    /// In-memory or ephemeral resource.
103    Memory,
104    /// Produced artifact (generated output, build result).
105    Artifact,
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_role_serde_roundtrip() {
114        let role = Role::Assistant;
115        let json = serde_json::to_string(&role).unwrap();
116        assert_eq!(json, "\"assistant\"");
117        let deserialized: Role = serde_json::from_str(&json).unwrap();
118        assert_eq!(deserialized, Role::Assistant);
119    }
120
121    #[test]
122    fn test_channel_serde_roundtrip() {
123        let channel = Channel::Final;
124        let json = serde_json::to_string(&channel).unwrap();
125        assert_eq!(json, "\"final\"");
126        let deserialized: Channel = serde_json::from_str(&json).unwrap();
127        assert_eq!(deserialized, Channel::Final);
128    }
129
130    #[test]
131    fn test_content_type_serde_roundtrip() {
132        let ct = ContentType::ToolCall;
133        let json = serde_json::to_string(&ct).unwrap();
134        assert_eq!(json, "\"tool_call\"");
135        let deserialized: ContentType = serde_json::from_str(&json).unwrap();
136        assert_eq!(deserialized, ContentType::ToolCall);
137    }
138
139    #[test]
140    fn test_content_type_resource_ref() {
141        let ct = ContentType::ResourceRef;
142        let json = serde_json::to_string(&ct).unwrap();
143        assert_eq!(json, "\"resource_ref\"");
144    }
145
146    #[test]
147    fn test_content_type_prompt_variants() {
148        let req = ContentType::PromptRequest;
149        let res = ContentType::PromptResult;
150        assert_eq!(serde_json::to_string(&req).unwrap(), "\"prompt_request\"");
151        assert_eq!(serde_json::to_string(&res).unwrap(), "\"prompt_result\"");
152    }
153
154    #[test]
155    fn test_resource_type_serde_roundtrip() {
156        let rt = ResourceType::Database;
157        let json = serde_json::to_string(&rt).unwrap();
158        assert_eq!(json, "\"database\"");
159        let deserialized: ResourceType = serde_json::from_str(&json).unwrap();
160        assert_eq!(deserialized, ResourceType::Database);
161    }
162
163    #[test]
164    fn test_all_roles_deserialize() {
165        for (s, expected) in &[
166            ("\"system\"", Role::System),
167            ("\"developer\"", Role::Developer),
168            ("\"user\"", Role::User),
169            ("\"assistant\"", Role::Assistant),
170            ("\"tool\"", Role::Tool),
171        ] {
172            let role: Role = serde_json::from_str(s).unwrap();
173            assert_eq!(role, *expected);
174        }
175    }
176}