1use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9#[serde(tag = "role", rename_all = "snake_case")]
10pub enum Message {
11 User {
12 content: String,
13 #[serde(default, skip_serializing_if = "Vec::is_empty")]
16 images: Vec<ImageInput>,
17 },
18 Assistant {
19 content: String,
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
21 tool_calls: Vec<ToolCall>,
22 },
23 Tool {
24 call_id: String,
25 name: String,
26 content: String,
27 is_error: bool,
28 },
29 HistoryNote {
30 content: String,
31 },
32}
33
34impl Message {
35 pub fn user(content: impl Into<String>) -> Self {
37 Self::User {
38 content: content.into(),
39 images: Vec::new(),
40 }
41 }
42
43 pub(crate) fn estimated_tokens(&self, bytes_per_token: usize) -> usize {
46 let bytes = serde_json::to_vec(self).map_or(0, |value| value.len());
47 let images = match self {
48 Self::User { images, .. } => images.len(),
49 _ => 0,
50 };
51 bytes
52 .div_ceil(bytes_per_token)
53 .saturating_add(4)
54 .saturating_add(images.saturating_mul(IMAGE_TOKENS))
55 }
56}
57
58pub(crate) const IMAGE_TOKENS: usize = 1600;
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
64pub struct ImageInput {
65 pub path: PathBuf,
68 pub mime: String,
70}
71
72#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct TurnInput {
75 pub text: String,
76 pub images: Vec<ImageInput>,
77}
78
79impl From<String> for TurnInput {
80 fn from(text: String) -> Self {
81 Self {
82 text,
83 images: Vec::new(),
84 }
85 }
86}
87
88impl From<&str> for TurnInput {
89 fn from(text: &str) -> Self {
90 text.to_owned().into()
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95pub struct ToolCall {
96 pub id: String,
97 pub name: String,
98 pub arguments: Value,
99}
100
101#[cfg(test)]
102mod tests;