1use super::response_format::StructuredOutputOptions;
13use super::sampling::{ProviderConfig, SamplingParams};
14use super::tools::McpTool;
15use crate::execution::context::RequestContext;
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19#[serde(tag = "type", rename_all = "snake_case")]
20pub enum AiContentPart {
21 Text { text: String },
22 Image { mime_type: String, data: String },
23 Audio { mime_type: String, data: String },
24 Video { mime_type: String, data: String },
25}
26
27impl AiContentPart {
28 pub fn text(text: impl Into<String>) -> Self {
29 Self::Text { text: text.into() }
30 }
31
32 pub fn image(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
33 Self::Image {
34 mime_type: mime_type.into(),
35 data: data.into(),
36 }
37 }
38
39 pub fn audio(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
40 Self::Audio {
41 mime_type: mime_type.into(),
42 data: data.into(),
43 }
44 }
45
46 pub fn video(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
47 Self::Video {
48 mime_type: mime_type.into(),
49 data: data.into(),
50 }
51 }
52
53 pub const fn is_media(&self) -> bool {
54 matches!(
55 self,
56 Self::Image { .. } | Self::Audio { .. } | Self::Video { .. }
57 )
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct AiMessage {
63 pub role: MessageRole,
64 pub content: String,
65 #[serde(default, skip_serializing_if = "Vec::is_empty")]
66 pub parts: Vec<AiContentPart>,
67}
68
69impl AiMessage {
70 pub fn user(content: impl Into<String>) -> Self {
71 Self {
72 role: MessageRole::User,
73 content: content.into(),
74 parts: Vec::new(),
75 }
76 }
77
78 pub fn assistant(content: impl Into<String>) -> Self {
79 Self {
80 role: MessageRole::Assistant,
81 content: content.into(),
82 parts: Vec::new(),
83 }
84 }
85
86 pub fn system(content: impl Into<String>) -> Self {
87 Self {
88 role: MessageRole::System,
89 content: content.into(),
90 parts: Vec::new(),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "lowercase")]
97pub enum MessageRole {
98 System,
99 User,
100 Assistant,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct AiRequest {
105 pub messages: Vec<AiMessage>,
106 pub provider_config: ProviderConfig,
107 pub context: RequestContext,
108 pub sampling: Option<SamplingParams>,
109 pub tools: Option<Vec<McpTool>>,
110 pub structured_output: Option<StructuredOutputOptions>,
111 pub system_prompt: Option<String>,
112}
113
114impl AiRequest {
115 pub fn builder(
116 messages: Vec<AiMessage>,
117 provider: impl Into<String>,
118 model: impl Into<String>,
119 max_output_tokens: u32,
120 context: RequestContext,
121 ) -> AiRequestBuilder {
122 AiRequestBuilder::new(messages, provider, model, max_output_tokens, context)
123 }
124
125 pub fn has_tools(&self) -> bool {
126 self.tools.as_ref().is_some_and(|t| !t.is_empty())
127 }
128
129 pub fn provider(&self) -> &str {
130 &self.provider_config.provider
131 }
132
133 pub fn model(&self) -> &str {
134 &self.provider_config.model
135 }
136
137 pub const fn max_output_tokens(&self) -> u32 {
138 self.provider_config.max_output_tokens
139 }
140}
141
142#[derive(Debug)]
143pub struct AiRequestBuilder {
144 messages: Vec<AiMessage>,
145 provider_config: ProviderConfig,
146 context: RequestContext,
147 sampling: Option<SamplingParams>,
148 tools: Option<Vec<McpTool>>,
149 structured_output: Option<StructuredOutputOptions>,
150 system_prompt: Option<String>,
151}
152
153impl AiRequestBuilder {
154 pub fn new(
155 messages: Vec<AiMessage>,
156 provider: impl Into<String>,
157 model: impl Into<String>,
158 max_output_tokens: u32,
159 context: RequestContext,
160 ) -> Self {
161 Self {
162 messages,
163 provider_config: ProviderConfig::new(provider, model, max_output_tokens),
164 context,
165 sampling: None,
166 tools: None,
167 structured_output: None,
168 system_prompt: None,
169 }
170 }
171
172 pub fn with_sampling(mut self, sampling: SamplingParams) -> Self {
173 self.sampling = Some(sampling);
174 self
175 }
176
177 pub fn with_tools(mut self, tools: Vec<McpTool>) -> Self {
178 self.tools = Some(tools);
179 self
180 }
181
182 pub fn with_structured_output(mut self, options: StructuredOutputOptions) -> Self {
183 self.structured_output = Some(options);
184 self
185 }
186
187 pub fn build(self) -> AiRequest {
188 AiRequest {
189 messages: self.messages,
190 provider_config: self.provider_config,
191 context: self.context,
192 sampling: self.sampling,
193 tools: self.tools,
194 structured_output: self.structured_output,
195 system_prompt: self.system_prompt,
196 }
197 }
198}