Skip to main content

oramacore_client/
stream_manager.rs

1//! AI session streaming functionality.
2
3use std::pin::Pin;
4use std::sync::Arc;
5use std::time::Duration;
6
7use futures::stream::{Stream, StreamExt};
8use reqwest_eventsource::{Event, EventSource};
9use serde::Serialize;
10use tokio::sync::RwLock;
11use tracing::{debug, error, info, warn};
12
13use crate::auth::Target;
14use crate::client::{ApiKeyPosition, ClientRequest, OramaClient};
15use crate::error::{OramaError, Result};
16use crate::types::*;
17use crate::utils::{generate_uuid, parse_ai_response};
18
19/// Streaming chunk types
20#[derive(Debug, Clone, PartialEq)]
21pub enum StreamChunk {
22    /// Connection opened successfully
23    ConnectionOpened,
24    /// Content chunk from the AI response
25    Content(String),
26    /// Status update from the processing pipeline
27    StatusUpdate(String),
28    /// Raw data that couldn't be parsed
29    RawData(String),
30    /// Stream completed successfully
31    Done,
32    /// Connection retry attempt
33    Retry { attempt: u32, delay_ms: u64 },
34}
35
36/// Configuration for streaming resilience
37#[derive(Debug, Clone)]
38pub struct StreamConfig {
39    /// Maximum number of retry attempts
40    pub max_retries: u32,
41    /// Initial retry delay in milliseconds
42    pub initial_retry_delay: u64,
43    /// Maximum retry delay in milliseconds (for exponential backoff)
44    pub max_retry_delay: u64,
45    /// Connection timeout in seconds
46    pub connection_timeout: u64,
47    /// Stream idle timeout in seconds
48    pub stream_timeout: u64,
49}
50
51impl Default for StreamConfig {
52    fn default() -> Self {
53        Self {
54            max_retries: 3,
55            initial_retry_delay: 1000, // 1 second
56            max_retry_delay: 30000,    // 30 seconds
57            connection_timeout: 30,    // 30 seconds
58            stream_timeout: 300,       // 5 minutes
59        }
60    }
61}
62
63/// Configuration for creating an AI session
64#[derive(Debug, Clone)]
65pub struct CreateAiSessionConfig {
66    pub llm_config: Option<LlmConfig>,
67    pub initial_messages: Option<Vec<Message>>,
68}
69
70/// Answer configuration for AI requests
71#[derive(Debug, Clone, Serialize)]
72pub struct AnswerConfig {
73    pub query: String,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub interaction_id: Option<String>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub visitor_id: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub session_id: Option<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub messages: Option<Vec<Message>>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub related: Option<RelatedQuestionsConfig>,
84    #[serde(rename = "datasourceIDs", skip_serializing_if = "Option::is_none")]
85    pub datasource_ids: Option<Vec<String>>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub min_similarity: Option<f64>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub max_documents: Option<u32>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub ragat_notation: Option<String>,
92    #[serde(rename = "LLMConfig", skip_serializing_if = "Option::is_none")]
93    pub llm_config: Option<LlmConfig>,
94}
95
96/// Interaction state for conversations
97#[derive(Debug, Clone)]
98pub struct Interaction {
99    pub id: String,
100    pub query: String,
101    pub response: String,
102    pub sources: Option<AnyObject>,
103    pub loading: bool,
104    pub error: bool,
105    pub error_message: Option<String>,
106    pub aborted: bool,
107    pub related: Option<String>,
108    pub current_step: Option<String>,
109    pub current_step_verbose: Option<String>,
110    pub selected_llm: Option<LlmConfig>,
111    pub optimized_query: Option<SearchParams>,
112    pub advanced_autoquery: Option<serde_json::Value>,
113}
114
115impl Interaction {
116    /// Create a new interaction
117    pub fn new(id: String, query: String) -> Self {
118        Self {
119            id,
120            query,
121            response: String::new(),
122            sources: None,
123            loading: true,
124            error: false,
125            error_message: None,
126            aborted: false,
127            related: None,
128            current_step: Some("starting".to_string()),
129            current_step_verbose: None,
130            selected_llm: None,
131            optimized_query: None,
132            advanced_autoquery: None,
133        }
134    }
135}
136
137/// AI session stream manager
138#[derive(Debug)]
139pub struct OramaCoreStream {
140    collection_id: String,
141    client: OramaClient,
142    session_id: String,
143    llm_config: Option<LlmConfig>,
144    messages: Arc<RwLock<Vec<Message>>>,
145    state: Arc<RwLock<Vec<Interaction>>>,
146    last_interaction_params: Arc<RwLock<Option<AnswerConfig>>>,
147    stream_config: StreamConfig,
148}
149
150impl OramaCoreStream {
151    /// Create a new AI session stream
152    pub async fn new(collection_id: String, client: OramaClient) -> Result<Self> {
153        Ok(Self {
154            collection_id,
155            client,
156            session_id: generate_uuid(),
157            llm_config: None,
158            messages: Arc::new(RwLock::new(Vec::new())),
159            state: Arc::new(RwLock::new(Vec::new())),
160            last_interaction_params: Arc::new(RwLock::new(None)),
161            stream_config: StreamConfig::default(),
162        })
163    }
164
165    /// Create a new AI session stream with configuration
166    pub async fn with_config(
167        collection_id: String,
168        client: OramaClient,
169        config: CreateAiSessionConfig,
170    ) -> Result<Self> {
171        let messages = config.initial_messages.unwrap_or_default();
172
173        Ok(Self {
174            collection_id,
175            client,
176            session_id: generate_uuid(),
177            llm_config: config.llm_config,
178            messages: Arc::new(RwLock::new(messages)),
179            state: Arc::new(RwLock::new(Vec::new())),
180            last_interaction_params: Arc::new(RwLock::new(None)),
181            stream_config: StreamConfig::default(),
182        })
183    }
184
185    /// Create a new AI session stream with streaming configuration
186    pub async fn with_stream_config(
187        collection_id: String,
188        client: OramaClient,
189        config: CreateAiSessionConfig,
190        stream_config: StreamConfig,
191    ) -> Result<Self> {
192        let messages = config.initial_messages.unwrap_or_default();
193
194        Ok(Self {
195            collection_id,
196            client,
197            session_id: generate_uuid(),
198            llm_config: config.llm_config,
199            messages: Arc::new(RwLock::new(messages)),
200            state: Arc::new(RwLock::new(Vec::new())),
201            last_interaction_params: Arc::new(RwLock::new(None)),
202            stream_config,
203        })
204    }
205
206    /// Get a complete answer (non-streaming)
207    pub async fn answer(&self, data: AnswerConfig) -> Result<String> {
208        info!("Starting AI answer request");
209        let enriched_config = self.enrich_config(data).await;
210        debug!("Enriched config: {:?}", enriched_config);
211
212        // Store the interaction parameters
213        {
214            let mut last_params = self.last_interaction_params.write().await;
215            *last_params = Some(enriched_config.clone());
216        }
217
218        // Add user message
219        {
220            let mut messages = self.messages.write().await;
221            messages.push(Message {
222                role: Role::User,
223                content: enriched_config.query.clone(),
224            });
225            messages.push(Message {
226                role: Role::Assistant,
227                content: String::new(),
228            });
229        }
230
231        // Create interaction
232        let interaction_id = enriched_config
233            .interaction_id
234            .clone()
235            .unwrap_or_else(generate_uuid);
236
237        let interaction = Interaction::new(interaction_id.clone(), enriched_config.query.clone());
238
239        {
240            let mut state = self.state.write().await;
241            state.push(interaction);
242        }
243
244        // Make the actual API call
245        let request = ClientRequest::post(
246            format!("/v1/collections/{}/ai/answer", self.collection_id),
247            Target::Reader,
248            ApiKeyPosition::QueryParams,
249            enriched_config,
250        );
251
252        let response: serde_json::Value = self.client.request(request).await.map_err(|e| {
253            error!("API request failed: {}", e);
254            e
255        })?;
256
257        // Extract the answer from the response
258        let answer = response["answer"].as_str().unwrap_or_default().to_string();
259
260        // Update the interaction and message
261        {
262            let mut state = self.state.write().await;
263            if let Some(last_interaction) = state.last_mut() {
264                last_interaction.response = answer.clone();
265                last_interaction.loading = false;
266                last_interaction.current_step = Some("completed".to_string());
267
268                // Update with additional response data if available
269                if let Some(sources) = response.get("sources") {
270                    last_interaction.sources = Some(sources.clone());
271                }
272                if let Some(_related) = response.get("related") {
273                    last_interaction.related = response["related"].as_str().map(String::from);
274                }
275            }
276        }
277
278        {
279            let mut messages = self.messages.write().await;
280            if let Some(last_message) = messages.last_mut() {
281                last_message.content = answer.clone();
282            }
283        }
284
285        info!("AI answer completed successfully, length: {}", answer.len());
286        Ok(answer)
287    }
288
289    /// Create resilient SSE stream with retry logic
290    async fn create_resilient_stream(
291        &self,
292        client: OramaClient,
293        stream_url: String,
294        auth_ref: crate::auth::AuthRef,
295        enriched_config: AnswerConfig,
296        messages: Arc<RwLock<Vec<Message>>>,
297        state: Arc<RwLock<Vec<Interaction>>>,
298    ) -> Result<impl Stream<Item = Result<StreamChunk>> + Send> {
299        let stream_timeout = Duration::from_secs(self.stream_config.stream_timeout);
300        let start_time = std::time::Instant::now();
301
302        // Create request builder for EventSource
303        let request_builder = client
304            .inner()
305            .post(&stream_url)
306            .header("Accept", "text/event-stream")
307            .header("Cache-Control", "no-cache")
308            .header("Connection", "keep-alive")
309            .header("Authorization", format!("Bearer {}", auth_ref.bearer))
310            .timeout(Duration::from_secs(self.stream_config.connection_timeout))
311            .json(&enriched_config);
312
313        // Create EventSource
314        let event_source = EventSource::new(request_builder).map_err(|e| {
315            error!("Failed to create EventSource: {}", e);
316            OramaError::generic(format!("EventSource creation failed: {e}"))
317        })?;
318
319        info!("Successfully created EventSource for streaming");
320
321        // Convert EventSource to stream with comprehensive error handling
322        let event_stream = event_source.map(move |event_result| {
323            // Check for timeout
324            if start_time.elapsed() >= stream_timeout {
325                let timeout_secs = stream_timeout.as_secs();
326                error!("Stream timeout after {} seconds", timeout_secs);
327                let state_clone = state.clone();
328                let timeout_msg = format!("Stream timeout after {timeout_secs} seconds");
329                tokio::spawn(async move {
330                    Self::mark_interaction_error(state_clone, timeout_msg).await;
331                });
332                return Err(OramaError::generic(format!(
333                    "Stream timeout after {timeout_secs} seconds"
334                )));
335            }
336
337            match event_result {
338                Ok(event) => match event {
339                    Event::Open => {
340                        debug!("Stream connection opened");
341                        Ok(StreamChunk::ConnectionOpened)
342                    }
343                    Event::Message(message) => {
344                        debug!("Received streaming message: {}", message.data);
345
346                        match message.data.as_str() {
347                            "[DONE]" => {
348                                info!("Streaming completed successfully");
349                                let state_clone = state.clone();
350                                tokio::spawn(async move {
351                                    let mut state = state_clone.write().await;
352                                    if let Some(interaction) = state.last_mut() {
353                                        interaction.loading = false;
354                                        interaction.current_step = Some("completed".to_string());
355                                    }
356                                });
357                                Ok(StreamChunk::Done)
358                            }
359                            data => {
360                                Self::process_stream_data(data, messages.clone(), state.clone())
361                            }
362                        }
363                    }
364                },
365                Err(event_error) => {
366                    error!("Stream event error: {}", event_error);
367                    let state_clone = state.clone();
368                    let error_msg = event_error.to_string();
369                    tokio::spawn(async move {
370                        Self::mark_interaction_error(state_clone, error_msg).await;
371                    });
372                    Err(OramaError::generic(format!(
373                        "Stream event error: {event_error}"
374                    )))
375                }
376            }
377        });
378
379        Ok(event_stream)
380    }
381
382    /// Get streaming answer with server-sent events
383    pub async fn answer_stream(
384        &self,
385        data: AnswerConfig,
386    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
387        info!("Starting streaming AI answer request");
388        let enriched_config = self.enrich_config(data).await;
389        debug!("Enriched streaming config: {:?}", enriched_config);
390
391        // Store the interaction parameters
392        {
393            let mut last_params = self.last_interaction_params.write().await;
394            *last_params = Some(enriched_config.clone());
395        }
396
397        // Add user message
398        {
399            let mut messages = self.messages.write().await;
400            messages.push(Message {
401                role: Role::User,
402                content: enriched_config.query.clone(),
403            });
404            messages.push(Message {
405                role: Role::Assistant,
406                content: String::new(),
407            });
408        }
409
410        // Create interaction
411        let interaction_id = enriched_config
412            .interaction_id
413            .clone()
414            .unwrap_or_else(generate_uuid);
415
416        let interaction = Interaction::new(interaction_id.clone(), enriched_config.query.clone());
417
418        {
419            let mut state = self.state.write().await;
420            state.push(interaction);
421        }
422
423        let client = self.client.clone();
424        let collection_id = self.collection_id.clone();
425        let messages = self.messages.clone();
426        let state = self.state.clone();
427
428        // Get auth reference for the streaming request
429        let auth_ref = client.get_auth_ref(Target::Reader).await.map_err(|e| {
430            error!("Failed to get auth reference: {}", e);
431            e
432        })?;
433
434        let base_url = &auth_ref.base_url;
435        let stream_url = format!("{base_url}/v1/collections/{collection_id}/ai/answer/stream");
436
437        debug!("Creating streaming request to: {}", stream_url);
438
439        // Create SSE stream using reqwest-eventsource with retry
440        let stream = self
441            .create_resilient_stream(
442                client.clone(),
443                stream_url,
444                auth_ref,
445                enriched_config,
446                messages.clone(),
447                state.clone(),
448            )
449            .await?;
450
451        Ok(Box::pin(stream))
452    }
453
454    /// Regenerate the last response
455    pub async fn regenerate_last(&self, stream: bool) -> Result<String> {
456        info!("Starting regenerate_last, stream: {}", stream);
457
458        let state_len = {
459            let state = self.state.read().await;
460            state.len()
461        };
462
463        let messages_len = {
464            let messages = self.messages.read().await;
465            messages.len()
466        };
467
468        if state_len == 0 || messages_len == 0 {
469            warn!("No messages to regenerate");
470            return Err(OramaError::generic("No messages to regenerate"));
471        }
472
473        // Check if last message is from assistant
474        {
475            let messages = self.messages.read().await;
476            if let Some(last_message) = messages.last() {
477                if !matches!(last_message.role, Role::Assistant) {
478                    warn!("Last message is not from assistant");
479                    return Err(OramaError::generic(
480                        "Last message is not an assistant message",
481                    ));
482                }
483            }
484        }
485
486        // Get the last interaction parameters
487        let last_params = {
488            let params = self.last_interaction_params.read().await;
489            params.clone()
490        };
491
492        let last_params = last_params.ok_or_else(|| {
493            warn!("No last interaction parameters available");
494            OramaError::generic("No last interaction parameters available")
495        })?;
496
497        // Remove last assistant message and state
498        {
499            let mut messages = self.messages.write().await;
500            messages.pop();
501        }
502
503        {
504            let mut state = self.state.write().await;
505            state.pop();
506        }
507
508        // Regenerate based on stream preference
509        if stream {
510            info!("Regenerating with streaming");
511            let mut stream_result = self.answer_stream(last_params).await?;
512            let mut complete_response = String::new();
513
514            // Collect the stream
515            while let Some(chunk_result) = stream_result.next().await {
516                match chunk_result? {
517                    StreamChunk::Content(content) => {
518                        complete_response.push_str(&content);
519                    }
520                    StreamChunk::Done => {
521                        break;
522                    }
523                    StreamChunk::StatusUpdate(status) => {
524                        debug!("Status update during regeneration: {}", status);
525                    }
526                    _ => {
527                        // Ignore other chunk types for regeneration
528                    }
529                }
530            }
531
532            Ok(complete_response)
533        } else {
534            info!("Regenerating without streaming");
535            self.answer(last_params).await
536        }
537    }
538
539    /// Clear the session
540    pub async fn clear_session(&self) {
541        {
542            let mut messages = self.messages.write().await;
543            messages.clear();
544        }
545
546        {
547            let mut state = self.state.write().await;
548            state.clear();
549        }
550    }
551
552    /// Get current messages
553    pub async fn get_messages(&self) -> Vec<Message> {
554        let messages = self.messages.read().await;
555        messages.clone()
556    }
557
558    /// Get current state
559    pub async fn get_state(&self) -> Vec<Interaction> {
560        let state = self.state.read().await;
561        state.clone()
562    }
563
564    /// Get session ID
565    pub fn session_id(&self) -> &str {
566        &self.session_id
567    }
568
569    /// Get current stream configuration
570    pub fn get_stream_config(&self) -> &StreamConfig {
571        &self.stream_config
572    }
573
574    /// Update stream configuration
575    pub fn set_stream_config(&mut self, config: StreamConfig) {
576        self.stream_config = config;
577    }
578
579    /// Enrich config with default values
580    async fn enrich_config(&self, mut config: AnswerConfig) -> AnswerConfig {
581        if config.visitor_id.is_none() {
582            config.visitor_id = Some(DEFAULT_SERVER_USER_ID.to_string());
583        }
584
585        if config.interaction_id.is_none() {
586            config.interaction_id = Some(generate_uuid());
587        }
588
589        if config.session_id.is_none() {
590            config.session_id = Some(self.session_id.clone());
591        }
592
593        // Use session's LLM config if none is provided in the request
594        if config.llm_config.is_none() {
595            config.llm_config = self.llm_config.clone();
596        }
597
598        config
599    }
600
601    /// Process streaming data chunk with robust JSON parsing
602    fn process_stream_data(
603        data: &str,
604        messages: Arc<RwLock<Vec<Message>>>,
605        state: Arc<RwLock<Vec<Interaction>>>,
606    ) -> Result<StreamChunk> {
607        // Use robust AI response parsing with automatic JSON fixing
608        match parse_ai_response::<serde_json::Value>(data) {
609            Ok(parsed) => {
610                if let Some(content) = parsed.get("content").and_then(|c| c.as_str()) {
611                    // Content chunk - update message and interaction
612                    let content = content.to_string();
613                    let content_for_update = content.clone();
614                    let parsed_clone = parsed.clone();
615
616                    tokio::spawn(async move {
617                        // Update assistant message
618                        {
619                            let mut messages = messages.write().await;
620                            if let Some(last_message) = messages.last_mut() {
621                                if matches!(last_message.role, Role::Assistant) {
622                                    last_message.content.push_str(&content_for_update);
623                                }
624                            }
625                        }
626
627                        // Update interaction state
628                        {
629                            let mut state = state.write().await;
630                            if let Some(last_interaction) = state.last_mut() {
631                                last_interaction.response.push_str(&content_for_update);
632
633                                // Update step if provided
634                                if let Some(step) =
635                                    parsed_clone.get("step").and_then(|s| s.as_str())
636                                {
637                                    last_interaction.current_step = Some(step.to_string());
638                                }
639
640                                // Update verbose step if provided
641                                if let Some(verbose) =
642                                    parsed_clone.get("verbose_step").and_then(|s| s.as_str())
643                                {
644                                    last_interaction.current_step_verbose =
645                                        Some(verbose.to_string());
646                                }
647                            }
648                        }
649                    });
650
651                    Ok(StreamChunk::Content(content))
652                } else if let Some(step) = parsed.get("step").and_then(|s| s.as_str()) {
653                    // Status update
654                    let step = step.to_string();
655                    let step_for_update = step.clone();
656
657                    tokio::spawn(async move {
658                        let mut state = state.write().await;
659                        if let Some(last_interaction) = state.last_mut() {
660                            last_interaction.current_step = Some(step_for_update);
661                        }
662                    });
663
664                    Ok(StreamChunk::StatusUpdate(step))
665                } else if let Some(error_msg) = parsed.get("error").and_then(|e| e.as_str()) {
666                    // Error in stream
667                    warn!("Stream error received: {}", error_msg);
668
669                    let state_clone = state.clone();
670                    let error_message = error_msg.to_string();
671                    tokio::spawn(async move {
672                        Self::mark_interaction_error(state_clone, error_message).await;
673                    });
674                    Err(OramaError::generic(error_msg))
675                } else {
676                    // Unknown structured data
677                    debug!("Unknown structured stream data: {}", data);
678                    Ok(StreamChunk::RawData(data.to_string()))
679                }
680            }
681            Err(parse_err) => {
682                // Parsing failed even with JSON fixing - treat as raw data
683                debug!(
684                    "Failed to parse AI response as JSON ({}): {}",
685                    parse_err, data
686                );
687                Ok(StreamChunk::RawData(data.to_string()))
688            }
689        }
690    }
691
692    /// Mark interaction as errored (async version)
693    async fn mark_interaction_error(state: Arc<RwLock<Vec<Interaction>>>, error_message: String) {
694        let mut state = state.write().await;
695        if let Some(interaction) = state.last_mut() {
696            interaction.error = true;
697            interaction.error_message = Some(error_message);
698            interaction.loading = false;
699        }
700    }
701}
702
703// Builder implementations
704impl AnswerConfig {
705    /// Create a new AnswerConfig
706    pub fn new<S: Into<String>>(query: S) -> Self {
707        Self {
708            query: query.into(),
709            interaction_id: None,
710            visitor_id: None,
711            session_id: None,
712            messages: None,
713            related: None,
714            datasource_ids: None,
715            min_similarity: None,
716            max_documents: None,
717            ragat_notation: None,
718            llm_config: None,
719        }
720    }
721
722    /// Set interaction ID
723    pub fn with_interaction_id<S: Into<String>>(mut self, id: S) -> Self {
724        self.interaction_id = Some(id.into());
725        self
726    }
727
728    /// Set visitor ID
729    pub fn with_visitor_id<S: Into<String>>(mut self, id: S) -> Self {
730        self.visitor_id = Some(id.into());
731        self
732    }
733
734    /// Set session ID
735    pub fn with_session_id<S: Into<String>>(mut self, id: S) -> Self {
736        self.session_id = Some(id.into());
737        self
738    }
739
740    /// Set messages
741    pub fn with_messages(mut self, messages: Vec<Message>) -> Self {
742        self.messages = Some(messages);
743        self
744    }
745
746    /// Set related questions config
747    pub fn with_related(mut self, related: RelatedQuestionsConfig) -> Self {
748        self.related = Some(related);
749        self
750    }
751
752    /// Set datasource IDs
753    pub fn with_datasource_ids(mut self, ids: Vec<String>) -> Self {
754        self.datasource_ids = Some(ids);
755        self
756    }
757
758    /// Set minimum similarity
759    pub fn with_min_similarity(mut self, similarity: f64) -> Self {
760        self.min_similarity = Some(similarity);
761        self
762    }
763
764    /// Set maximum documents
765    pub fn with_max_documents(mut self, max_docs: u32) -> Self {
766        self.max_documents = Some(max_docs);
767        self
768    }
769
770    /// Set RAGAT notation
771    pub fn with_ragat_notation<S: Into<String>>(mut self, notation: S) -> Self {
772        self.ragat_notation = Some(notation.into());
773        self
774    }
775
776    /// Set LLM configuration
777    pub fn with_llm_config(mut self, config: LlmConfig) -> Self {
778        self.llm_config = Some(config);
779        self
780    }
781}
782
783impl CreateAiSessionConfig {
784    /// Create a new CreateAiSessionConfig
785    pub fn new() -> Self {
786        Self {
787            llm_config: None,
788            initial_messages: None,
789        }
790    }
791
792    /// Set LLM configuration
793    pub fn with_llm_config(mut self, config: LlmConfig) -> Self {
794        self.llm_config = Some(config);
795        self
796    }
797
798    /// Set initial messages
799    pub fn with_initial_messages(mut self, messages: Vec<Message>) -> Self {
800        self.initial_messages = Some(messages);
801        self
802    }
803}
804
805impl Default for CreateAiSessionConfig {
806    fn default() -> Self {
807        Self::new()
808    }
809}