1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::WireProtocol;
7
8pub type Extensions = BTreeMap<String, Value>;
9
10#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct UniversalRequest {
13 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub id: Option<String>,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub model: Option<String>,
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
18 pub instructions: Vec<ContentBlock>,
19 #[serde(default, skip_serializing_if = "Vec::is_empty")]
20 pub input: Vec<UniversalItem>,
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub tools: Vec<UniversalTool>,
23 #[serde(default, skip_serializing_if = "Vec::is_empty")]
24 pub server_tools: Vec<ServerToolDeclaration>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub tool_choice: Option<ToolChoice>,
27 #[serde(default, skip_serializing_if = "is_false")]
28 pub stream: bool,
29 #[serde(default, skip_serializing_if = "GenerationConfig::is_empty")]
30 pub generation: GenerationConfig,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub reasoning: Option<ReasoningConfig>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub source: Option<SourcePayload>,
35 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
36 pub extensions: Extensions,
37}
38
39#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct UniversalResponse {
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub id: Option<String>,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub model: Option<String>,
46 #[serde(default, skip_serializing_if = "Vec::is_empty")]
47 pub output: Vec<UniversalItem>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub usage: Option<Usage>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub finish_reason: Option<FinishReason>,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub status: Option<String>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub source: Option<SourcePayload>,
56 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
57 pub extensions: Extensions,
58}
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60#[serde(
61 tag = "type",
62 rename_all = "snake_case",
63 rename_all_fields = "camelCase"
64)]
65pub enum UniversalItem {
66 Message {
67 role: Role,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 id: Option<String>,
70 #[serde(default, skip_serializing_if = "Vec::is_empty")]
71 content: Vec<ContentBlock>,
72 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
73 extensions: Extensions,
74 },
75 ToolCall {
76 id: String,
77 name: String,
78 arguments: Value,
79 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
80 extensions: Extensions,
81 },
82 ToolResult {
83 tool_call_id: String,
84 #[serde(default, skip_serializing_if = "Vec::is_empty")]
85 content: Vec<ContentBlock>,
86 #[serde(default, skip_serializing_if = "is_false")]
87 is_error: bool,
88 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
89 extensions: Extensions,
90 },
91 Reasoning {
92 #[serde(default, skip_serializing_if = "Option::is_none")]
93 id: Option<String>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 text: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 encrypted: Option<String>,
98 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
99 extensions: Extensions,
100 },
101 Unknown {
102 raw: Value,
103 },
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107#[serde(
108 tag = "type",
109 rename_all = "snake_case",
110 rename_all_fields = "camelCase"
111)]
112pub enum ContentBlock {
113 Text {
114 text: String,
115 },
116 Image {
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 media_type: Option<String>,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
120 url: Option<String>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 data: Option<String>,
123 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
124 extensions: Extensions,
125 },
126 File {
127 #[serde(default, skip_serializing_if = "Option::is_none")]
128 media_type: Option<String>,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 filename: Option<String>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
132 url: Option<String>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 data: Option<String>,
135 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
136 extensions: Extensions,
137 },
138 ToolCall {
139 id: String,
140 name: String,
141 arguments: Value,
142 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
143 extensions: Extensions,
144 },
145 ToolResult {
146 tool_call_id: String,
147 #[serde(default, skip_serializing_if = "Vec::is_empty")]
148 content: Vec<ContentBlock>,
149 #[serde(default, skip_serializing_if = "is_false")]
150 is_error: bool,
151 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
152 extensions: Extensions,
153 },
154 Reasoning {
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 text: Option<String>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 encrypted: Option<String>,
159 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
160 extensions: Extensions,
161 },
162 Unknown {
163 raw: Value,
164 },
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "snake_case")]
169pub enum Role {
170 Developer,
171 System,
172 User,
173 Assistant,
174 Tool,
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
178#[serde(
179 tag = "type",
180 rename_all = "snake_case",
181 rename_all_fields = "camelCase"
182)]
183pub enum ToolChoice {
184 Auto,
185 None,
186 Required,
187 Tool { name: String },
188 ServerTool { kind: ServerToolKind },
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192#[serde(rename_all = "camelCase")]
193pub struct UniversalTool {
194 pub name: String,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub description: Option<String>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub input_schema: Option<Value>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub strict: Option<bool>,
201 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
202 pub extensions: Extensions,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "snake_case")]
207pub enum ServerToolKind {
208 WebSearch,
209 XSearch,
210 FileSearch,
211 CodeInterpreter,
212 CodeExecution,
213 Unknown,
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct ServerToolDeclaration {
219 pub kind: ServerToolKind,
220 pub wire_type: String,
221 pub source_protocol: WireProtocol,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub name: Option<String>,
224 #[serde(default, skip_serializing_if = "Value::is_null")]
225 pub config: Value,
226 pub raw: Value,
227 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
228 pub extensions: Extensions,
229}
230
231#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct GenerationConfig {
234 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub temperature: Option<f64>,
236 #[serde(default, skip_serializing_if = "Option::is_none")]
237 pub top_p: Option<f64>,
238 #[serde(default, skip_serializing_if = "Option::is_none")]
239 pub max_output_tokens: Option<u64>,
240 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
241 pub extensions: Extensions,
242}
243
244impl GenerationConfig {
245 pub fn is_empty(&self) -> bool {
246 self.temperature.is_none()
247 && self.top_p.is_none()
248 && self.max_output_tokens.is_none()
249 && self.extensions.is_empty()
250 }
251}
252
253#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct ReasoningConfig {
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub effort: Option<String>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 pub budget_tokens: Option<u64>,
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub visible: Option<bool>,
262 #[serde(default, skip_serializing_if = "Extensions::is_empty")]
263 pub extensions: Extensions,
264}
265
266#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct Usage {
269 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub input_tokens: Option<u64>,
271 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub output_tokens: Option<u64>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub total_tokens: Option<u64>,
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(rename_all = "snake_case")]
279pub enum FinishReason {
280 Stop,
281 Length,
282 ToolCall,
283 ContentFilter,
284 Error,
285 Unknown,
286}
287
288#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct SourcePayload {
291 pub protocol: WireProtocol,
292 #[serde(default, skip_serializing_if = "Option::is_none")]
293 pub raw: Option<Value>,
294}
295
296fn is_false(value: &bool) -> bool {
297 !*value
298}