use ferrin_spec::BoxStream;
use ferrin_spec::FinishReason;
use ferrin_spec::ProviderMetadata;
use ferrin_spec::RequestMetadata;
use ferrin_spec::ResponseMetadata;
use ferrin_spec::Usage;
use ferrin_spec::Warning;
use ferrin_spec::batch::BatchError;
use ferrin_spec::batch::BatchItem;
use ferrin_spec::batch::BatchItemResult;
use ferrin_spec::image_model::ImageUsage;
use ferrin_tool::ToolSet;
use crate::error::Error;
use crate::generate_text::RefineToolInputs;
use crate::generate_text::StepContent;
use crate::generate_text::parse_tool_call::ParseContext;
use crate::generate_text::run::convert_content;
use crate::image::GeneratedImage;
use crate::image::convert_images;
#[derive(Debug, Clone, PartialEq)]
pub struct TextBatchResult {
pub content: Vec<StepContent>,
pub finish_reason: FinishReason,
pub usage: Usage,
pub warnings: Vec<Warning>,
pub request: RequestMetadata,
pub response: ResponseMetadata,
pub provider_metadata: Option<ProviderMetadata>,
}
impl TextBatchResult {
#[must_use]
pub fn text(&self) -> String {
self.content
.iter()
.filter_map(|part| match part {
StepContent::Text { text, .. } => Some(text.as_str()),
_ => None,
})
.collect()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageBatchResult {
pub images: Vec<GeneratedImage>,
pub warnings: Vec<Warning>,
pub response: ResponseMetadata,
pub provider_metadata: Option<ProviderMetadata>,
pub usage: Option<ImageUsage>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum BatchResultItem {
Text(Box<BatchItem<TextBatchResult>>),
Image(Box<BatchItem<ImageBatchResult>>),
}
impl BatchResultItem {
#[must_use]
pub fn id(&self) -> &str {
match self {
Self::Text(item) => item.id(),
Self::Image(item) => item.id(),
}
}
}
pub type BatchResults = BoxStream<'static, Result<BatchResultItem, Error>>;
pub(super) fn unsupported_status<R>(id: String) -> BatchItem<R> {
BatchItem::Failed {
id,
error: BatchError {
message: "unsupported batch item status".to_owned(),
error_type: None,
code: None,
status_code: None,
},
provider_metadata: None,
}
}
pub(super) async fn convert_item(item: BatchItemResult, tools: &ToolSet) -> BatchResultItem {
let item_id = item.id().to_owned();
match item {
BatchItemResult::Text(item) => BatchResultItem::Text(Box::new(match *item {
BatchItem::Succeeded { id, result } => {
let refine = RefineToolInputs::default();
let parse_ctx = ParseContext {
tools,
tool_choice: None,
repair: None,
refine: &refine,
system: None,
messages: &[],
};
let (content, _calls) = convert_content(&result.content, &parse_ctx, tools).await;
BatchItem::Succeeded {
id,
result: TextBatchResult {
content,
finish_reason: result.finish_reason,
usage: result.usage,
warnings: result.warnings,
request: result.request,
response: result.response,
provider_metadata: result.provider_metadata,
},
}
}
BatchItem::Failed {
id,
error,
provider_metadata,
} => BatchItem::Failed {
id,
error,
provider_metadata,
},
BatchItem::Cancelled {
id,
error,
provider_metadata,
} => BatchItem::Cancelled {
id,
error,
provider_metadata,
},
BatchItem::Expired {
id,
error,
provider_metadata,
} => BatchItem::Expired {
id,
error,
provider_metadata,
},
#[allow(unreachable_patterns, reason = "BatchItem is non-exhaustive")]
_ => unsupported_status(item_id),
})),
BatchItemResult::Image(item) => BatchResultItem::Image(Box::new(match *item {
BatchItem::Succeeded { id, result } => BatchItem::Succeeded {
id,
result: ImageBatchResult {
images: convert_images(&result),
warnings: result.warnings,
response: result.response,
provider_metadata: result.provider_metadata,
usage: result.usage,
},
},
BatchItem::Failed {
id,
error,
provider_metadata,
} => BatchItem::Failed {
id,
error,
provider_metadata,
},
BatchItem::Cancelled {
id,
error,
provider_metadata,
} => BatchItem::Cancelled {
id,
error,
provider_metadata,
},
BatchItem::Expired {
id,
error,
provider_metadata,
} => BatchItem::Expired {
id,
error,
provider_metadata,
},
#[allow(unreachable_patterns, reason = "BatchItem is non-exhaustive")]
_ => unsupported_status(item_id),
})),
#[allow(unreachable_patterns, reason = "BatchItemResult is non-exhaustive")]
_ => BatchResultItem::Text(Box::new(unsupported_status(item_id))),
}
}