1use std::num::NonZeroUsize;
2use std::sync::Arc;
3
4use crate::{AttachmentRef, SchemaProjectionOverride};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum LlmTerminalReason {
9 Stop,
10 ToolUse,
11 OutputLimit,
12 ContextOverflow,
13 ContentFilter,
14 ProviderError,
15 Cancelled,
16 #[default]
17 Unknown,
18}
19
20impl LlmTerminalReason {
21 pub fn code(self) -> &'static str {
22 match self {
23 Self::Stop => "stop",
24 Self::ToolUse => "tool_use",
25 Self::OutputLimit => "output_limit",
26 Self::ContextOverflow => "context_overflow",
27 Self::ContentFilter => "content_filter",
28 Self::ProviderError => "provider_error",
29 Self::Cancelled => "cancelled",
30 Self::Unknown => "unknown",
31 }
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
36pub struct ResponseTextMeta {
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub id: Option<String>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub status: Option<String>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub phase: Option<String>,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
49pub struct LlmToolSpec {
50 pub name: String,
51 pub description: String,
52 pub input_schema: serde_json::Value,
53 pub output_schema: serde_json::Value,
54 pub input_schema_projections: Vec<SchemaProjectionOverride>,
55 pub output_schema_projections: Vec<SchemaProjectionOverride>,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
59pub enum LlmToolChoice {
60 #[default]
61 Auto,
62 None,
63 Required,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
67pub struct ProviderReplayMeta {
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub item_id: Option<String>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub opaque: Option<String>,
72}
73
74impl ProviderReplayMeta {
75 pub fn is_empty(&self) -> bool {
76 self.item_id.is_none() && self.opaque.is_none()
77 }
78}
79
80#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
81pub struct ProviderReasoningReplay {
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub item_id: Option<String>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub encrypted_content: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub signature: Option<String>,
88 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
89 pub redacted: bool,
90 #[serde(default, skip_serializing_if = "Vec::is_empty")]
91 pub summary: Vec<String>,
92}
93
94impl ProviderReasoningReplay {
95 pub fn is_empty(&self) -> bool {
96 self.item_id.is_none()
97 && self.encrypted_content.is_none()
98 && self.signature.is_none()
99 && !self.redacted
100 && self.summary.is_empty()
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
105pub enum LlmOutputPart {
106 Text {
107 text: String,
108 response_meta: Option<ResponseTextMeta>,
109 },
110 Reasoning {
117 text: String,
118 replay: Option<ProviderReasoningReplay>,
119 },
120 ToolCall {
121 call_id: String,
122 tool_name: String,
123 input_json: String,
124 replay: Option<ProviderReplayMeta>,
127 },
128}
129
130#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
131pub enum LlmRole {
132 User,
133 Assistant,
134 System,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
141pub enum LlmContentBlock {
142 Text {
143 text: Arc<str>,
144 response_meta: Option<ResponseTextMeta>,
145 cache_breakpoint: bool,
146 },
147 Image { attachment_idx: usize },
151 ToolCall {
153 call_id: String,
154 tool_name: String,
155 input_json: String,
156 replay: Option<ProviderReplayMeta>,
157 },
158 ToolResult {
161 call_id: String,
162 content: String,
163 tool_name: Option<String>,
166 },
167 Reasoning {
171 text: String,
172 replay: Option<ProviderReasoningReplay>,
173 },
174}
175
176#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
181pub struct LlmMessage {
182 pub role: LlmRole,
183 pub blocks: Arc<Vec<LlmContentBlock>>,
184}
185
186impl LlmMessage {
187 pub fn new(role: LlmRole, blocks: Vec<LlmContentBlock>) -> Self {
188 Self {
189 role,
190 blocks: Arc::new(blocks),
191 }
192 }
193
194 pub fn text(role: LlmRole, text: impl Into<Arc<str>>) -> Self {
196 Self {
197 role,
198 blocks: Arc::new(vec![LlmContentBlock::Text {
199 text: text.into(),
200 response_meta: None,
201 cache_breakpoint: false,
202 }]),
203 }
204 }
205
206 pub fn is_blank(&self) -> bool {
208 self.blocks.iter().all(|b| match b {
209 LlmContentBlock::Text { text, .. } => text.trim().is_empty(),
210 _ => false,
211 })
212 }
213}
214
215#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
216pub struct LlmAttachment {
217 pub mime: String,
218 pub data: Vec<u8>,
219 pub reference: Option<AttachmentRef>,
220}
221
222impl LlmAttachment {
223 pub fn bytes(mime: impl Into<String>, data: Vec<u8>) -> Self {
224 Self {
225 mime: mime.into(),
226 data,
227 reference: None,
228 }
229 }
230
231 pub fn reference(reference: AttachmentRef) -> Self {
232 Self {
233 mime: reference.canonical_mime().to_string(),
234 data: Vec::new(),
235 reference: Some(reference),
236 }
237 }
238
239 pub fn is_resolved(&self) -> bool {
240 !self.data.is_empty() || self.reference.is_none()
241 }
242}
243
244#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
245pub struct LlmJsonSchema {
246 pub name: String,
247 pub schema: serde_json::Value,
248 pub strict: bool,
249}
250
251#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
252pub enum LlmOutputSpec {
253 JsonObject,
254 JsonSchema(LlmJsonSchema),
255}
256
257#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
258#[serde(deny_unknown_fields)]
259pub struct GenerationOptions {
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub output_token_cap: Option<NonZeroUsize>,
262}
263
264impl GenerationOptions {
265 pub fn output_token_cap_u64(&self) -> Option<u64> {
266 self.output_token_cap
267 .map(NonZeroUsize::get)
268 .map(|value| value as u64)
269 }
270}
271
272#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
273pub struct LlmRequest {
274 pub model: String,
275 pub messages: Vec<LlmMessage>,
276 pub attachments: Vec<LlmAttachment>,
277 pub tools: Arc<Vec<LlmToolSpec>>,
278 pub tool_choice: LlmToolChoice,
279 pub model_variant: Option<String>,
280 #[serde(default)]
281 pub generation: GenerationOptions,
282 pub session_id: Option<String>,
283 pub output_spec: Option<LlmOutputSpec>,
284 #[serde(default, skip)]
285 pub stream_events: Option<LlmEventSender>,
286 #[serde(default, skip)]
287 pub provider_trace: Option<LlmProviderTraceSender>,
288}
289
290#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
291pub struct LlmUsage {
292 pub input_tokens: i64,
293 pub output_tokens: i64,
294 pub cached_input_tokens: i64,
295 pub reasoning_tokens: i64,
296}
297
298#[derive(Clone, Debug)]
299pub enum LlmStreamEvent {
300 Delta(String),
301 ReasoningDelta(String),
305 Part(LlmOutputPart),
306 Usage(LlmUsage),
307 RetryStatus {
308 wait_seconds: u64,
309 attempt: usize,
310 max_attempts: usize,
311 reason: String,
312 },
313}
314
315#[derive(Clone)]
316pub struct LlmEventSender(Arc<dyn Fn(LlmStreamEvent) + Send + Sync>);
317
318impl LlmEventSender {
319 pub fn new<F>(send: F) -> Self
320 where
321 F: Fn(LlmStreamEvent) + Send + Sync + 'static,
322 {
323 Self(Arc::new(send))
324 }
325
326 pub fn send(&self, event: LlmStreamEvent) {
327 (self.0)(event);
328 }
329}
330
331#[derive(Clone, Debug)]
332pub struct LlmProviderTraceEvent {
333 pub provider: &'static str,
334 pub event_name: String,
335 pub raw: String,
336}
337
338#[derive(Clone)]
339pub struct LlmProviderTraceSender(Arc<dyn Fn(LlmProviderTraceEvent) + Send + Sync>);
340
341impl LlmProviderTraceSender {
342 pub fn new<F>(send: F) -> Self
343 where
344 F: Fn(LlmProviderTraceEvent) + Send + Sync + 'static,
345 {
346 Self(Arc::new(send))
347 }
348
349 pub fn send(&self, event: LlmProviderTraceEvent) {
350 (self.0)(event);
351 }
352}
353
354impl std::fmt::Debug for LlmProviderTraceSender {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 f.debug_struct("LlmProviderTraceSender")
357 .finish_non_exhaustive()
358 }
359}
360
361impl std::fmt::Debug for LlmEventSender {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 f.debug_struct("LlmEventSender").finish_non_exhaustive()
364 }
365}
366
367#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
368pub struct LlmResponse {
369 pub full_text: String,
370 pub parts: Vec<LlmOutputPart>,
371 pub usage: LlmUsage,
372 pub terminal_reason: LlmTerminalReason,
373 pub terminal_diagnostic: Option<String>,
374 pub provider_usage: Option<serde_json::Value>,
375 pub request_body: Option<String>,
376 pub http_summary: Option<String>,
377}
378
379#[derive(Clone, Debug)]
380pub struct ModelSelection {
381 pub model: &'static str,
382 pub variant: Option<&'static str>,
383}