ferrin_core/generate_text/
result.rs1use ferrin_message::Message;
4use ferrin_spec::FinishReason;
5use ferrin_spec::ProviderMetadata;
6use ferrin_spec::Usage;
7use ferrin_spec::Warning;
8use ferrin_spec::language_model::Source;
9
10use super::GeneratedFile;
11use super::ParsedToolCall;
12use super::StepContent;
13use super::StepRequest;
14use super::StepResponse;
15use super::ToolResult;
16
17use super::StepResult;
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct GenerateTextResult<O> {
22 pub steps: Vec<StepResult>,
24 pub total_usage: Usage,
26 pub output: O,
28}
29
30impl<O> GenerateTextResult<O> {
31 #[must_use]
38 pub fn last_step(&self) -> &StepResult {
39 #[allow(clippy::expect_used, reason = "the loop records at least one step")]
40 self.steps.last().expect("at least one step")
41 }
42
43 #[must_use]
45 pub fn text(&self) -> String {
46 self.last_step().text()
47 }
48
49 #[must_use]
51 pub fn reasoning_text(&self) -> Option<String> {
52 self.last_step().reasoning_text()
53 }
54
55 #[must_use]
57 pub fn finish_reason(&self) -> &FinishReason {
58 &self.last_step().finish_reason
59 }
60
61 #[must_use]
65 pub fn usage(&self) -> &Usage {
66 &self.total_usage
67 }
68
69 #[must_use]
71 pub fn provider_metadata(&self) -> Option<&ProviderMetadata> {
72 self.last_step().provider_metadata.as_ref()
73 }
74
75 #[must_use]
77 pub fn warnings(&self) -> Vec<&Warning> {
78 self.steps.iter().flat_map(|step| &step.warnings).collect()
79 }
80
81 pub fn content(&self) -> impl Iterator<Item = &StepContent> {
83 self.steps.iter().flat_map(|step| &step.content)
84 }
85
86 pub fn files(&self) -> impl Iterator<Item = &GeneratedFile> {
88 self.steps.iter().flat_map(StepResult::files)
89 }
90
91 pub fn sources(&self) -> impl Iterator<Item = &Source> {
93 self.steps.iter().flat_map(StepResult::sources)
94 }
95
96 pub fn tool_calls(&self) -> impl Iterator<Item = &ParsedToolCall> {
98 self.steps.iter().flat_map(StepResult::tool_calls)
99 }
100
101 pub fn static_tool_calls(&self) -> impl Iterator<Item = &ParsedToolCall> {
103 self.tool_calls().filter(|call| !call.dynamic)
104 }
105
106 pub fn dynamic_tool_calls(&self) -> impl Iterator<Item = &ParsedToolCall> {
108 self.tool_calls().filter(|call| call.dynamic)
109 }
110
111 pub fn tool_results(&self) -> impl Iterator<Item = &ToolResult> {
113 self.steps.iter().flat_map(StepResult::tool_results)
114 }
115
116 pub fn static_tool_results(&self) -> impl Iterator<Item = &ToolResult> {
118 self.tool_results().filter(|result| !result.dynamic)
119 }
120
121 pub fn dynamic_tool_results(&self) -> impl Iterator<Item = &ToolResult> {
123 self.tool_results().filter(|result| result.dynamic)
124 }
125
126 #[must_use]
128 pub fn final_step(&self) -> &StepResult {
129 self.last_step()
130 }
131
132 #[must_use]
134 pub fn raw_finish_reason(&self) -> Option<&str> {
135 self.finish_reason().raw.as_deref()
136 }
137
138 #[must_use]
140 pub fn request(&self) -> &StepRequest {
141 &self.last_step().request
142 }
143
144 #[must_use]
146 pub fn response(&self) -> &StepResponse {
147 &self.last_step().response
148 }
149
150 #[must_use]
152 pub fn response_messages(&self) -> Vec<Message> {
153 self.steps
154 .iter()
155 .flat_map(|step| step.response.messages.iter().cloned())
156 .collect()
157 }
158
159 pub fn map_output<P>(self, f: impl FnOnce(O) -> P) -> GenerateTextResult<P> {
161 GenerateTextResult {
162 steps: self.steps,
163 total_usage: self.total_usage,
164 output: f(self.output),
165 }
166 }
167}