foundry_local_sdk/
response.rs1use crate::detail::ffi::*;
6use crate::detail::session::NativeResponse;
7use crate::error::Result;
8use crate::item::Item;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12pub enum FinishReason {
13 #[default]
15 None,
16 Error,
18 Stop,
20 Length,
22 ToolCalls,
24}
25
26impl FinishReason {
27 pub(crate) fn from_native(value: flFinishReason) -> FinishReason {
28 match value {
29 FOUNDRY_LOCAL_FINISH_ERROR => FinishReason::Error,
30 FOUNDRY_LOCAL_FINISH_STOP => FinishReason::Stop,
31 FOUNDRY_LOCAL_FINISH_LENGTH => FinishReason::Length,
32 FOUNDRY_LOCAL_FINISH_TOOL_CALLS => FinishReason::ToolCalls,
33 _ => FinishReason::None,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
40pub struct Usage {
41 pub prompt_tokens: u32,
43 pub completion_tokens: u32,
45 pub total_tokens: u32,
47}
48
49impl Usage {
50 pub(crate) fn from_native(prompt: i64, completion: i64, total: i64) -> Usage {
53 Usage {
54 prompt_tokens: clamp_u32(prompt),
55 completion_tokens: clamp_u32(completion),
56 total_tokens: clamp_u32(total),
57 }
58 }
59}
60
61fn clamp_u32(v: i64) -> u32 {
62 v.clamp(0, u32::MAX as i64) as u32
63}
64
65#[derive(Debug, Clone, PartialEq)]
68pub struct Response {
69 pub items: Vec<Item>,
71 pub finish_reason: FinishReason,
73 pub usage: Usage,
75}
76
77impl Response {
78 pub(crate) fn from_native(native: &NativeResponse) -> Result<Response> {
80 let items = native.items()?;
81 let finish_reason = FinishReason::from_native(native.finish_reason());
82 let (prompt, completion, total) = native.usage()?;
83 Ok(Response {
84 items,
85 finish_reason,
86 usage: Usage::from_native(prompt, completion, total),
87 })
88 }
89
90 pub fn text(&self) -> String {
97 let mut out = String::new();
98 for item in &self.items {
99 match item {
100 Item::Text { text, .. } => out.push_str(text),
101 Item::Message(message) => out.push_str(&message.text()),
102 _ => {}
103 }
104 }
105 out
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn finish_reason_from_native() {
115 assert_eq!(
116 FinishReason::from_native(FOUNDRY_LOCAL_FINISH_STOP),
117 FinishReason::Stop
118 );
119 assert_eq!(FinishReason::from_native(9999), FinishReason::None);
120 }
121
122 #[test]
123 fn usage_clamps_negatives() {
124 let u = Usage::from_native(-1, 5, i64::MAX);
125 assert_eq!(u.prompt_tokens, 0);
126 assert_eq!(u.completion_tokens, 5);
127 assert_eq!(u.total_tokens, u32::MAX);
128 }
129
130 #[test]
131 fn response_text_concatenates_text_and_message_items() {
132 let resp = Response {
133 items: vec![
134 Item::text("a"),
135 Item::bytes(vec![1]),
136 Item::text("b"),
137 Item::assistant_message(vec![Item::text("c")]),
138 ],
139 finish_reason: FinishReason::Stop,
140 usage: Usage::default(),
141 };
142 assert_eq!(resp.text(), "abc");
143 }
144}