use crate::completion::FinishReason;
use crate::error::ProviderError;
use crate::providers::openai::wire::Quirks;
pub(crate) fn provider_error_envelope(data: &str) -> Option<ProviderError> {
let value = serde_json::from_str::<serde_json::Value>(data).ok()?;
let error = value
.get("error")
.filter(|error| error.is_object() || error.as_str().is_some_and(|s| !s.is_empty()))?;
if value
.get("choices")
.and_then(serde_json::Value::as_array)
.is_some_and(|choices| !choices.is_empty())
{
return None;
}
if let Some(message) = error.get("message").and_then(serde_json::Value::as_str) {
tracing::warn!(message, "provider returned a streaming error event");
}
Some(ProviderError::from_provider_body(data))
}
pub(crate) const CHAT_FINISHES: &[(&str, FinishReason)] = &[
("stop", FinishReason::Stop),
("end", FinishReason::Stop),
("eos", FinishReason::Stop),
("end_turn", FinishReason::Stop),
("stop_sequence", FinishReason::Stop),
("length", FinishReason::Length),
("max_tokens", FinishReason::Length),
("model_length", FinishReason::Length),
("tool_calls", FinishReason::ToolCalls),
("function_call", FinishReason::ToolCalls),
("content_filter", FinishReason::ContentFilter),
];
pub(crate) fn finish_reason(reason: &str, quirks: &Quirks) -> FinishReason {
quirks
.finishes
.iter()
.chain(CHAT_FINISHES)
.find(|(name, _)| *name == reason)
.map_or_else(
|| FinishReason::Other(reason.to_owned()),
|(_, finish)| finish.clone(),
)
}
const NATIVE_FINISHES: &[(&str, FinishReason)] = &[
("stop", FinishReason::Stop),
("end_turn", FinishReason::Stop),
("stop_sequence", FinishReason::Stop),
("complete", FinishReason::Stop),
("completed", FinishReason::Stop),
("length", FinishReason::Length),
("max_tokens", FinishReason::Length),
("max_output_tokens", FinishReason::Length),
("model_length", FinishReason::Length),
("tool_calls", FinishReason::ToolCalls),
("function_call", FinishReason::ToolCalls),
("tool_use", FinishReason::ToolCalls),
("content_filter", FinishReason::ContentFilter),
("safety", FinishReason::ContentFilter),
("blocklist", FinishReason::ContentFilter),
("prohibited_content", FinishReason::ContentFilter),
("spii", FinishReason::ContentFilter),
];
pub(crate) fn native_finish_reason(reason: &str) -> FinishReason {
let reason = reason.to_ascii_lowercase();
NATIVE_FINISHES
.iter()
.find(|(name, _)| *name == reason)
.map_or_else(
|| FinishReason::Other(reason.clone()),
|(_, finish)| finish.clone(),
)
}
#[cfg(test)]
pub(crate) mod tests;