1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
6#[serde(rename_all = "lowercase")]
7pub enum Role {
8 System,
9 User,
10 Assistant,
11 Tool,
12}
13
14#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
16#[serde(rename_all = "snake_case")]
17pub enum ToolArgumentIntegrity {
18 #[default]
19 Valid,
20 MalformedJson,
21}
22
23#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
26#[serde(rename_all = "snake_case")]
27pub enum ToolExecutionProvenance {
28 #[default]
29 FxLocal,
30 Provider,
31}
32
33#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
39pub struct ToolCall {
40 pub id: String,
41 pub name: String,
42 pub arguments_json: String,
43 #[serde(default)]
44 pub argument_integrity: ToolArgumentIntegrity,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub provisional_id: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub provider_result: Option<String>,
49 #[serde(default)]
50 pub provenance: ToolExecutionProvenance,
51}
52
53impl ToolCall {
54 pub fn arguments(&self) -> Result<Value, serde_json::Error> {
55 serde_json::from_str(&self.arguments_json)
56 }
57}
58
59#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
60#[serde(rename_all = "snake_case")]
61pub enum CachePolicy {
62 #[default]
63 Default,
64 NoCache,
65}
66
67#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
70pub struct ChatMessage {
71 pub role: Role,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub content: Option<String>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub tool_call_id: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub tool_name: Option<String>,
78 #[serde(default, skip_serializing_if = "Vec::is_empty")]
79 pub tool_calls: Vec<ToolCall>,
80 #[serde(default)]
81 pub permission_feedback: bool,
82 #[serde(default)]
83 pub cache_policy: CachePolicy,
84}
85
86impl ChatMessage {
87 pub fn text(role: Role, content: impl Into<String>) -> Self {
88 Self {
89 role,
90 content: Some(content.into()),
91 tool_call_id: None,
92 tool_name: None,
93 tool_calls: Vec::new(),
94 permission_feedback: false,
95 cache_policy: CachePolicy::Default,
96 }
97 }
98}
99
100#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
101#[serde(rename_all = "kebab-case")]
102pub enum FinishReason {
103 Stop,
104 Length,
105 ContentFilter,
106 ToolCalls,
107 Error,
108 Other,
109}
110
111#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
112pub struct Usage {
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub input_tokens: Option<u64>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub output_tokens: Option<u64>,
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn tool_call_preserves_raw_json_and_parses_on_demand() {
125 let call = ToolCall {
126 id: "call_1".into(),
127 name: "read_file".into(),
128 arguments_json: r#"{"path":"README.md"}"#.into(),
129 argument_integrity: ToolArgumentIntegrity::Valid,
130 provisional_id: None,
131 provider_result: None,
132 provenance: ToolExecutionProvenance::FxLocal,
133 };
134
135 assert_eq!(call.arguments().unwrap()["path"], "README.md");
136 assert_eq!(call.arguments_json, r#"{"path":"README.md"}"#);
137 }
138}