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