use crate::detail::ffi::*;
use crate::detail::session::NativeResponse;
use crate::error::Result;
use crate::item::Item;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FinishReason {
#[default]
None,
Error,
Stop,
Length,
ToolCalls,
}
impl FinishReason {
pub(crate) fn from_native(value: flFinishReason) -> FinishReason {
match value {
FOUNDRY_LOCAL_FINISH_ERROR => FinishReason::Error,
FOUNDRY_LOCAL_FINISH_STOP => FinishReason::Stop,
FOUNDRY_LOCAL_FINISH_LENGTH => FinishReason::Length,
FOUNDRY_LOCAL_FINISH_TOOL_CALLS => FinishReason::ToolCalls,
_ => FinishReason::None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
impl Usage {
pub(crate) fn from_native(prompt: i64, completion: i64, total: i64) -> Usage {
Usage {
prompt_tokens: clamp_u32(prompt),
completion_tokens: clamp_u32(completion),
total_tokens: clamp_u32(total),
}
}
}
fn clamp_u32(v: i64) -> u32 {
v.clamp(0, u32::MAX as i64) as u32
}
#[derive(Debug, Clone, PartialEq)]
pub struct Response {
pub items: Vec<Item>,
pub finish_reason: FinishReason,
pub usage: Usage,
}
impl Response {
pub(crate) fn from_native(native: &NativeResponse) -> Result<Response> {
let items = native.items()?;
let finish_reason = FinishReason::from_native(native.finish_reason());
let (prompt, completion, total) = native.usage()?;
Ok(Response {
items,
finish_reason,
usage: Usage::from_native(prompt, completion, total),
})
}
pub fn text(&self) -> String {
let mut out = String::new();
for item in &self.items {
match item {
Item::Text { text, .. } => out.push_str(text),
Item::Message(message) => out.push_str(&message.text()),
_ => {}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finish_reason_from_native() {
assert_eq!(
FinishReason::from_native(FOUNDRY_LOCAL_FINISH_STOP),
FinishReason::Stop
);
assert_eq!(FinishReason::from_native(9999), FinishReason::None);
}
#[test]
fn usage_clamps_negatives() {
let u = Usage::from_native(-1, 5, i64::MAX);
assert_eq!(u.prompt_tokens, 0);
assert_eq!(u.completion_tokens, 5);
assert_eq!(u.total_tokens, u32::MAX);
}
#[test]
fn response_text_concatenates_text_and_message_items() {
let resp = Response {
items: vec![
Item::text("a"),
Item::bytes(vec![1]),
Item::text("b"),
Item::assistant_message(vec![Item::text("c")]),
],
finish_reason: FinishReason::Stop,
usage: Usage::default(),
};
assert_eq!(resp.text(), "abc");
}
}