1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum Role {
17 System,
19 User,
21 Assistant,
23 Tool,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct Message {
33 pub role: Role,
34 pub blocks: Vec<ContentBlock>,
35}
36
37impl Message {
38 pub fn system(text: impl Into<String>) -> Self {
39 Self::text(Role::System, text)
40 }
41
42 pub fn user(text: impl Into<String>) -> Self {
43 Self::text(Role::User, text)
44 }
45
46 pub fn assistant(text: impl Into<String>) -> Self {
47 Self::text(Role::Assistant, text)
48 }
49
50 pub fn tool(result: ToolResult) -> Self {
51 Self { role: Role::Tool, blocks: vec![ContentBlock::ToolResult(result)] }
52 }
53
54 pub fn tool_results(results: Vec<ToolResult>) -> Self {
55 Self {
56 role: Role::Tool,
57 blocks: results.into_iter().map(ContentBlock::ToolResult).collect(),
58 }
59 }
60
61 pub fn text(role: Role, text: impl Into<String>) -> Self {
62 Self { role, blocks: vec![ContentBlock::text(text)] }
63 }
64
65 pub fn text_content(&self) -> String {
66 self.blocks
67 .iter()
68 .filter_map(|block| match block {
69 ContentBlock::Text(text) => Some(text.text.as_str()),
70 _ => None,
71 })
72 .collect::<Vec<_>>()
73 .join("\n")
74 }
75
76 pub fn thinking_content(&self) -> String {
77 self.blocks
78 .iter()
79 .filter_map(|block| match block {
80 ContentBlock::Thinking(thinking) => Some(thinking.text.as_str()),
81 _ => None,
82 })
83 .collect::<Vec<_>>()
84 .join("\n")
85 }
86
87 pub fn tool_calls(&self) -> impl Iterator<Item = &ToolCall> {
88 self.blocks.iter().filter_map(|block| match block {
89 ContentBlock::ToolCall(call) => Some(call),
90 _ => None,
91 })
92 }
93
94 pub fn tool_results_iter(&self) -> impl Iterator<Item = &ToolResult> {
95 self.blocks.iter().filter_map(|block| match block {
96 ContentBlock::ToolResult(result) => Some(result),
97 _ => None,
98 })
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108pub enum ContentBlock {
109 Text(TextBlock),
111 ToolCall(ToolCall),
113 ToolResult(ToolResult),
115 Thinking(ThinkingBlock),
117}
118
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub struct TextBlock {
122 pub text: String,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub struct ThinkingBlock {
131 pub text: String,
133 pub signature: Option<String>,
136 pub is_redacted: bool,
138}
139
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct ToolCall {
146 pub id: String,
147 pub name: String,
148 pub arguments: Value,
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub struct ToolResult {
158 pub tool_call_id: String,
159 pub name: String,
160 pub content: Value,
161 pub is_error: bool,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub enum SystemReminder {
166 PlanMode,
167 Compaction { reason: String },
168 ProviderContext,
169 PolicyFeedback { point: String, name: String },
170 ToolResult { tool_name: String, note: String },
171 MemoryInjection { content: String },
172 SkillDiscovery { content: String },
173}
174
175impl SystemReminder {
176 pub fn render(&self) -> String {
177 let body = match self {
178 Self::PlanMode => "You are entering plan mode. Follow the plan instructions and do not write implementation code until the plan is approved.".to_string(),
179 Self::Compaction { reason } => format!(
180 "Prior messages were compacted (reason: {reason}). Some context may have been summarized."
181 ),
182 Self::ProviderContext => "The provider/model context has changed. Adjust to any new instructions or constraints.".to_string(),
183 Self::PolicyFeedback { point, name } => format!(
184 "A policy added feedback at {point} ({name}). Treat it as user feedback."
185 ),
186 Self::ToolResult { tool_name, note } => format!("Tool `{tool_name}` reported: {note}"),
187 Self::MemoryInjection { content } | Self::SkillDiscovery { content } => content.clone(),
188 };
189 format!("<system-reminder>\n{body}\n</system-reminder>")
190 }
191}
192
193impl ContentBlock {
194 pub fn text(text: impl Into<String>) -> Self {
196 ContentBlock::Text(TextBlock { text: text.into() })
197 }
198
199 pub fn tool_call(id: impl Into<String>, name: impl Into<String>, arguments: Value) -> Self {
201 ContentBlock::ToolCall(ToolCall { id: id.into(), name: name.into(), arguments })
202 }
203
204 pub fn tool_result(
206 tool_call_id: impl Into<String>,
207 name: impl Into<String>,
208 content: Value,
209 is_error: bool,
210 ) -> Self {
211 ContentBlock::ToolResult(ToolResult {
212 tool_call_id: tool_call_id.into(),
213 name: name.into(),
214 content,
215 is_error,
216 })
217 }
218
219 pub fn thinking(
221 text: impl Into<String>,
222 signature: Option<impl Into<String>>,
223 is_redacted: bool,
224 ) -> Self {
225 ContentBlock::Thinking(ThinkingBlock {
226 text: text.into(),
227 signature: signature.map(|s| s.into()),
228 is_redacted,
229 })
230 }
231}