Skip to main content

aimux_core/
result.rs

1//! Result types for `do_generate` and `do_stream`.
2
3use std::collections::HashMap;
4
5use futures::Stream;
6use serde::{Deserialize, Serialize};
7use std::pin::Pin;
8use ts_rs::TS;
9
10use crate::error::AiMuxError;
11use crate::shared::FileData;
12use crate::stream_part::StreamPart;
13use crate::types::{FinishReason, ProviderMetadata, ResponseMetadata, Usage, Warning};
14
15use serde_json::Value;
16
17/// A content item in the generation result.
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
19#[ts(export)]
20pub enum GenerateContent {
21    /// Generated text.
22    Text {
23        text: String,
24        #[serde(default, skip_serializing_if = "Option::is_none")]
25        provider_metadata: Option<ProviderMetadata>,
26    },
27    /// A tool call requested by the model.
28    ToolCall {
29        tool_call_id: String,
30        tool_name: String,
31        input: serde_json::Value,
32        /// Whether the tool call will be executed by the provider.
33        /// If false/unset, the tool call is executed by the client.
34        #[serde(default, skip_serializing_if = "Option::is_none")]
35        provider_executed: Option<bool>,
36        /// Whether the tool is dynamic (defined at runtime, e.g. MCP tools).
37        #[serde(default, skip_serializing_if = "Option::is_none")]
38        dynamic: Option<bool>,
39        /// Provider-assigned thought signature (e.g. Google Gemini
40        /// `thoughtSignature`). Must be echoed back verbatim on the follow-up
41        /// turn when the tool result is sent.
42        #[serde(default, skip_serializing_if = "Option::is_none")]
43        thought_signature: Option<String>,
44        /// Additional provider-specific metadata for the tool call.
45        #[serde(default, skip_serializing_if = "Option::is_none")]
46        provider_metadata: Option<ProviderMetadata>,
47    },
48    /// A source / citation (e.g. URL citation from search-preview models).
49    Source {
50        id: String,
51        source_type: String,
52        url: Option<String>,
53        title: Option<String>,
54        #[serde(default, skip_serializing_if = "Option::is_none")]
55        provider_metadata: Option<ProviderMetadata>,
56    },
57    /// A reasoning / thinking segment produced by the model.
58    ///
59    /// Mirrors the Vercel AI SDK `reasoning` content type. `provider_metadata`
60    /// carries provider-specific data such as the Anthropic thinking-block
61    /// signature (`providerMetadata.anthropic.signature`).
62    Reasoning {
63        text: String,
64        provider_metadata: Option<ProviderMetadata>,
65    },
66    /// A file generated by the model (e.g. an image or document).
67    ///
68    /// Mirrors the Vercel AI SDK `file` content type. `data` is a tagged union
69    /// covering base64 data, URL, or provider reference.
70    File {
71        data: FileData,
72        media_type: String,
73        #[serde(default, skip_serializing_if = "Option::is_none")]
74        provider_metadata: Option<ProviderMetadata>,
75    },
76    /// A tool result from a provider-executed tool (e.g. xAI file_search,
77    /// web_search). Emitted alongside the preceding `ToolCall` when the
78    /// provider executes the tool server-side.
79    ToolResult {
80        tool_call_id: String,
81        tool_name: String,
82        result: Value,
83        /// Whether the result is an error or error message.
84        #[serde(default, skip_serializing_if = "Option::is_none")]
85        is_error: Option<bool>,
86        /// Whether the result is preliminary (replaces prior, e.g. image previews).
87        #[serde(default, skip_serializing_if = "Option::is_none")]
88        preliminary: Option<bool>,
89        /// Whether the tool is dynamic (defined at runtime, e.g. MCP tools).
90        #[serde(default, skip_serializing_if = "Option::is_none")]
91        dynamic: Option<bool>,
92        /// Additional provider-specific metadata for the tool result.
93        #[serde(default, skip_serializing_if = "Option::is_none")]
94        provider_metadata: Option<ProviderMetadata>,
95    },
96}
97
98// ── M7: top-level aggregation types ─────────────────────────────────────────
99//
100// Simplified views extracted from `GenerateContent` for the top-level fields of
101// `GenerateTextResult`. They omit provider-internal metadata so the common case
102// (reading reasoning/sources/files) doesn't require digging through `raw.content`.
103
104/// A reasoning / thinking segment, extracted to the top level (M7).
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
106#[ts(export)]
107pub struct ReasoningPart {
108    /// The reasoning text.
109    pub text: String,
110}
111
112/// A source / citation, extracted to the top level (M7).
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
114#[ts(export)]
115pub struct SourcePart {
116    pub id: String,
117    pub source_type: String,
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub url: Option<String>,
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub title: Option<String>,
122}
123
124/// A file generated by the model, extracted to the top level (M7).
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
126#[ts(export)]
127pub struct FilePart {
128    pub data: FileData,
129    pub media_type: String,
130}
131
132/// Aggregated result of `stream_text().consume()` (M11). Mirrors
133/// `GenerateTextResult`'s user-facing fields (without `raw`, since streaming
134/// has no `GenerateResult` equivalent).
135#[derive(Debug, Clone, Serialize, Deserialize, TS)]
136#[ts(export)]
137pub struct StreamTextResultAggregated {
138    /// The generated text (concatenated `TextDelta`).
139    pub text: String,
140    /// Reasoning / thinking segments.
141    #[serde(default)]
142    pub reasoning: Vec<ReasoningPart>,
143    /// Concatenated reasoning text.
144    #[serde(default)]
145    pub reasoning_text: String,
146    /// Tool calls requested by the model.
147    #[serde(default)]
148    pub tool_calls: Vec<crate::tool::ToolCall>,
149    /// Sources / citations.
150    #[serde(default)]
151    pub sources: Vec<SourcePart>,
152    /// Files generated by the model.
153    #[serde(default)]
154    pub files: Vec<FilePart>,
155    /// Why generation stopped.
156    pub finish_reason: crate::types::FinishReason,
157    /// Raw provider-specific finish reason string.
158    #[serde(default)]
159    pub raw_finish_reason: Option<String>,
160    /// Token usage.
161    #[serde(default)]
162    pub usage: crate::types::Usage,
163    /// Total token usage across all steps. In single-step mode (aimux's
164    /// default), `total_usage` equals `usage`. Provided for AI SDK parity.
165    #[serde(default)]
166    pub total_usage: crate::types::Usage,
167    /// Warnings from the provider.
168    #[serde(default)]
169    pub warnings: Vec<crate::types::Warning>,
170    /// Provider-specific metadata from the Finish chunk.
171    #[serde(default)]
172    pub provider_metadata: Option<serde_json::Value>,
173    /// Response metadata (id, timestamp, model_id) if emitted by the stream.
174    #[serde(default)]
175    pub response: Option<crate::types::ResponseMetadata>,
176    /// Assistant messages ready to append for the next turn.
177    #[serde(default)]
178    pub response_messages: Vec<crate::message::ModelMessage>,
179}
180
181/// Result of `LanguageModel::do_generate` (non-streaming).
182#[derive(Debug, Serialize, Deserialize, TS)]
183#[ts(export)]
184pub struct GenerateResult {
185    /// Generated content items (text, tool calls, etc.).
186    pub content: Vec<GenerateContent>,
187    /// Why generation stopped.
188    pub finish_reason: FinishReason,
189    /// Token usage.
190    pub usage: Usage,
191    /// Warnings issued by the provider.
192    pub warnings: Vec<Warning>,
193    /// Provider-specific metadata.
194    pub provider_metadata: Option<ProviderMetadata>,
195    /// Response metadata (id, timestamp, model_id).
196    pub response: ResponseMetadata,
197    /// The request body that was sent (for debugging).
198    pub request_body: Option<serde_json::Value>,
199    /// Response headers.
200    pub response_headers: Option<HashMap<String, String>>,
201}
202
203/// Result of `LanguageModel::do_stream` (streaming).
204pub struct StreamResult {
205    /// The stream of `StreamPart` items.
206    pub stream: Pin<Box<dyn Stream<Item = Result<StreamPart, AiMuxError>> + Send>>,
207    /// The request body that was sent (for debugging).
208    pub request_body: Option<serde_json::Value>,
209    /// Response headers.
210    pub response_headers: Option<HashMap<String, String>>,
211}
212
213impl std::fmt::Debug for StreamResult {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        f.debug_struct("StreamResult")
216            .field("request_body", &self.request_body)
217            .field("response_headers", &self.response_headers)
218            .field("stream", &"<stream>")
219            .finish()
220    }
221}