1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6use crate::{BoxFuture, ChatMessage, ToolCall, Usage};
7
8#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ToolChoice {
11 #[default]
12 Auto,
13 None,
14 Required,
15}
16
17#[derive(Clone, Debug, PartialEq, Serialize)]
18pub struct GatewayRequest {
19 pub model: String,
20 pub messages: Vec<ChatMessage>,
21 pub tools: Vec<ToolAdvertisement>,
22 pub tool_choice: ToolChoice,
23 pub max_output_tokens: Option<u32>,
24}
25
26#[derive(Clone, Debug, PartialEq, Serialize)]
27pub struct ToolAdvertisement {
28 pub name: String,
29 pub description: String,
30 pub input_schema: serde_json::Value,
31 pub kind: ToolAdvertisementKind,
32}
33
34impl ToolAdvertisement {
35 pub fn function(
36 name: impl Into<String>,
37 description: impl Into<String>,
38 input_schema: serde_json::Value,
39 ) -> Self {
40 Self {
41 name: name.into(),
42 description: description.into(),
43 input_schema,
44 kind: ToolAdvertisementKind::Function,
45 }
46 }
47
48 pub fn provider(
49 id: impl Into<String>,
50 name: impl Into<String>,
51 arguments: serde_json::Value,
52 ) -> Self {
53 Self {
54 name: name.into(),
55 description: String::new(),
56 input_schema: serde_json::Value::Null,
57 kind: ToolAdvertisementKind::Provider {
58 id: id.into(),
59 arguments,
60 },
61 }
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Serialize)]
71pub enum ToolAdvertisementKind {
72 Function,
73 Provider {
74 id: String,
75 arguments: serde_json::Value,
76 },
77}
78
79#[derive(Clone, Debug, Default, PartialEq)]
80pub struct GatewayResponse {
81 pub content: Option<String>,
82 pub tool_calls: Vec<ToolCall>,
83 pub generation_id: Option<String>,
84 pub finish_reason: Option<crate::FinishReason>,
85 pub usage: Usage,
86 pub delivery_ambiguous: bool,
89}
90
91#[derive(Clone, Debug, PartialEq)]
92pub enum GatewayEvent {
93 ContentDelta(String),
94 ReasoningDelta(String),
95 ToolStarted { id: String, name: String },
96}
97
98pub trait GatewayEventSink: Send {
99 fn emit(&mut self, event: GatewayEvent);
100}
101
102#[derive(Debug, Error)]
103pub enum GatewayError {
104 #[error("request was cancelled")]
105 Cancelled,
106 #[error("authentication failed")]
107 Authentication,
108 #[error("provider rejected the request: {0}")]
109 Rejected(String),
110 #[error("gateway transport failed before delivery")]
111 DefinitelyUnsent,
112 #[error("gateway transport failed after possible delivery")]
113 PossiblySent,
114 #[error("gateway response was invalid: {0}")]
115 InvalidResponse(String),
116 #[error("gateway is unavailable: {0}")]
117 Unavailable(String),
118}
119
120pub trait Gateway: Send + Sync {
125 fn complete<'a>(
126 &'a self,
127 request: GatewayRequest,
128 events: &'a mut dyn GatewayEventSink,
129 ) -> BoxFuture<'a, Result<GatewayResponse, GatewayError>>;
130}
131
132#[derive(Clone)]
137pub struct SafeRetryGateway {
138 inner: Arc<dyn Gateway>,
139 max_unsent_retries: usize,
140}
141
142impl SafeRetryGateway {
143 pub fn new(inner: Arc<dyn Gateway>) -> Self {
144 Self {
145 inner,
146 max_unsent_retries: 1,
147 }
148 }
149
150 pub fn with_max_unsent_retries(mut self, max_unsent_retries: usize) -> Self {
151 self.max_unsent_retries = max_unsent_retries;
152 self
153 }
154}
155
156impl Gateway for SafeRetryGateway {
157 fn complete<'a>(
158 &'a self,
159 request: GatewayRequest,
160 events: &'a mut dyn GatewayEventSink,
161 ) -> BoxFuture<'a, Result<GatewayResponse, GatewayError>> {
162 Box::pin(async move {
163 let mut retries = 0usize;
164 loop {
165 match self.inner.complete(request.clone(), events).await {
166 Err(GatewayError::DefinitelyUnsent) if retries < self.max_unsent_retries => {
167 retries += 1;
168 }
169 result => return result,
170 }
171 }
172 })
173 }
174}
175
176impl std::fmt::Debug for SafeRetryGateway {
177 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 formatter
179 .debug_struct("SafeRetryGateway")
180 .field("max_unsent_retries", &self.max_unsent_retries)
181 .finish_non_exhaustive()
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use std::collections::VecDeque;
188 use std::sync::Mutex;
189
190 use super::*;
191
192 struct ScriptedGateway(Mutex<VecDeque<Result<GatewayResponse, GatewayError>>>);
193
194 impl Gateway for ScriptedGateway {
195 fn complete<'a>(
196 &'a self,
197 _request: GatewayRequest,
198 _events: &'a mut dyn GatewayEventSink,
199 ) -> BoxFuture<'a, Result<GatewayResponse, GatewayError>> {
200 Box::pin(async move { self.0.lock().unwrap().pop_front().unwrap() })
201 }
202 }
203
204 struct Events;
205
206 impl GatewayEventSink for Events {
207 fn emit(&mut self, _event: GatewayEvent) {}
208 }
209
210 fn request() -> GatewayRequest {
211 GatewayRequest {
212 model: "model".into(),
213 messages: Vec::new(),
214 tools: Vec::new(),
215 tool_choice: ToolChoice::Auto,
216 max_output_tokens: None,
217 }
218 }
219
220 #[test]
221 fn retries_only_definitely_unsent_requests() {
222 let successful = Arc::new(ScriptedGateway(Mutex::new(VecDeque::from([
223 Err(GatewayError::DefinitelyUnsent),
224 Ok(GatewayResponse::default()),
225 ]))));
226 let retrying = SafeRetryGateway::new(successful.clone());
227 assert!(pollster::block_on(retrying.complete(request(), &mut Events)).is_ok());
228 assert!(successful.0.lock().unwrap().is_empty());
229
230 let ambiguous = Arc::new(ScriptedGateway(Mutex::new(VecDeque::from([
231 Err(GatewayError::PossiblySent),
232 Ok(GatewayResponse::default()),
233 ]))));
234 let retrying = SafeRetryGateway::new(ambiguous.clone());
235 assert!(matches!(
236 pollster::block_on(retrying.complete(request(), &mut Events)),
237 Err(GatewayError::PossiblySent)
238 ));
239 assert_eq!(ambiguous.0.lock().unwrap().len(), 1);
240 }
241}