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