1use std::future::IntoFuture;
7use std::sync::Arc;
8
9pub(super) use ferrin_spec::BatchId;
10use ferrin_spec::BatchRef;
11use ferrin_spec::BoxFuture;
12use ferrin_spec::batch::BatchCancelResult;
13use ferrin_spec::batch::BatchListOptions;
14use ferrin_spec::batch::BatchListResult;
15use ferrin_spec::batch::BatchOperationOptions;
16use ferrin_spec::batch::BatchStatus;
17use ferrin_spec::error::ProviderError;
18use ferrin_tool::ToolSet;
19use futures_util::StreamExt;
20use futures_util::stream;
21use tracing::Instrument;
22
23use crate::error::Error;
24use crate::modality::ModalityOptions;
25use crate::modality::impl_modality_builder;
26use crate::modality_stream::StreamDeadline;
27use crate::retry::retry;
28
29use super::result::BatchResults;
30use super::result::convert_item;
31use super::service_identity;
32use crate::telemetry::spans;
33
34macro_rules! batch_operation {
35 ($(#[$meta:meta])* $name:ident, $ty:ident) => {
36 $(#[$meta])*
37 #[must_use]
38 pub fn $name(batch: impl Into<BatchRef>, batch_id: impl Into<BatchId>) -> $ty {
39 $ty {
40 batch: batch.into(),
41 batch_id: batch_id.into(),
42 base: ModalityOptions::default(),
43 }
44 }
45 };
46}
47
48batch_operation!(
49 get_batch_status,
51 GetBatchStatus
52);
53
54#[derive(Debug)]
56pub struct GetBatchStatus {
57 batch: BatchRef,
58 batch_id: BatchId,
59 base: ModalityOptions,
60}
61
62impl_modality_builder!(GetBatchStatus);
63
64impl IntoFuture for GetBatchStatus {
65 type Output = Result<BatchStatus, Error>;
66 type IntoFuture = BoxFuture<'static, Self::Output>;
67
68 fn into_future(self) -> Self::IntoFuture {
69 Box::pin(async move {
70 let identity = service_identity(&self.batch);
71 let span = spans::modality_span("get_batch_status", &identity);
72 let batch = self.batch.clone();
73 let base = self.base.clone();
74 let batch_id = self.batch_id;
75 base.run(|base, token| {
76 async move {
77 let headers = base.request_headers();
78 retry(&base.retry_policy, &token, |_| {
79 let options = BatchOperationOptions {
80 batch_id: batch_id.clone(),
81 provider_options: base.provider_options.clone(),
82 headers: headers.clone(),
83 cancellation: token.child_token(),
84 };
85 let batch = &batch;
86 async move {
87 batch
88 .do_get_batch_status(options)
89 .await
90 .map_err(Error::from)
91 }
92 })
93 .await
94 }
95 .instrument(span)
96 })
97 .await
98 })
99 }
100}
101
102#[must_use]
104pub fn get_batch_results(
105 batch: impl Into<BatchRef>,
106 batch_id: impl Into<BatchId>,
107) -> GetBatchResults {
108 GetBatchResults {
109 batch: batch.into(),
110 batch_id: batch_id.into(),
111 tools: ToolSet::new(),
112 base: ModalityOptions::default(),
113 }
114}
115
116#[derive(Debug)]
118pub struct GetBatchResults {
119 batch: BatchRef,
120 batch_id: BatchId,
121 tools: ToolSet,
122 base: ModalityOptions,
123}
124
125impl GetBatchResults {
126 #[must_use]
128 pub fn tools(mut self, tools: ToolSet) -> Self {
129 self.tools = tools;
130 self
131 }
132}
133
134impl_modality_builder!(GetBatchResults);
135
136impl IntoFuture for GetBatchResults {
137 type Output = Result<BatchResults, Error>;
138 type IntoFuture = BoxFuture<'static, Self::Output>;
139
140 fn into_future(self) -> Self::IntoFuture {
141 Box::pin(async move {
142 let identity = service_identity(&self.batch);
143 let span = spans::modality_span("get_batch_results", &identity);
144 let batch = self.batch.clone();
145 let base = self.base.clone();
146 let batch_id = self.batch_id;
147 let tools = Arc::new(self.tools);
148 let deadline = StreamDeadline::new(&base.cancellation, base.timeout);
149 let token = deadline.cancellation.clone();
150 let stream = deadline
151 .run(
152 async move {
153 let headers = base.request_headers();
154 retry(&base.retry_policy, &token, |_| {
155 let options = BatchOperationOptions {
156 batch_id: batch_id.clone(),
157 provider_options: base.provider_options.clone(),
158 headers: headers.clone(),
159 cancellation: token.child_token(),
160 };
161 let batch = &batch;
162 async move {
163 batch
164 .do_get_batch_results(options)
165 .await
166 .map_err(Error::from)
167 }
168 })
169 .await
170 }
171 .instrument(span),
172 )
173 .await?;
174 let converted = stream::unfold(Some((deadline, stream, tools)), |state| async move {
175 let (deadline, mut stream, tools) = state?;
176 let item = deadline
177 .run(async {
178 match stream.next().await {
179 Some(Ok(item)) => Ok(Some(convert_item(item, &tools).await)),
180 Some(Err(error)) => Err(Error::from(error)),
181 None => Ok(None),
182 }
183 })
184 .await;
185 match item {
186 Ok(Some(item)) => Some((Ok(item), Some((deadline, stream, tools)))),
187 Ok(None) => None,
188 Err(error) => Some((Err(error), None)),
189 }
190 });
191 Ok(Box::pin(converted) as BatchResults)
192 })
193 }
194}
195
196batch_operation!(
197 cancel_batch,
200 CancelBatch
201);
202
203#[derive(Debug)]
205pub struct CancelBatch {
206 batch: BatchRef,
207 batch_id: BatchId,
208 base: ModalityOptions,
209}
210
211impl_modality_builder!(@no_retry CancelBatch);
212
213impl IntoFuture for CancelBatch {
214 type Output = Result<BatchCancelResult, Error>;
215 type IntoFuture = BoxFuture<'static, Self::Output>;
216
217 fn into_future(self) -> Self::IntoFuture {
218 Box::pin(async move {
219 let identity = service_identity(&self.batch);
220 let span = spans::modality_span("cancel_batch", &identity);
221 if !self.batch.supports_cancel_batch() {
222 return Err(Error::from(ProviderError::unsupported(
223 "batch cancellation",
224 )));
225 }
226 let batch = self.batch.clone();
227 let base = self.base.clone();
228 let batch_id = self.batch_id;
229 base.run(|base, token| {
230 async move {
231 batch
232 .do_cancel_batch(BatchOperationOptions {
233 batch_id,
234 provider_options: base.provider_options.clone(),
235 headers: base.request_headers(),
236 cancellation: token,
237 })
238 .await
239 .map_err(Error::from)
240 }
241 .instrument(span)
242 })
243 .await
244 })
245 }
246}
247
248#[must_use]
251pub fn list_batches(batch: impl Into<BatchRef>) -> ListBatches {
252 ListBatches {
253 batch: batch.into(),
254 limit: None,
255 cursor: None,
256 base: ModalityOptions::default(),
257 }
258}
259
260#[derive(Debug)]
262pub struct ListBatches {
263 batch: BatchRef,
264 limit: Option<usize>,
265 cursor: Option<String>,
266 base: ModalityOptions,
267}
268
269impl ListBatches {
270 #[must_use]
272 pub fn limit(mut self, limit: usize) -> Self {
273 self.limit = Some(limit);
274 self
275 }
276
277 #[must_use]
279 pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
280 self.cursor = Some(cursor.into());
281 self
282 }
283}
284
285impl_modality_builder!(ListBatches);
286
287impl IntoFuture for ListBatches {
288 type Output = Result<BatchListResult, Error>;
289 type IntoFuture = BoxFuture<'static, Self::Output>;
290
291 fn into_future(self) -> Self::IntoFuture {
292 Box::pin(async move {
293 let identity = service_identity(&self.batch);
294 let span = spans::modality_span("list_batches", &identity);
295 if !self.batch.supports_list_batches() {
296 return Err(Error::from(ProviderError::unsupported("batch listing")));
297 }
298 let batch = self.batch.clone();
299 let base = self.base.clone();
300 let limit = self.limit;
301 let cursor = self.cursor;
302 base.run(|base, token| {
303 async move {
304 let headers = base.request_headers();
305 retry(&base.retry_policy, &token, |_| {
306 let options = BatchListOptions {
307 limit,
308 cursor: cursor.clone(),
309 provider_options: base.provider_options.clone(),
310 headers: headers.clone(),
311 cancellation: token.child_token(),
312 };
313 let batch = &batch;
314 async move { batch.do_list_batches(options).await.map_err(Error::from) }
315 })
316 .await
317 }
318 .instrument(span)
319 })
320 .await
321 })
322 }
323}