1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ChatMessage {
13 pub role: String, #[serde(default)]
15 pub content: String,
16 #[serde(skip_serializing_if = "Option::is_none", default)]
17 pub tool_calls: Option<Vec<ToolCall>>,
18 #[serde(skip_serializing_if = "Option::is_none", default)]
19 pub tool_call_id: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none", default)]
21 pub reasoning_content: Option<String>,
22}
23
24impl ChatMessage {
25 pub fn system(content: impl Into<String>) -> Self {
26 Self::simple("system", content)
27 }
28 pub fn user(content: impl Into<String>) -> Self {
29 Self::simple("user", content)
30 }
31 pub fn assistant(content: impl Into<String>) -> Self {
32 Self::simple("assistant", content)
33 }
34 pub fn simple(role: &str, content: impl Into<String>) -> Self {
35 ChatMessage {
36 role: role.to_string(),
37 content: content.into(),
38 tool_calls: None,
39 tool_call_id: None,
40 reasoning_content: None,
41 }
42 }
43 pub fn tool_result(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
45 ChatMessage {
46 role: "tool".to_string(),
47 content: content.into(),
48 tool_calls: None,
49 tool_call_id: Some(tool_call_id.into()),
50 reasoning_content: None,
51 }
52 }
53 pub fn assistant_with_tools(content: impl Into<String>, tool_calls: Vec<ToolCall>) -> Self {
55 ChatMessage {
56 role: "assistant".to_string(),
57 content: content.into(),
58 tool_calls: Some(tool_calls),
59 tool_call_id: None,
60 reasoning_content: None,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67pub struct ToolCall {
68 pub id: String,
69 pub name: String,
70 pub arguments: String,
72}
73
74impl ToolCall {
75 pub fn parsed_args(&self) -> Value {
78 serde_json::from_str(&self.arguments).unwrap_or_else(|_| serde_json::json!({}))
79 }
80}
81
82#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
84pub struct Usage {
85 pub input_tokens: u64,
86 pub output_tokens: u64,
87 pub total_tokens: u64,
88 pub cache_read_tokens: u64,
89 pub cache_write_tokens: u64,
90 pub cache_miss_tokens: u64,
91 pub reasoning_tokens: u64,
92}
93
94impl Usage {
95 pub fn cache_hit_rate(&self) -> f64 {
98 let hit = self.cache_read_tokens as f64;
99 let miss = self.cache_miss_tokens as f64;
100 let denom = hit + miss;
101 if denom > 0.0 {
102 hit / denom
103 } else {
104 0.0
105 }
106 }
107}
108
109#[derive(Debug, Clone, Default)]
111pub struct LlmResponse {
112 pub text: String,
113 pub tool_calls: Vec<ToolCall>,
114 pub reasoning_content: Option<String>,
115 pub finish_reason: Option<String>,
117 pub usage: Option<Usage>,
119}
120
121impl LlmResponse {
122 pub fn is_truncated(&self) -> bool {
123 matches!(
124 self.finish_reason.as_deref(),
125 Some("length") | Some("max_tokens") | Some("connection_closed")
126 )
127 }
128}