Skip to main content

markon_core/chat/
message.rs

1//! Provider-agnostic message and content-block types.
2//!
3//! Modeled after Anthropic's structure (role + array-of-blocks) because it
4//! cleanly represents tool_use / tool_result interleaving. OpenAI translation
5//! is straightforward (single-content text becomes `content: string`,
6//! tool_use becomes `tool_calls`, tool_result becomes a `tool` role message).
7
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Role {
13    User,
14    Assistant,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(tag = "type", rename_all = "snake_case")]
19pub enum ContentBlock {
20    Text {
21        text: String,
22    },
23    ToolUse {
24        id: String,
25        name: String,
26        input: serde_json::Value,
27    },
28    ToolResult {
29        tool_use_id: String,
30        content: String,
31        #[serde(default, skip_serializing_if = "is_false")]
32        is_error: bool,
33    },
34}
35
36fn is_false(b: &bool) -> bool {
37    !*b
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Message {
42    pub role: Role,
43    pub content: Vec<ContentBlock>,
44}
45
46impl Message {
47    #[allow(dead_code)]
48    pub(crate) fn user_text(text: impl Into<String>) -> Self {
49        Self {
50            role: Role::User,
51            content: vec![ContentBlock::Text { text: text.into() }],
52        }
53    }
54
55    #[allow(dead_code)]
56    pub(crate) fn assistant(content: Vec<ContentBlock>) -> Self {
57        Self {
58            role: Role::Assistant,
59            content,
60        }
61    }
62
63    #[allow(dead_code)]
64    pub(crate) fn tool_results(results: Vec<ContentBlock>) -> Self {
65        Self {
66            role: Role::User,
67            content: results,
68        }
69    }
70}
71
72/// Token accounting reported by the provider (Anthropic only fills cache fields).
73#[derive(Debug, Clone, Default, Serialize, Deserialize)]
74pub struct Usage {
75    #[serde(default)]
76    pub input_tokens: u32,
77    #[serde(default)]
78    pub cache_creation_input_tokens: u32,
79    #[serde(default)]
80    pub cache_read_input_tokens: u32,
81    #[serde(default)]
82    pub output_tokens: u32,
83}
84
85impl Usage {
86    pub(crate) fn add(&mut self, other: &Usage) {
87        self.input_tokens += other.input_tokens;
88        self.cache_creation_input_tokens += other.cache_creation_input_tokens;
89        self.cache_read_input_tokens += other.cache_read_input_tokens;
90        self.output_tokens += other.output_tokens;
91    }
92}