Skip to main content

oxi/rpc_mode/
utils.rs

1//! Utility types: paste handling and RPC client.
2
3use anyhow::{Context, Result};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::io::{BufRead, Write};
7use tokio::sync::oneshot;
8
9use super::protocol::*;
10
11// ============================================================================
12// Bracketed Paste Detection
13// ============================================================================
14
15/// Bracketed paste state
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum PasteState {
18    /// Normal mode
19    Normal,
20    /// Bracketed paste mode - collecting paste data
21    Pasting,
22}
23
24/// Bracketed paste handler
25pub struct PasteHandler {
26    /// Current paste state.
27    pub state: PasteState,
28    /// Buffer for collecting paste data.
29    pub buffer: Vec<u8>,
30    /// Bracketed paste start sequence: ESC [ 2 0 0 ~
31    start_sequence: Vec<u8>,
32    /// Bracketed paste end sequence: ESC [ 2 0 1 ~
33    end_sequence: Vec<u8>,
34}
35
36impl PasteHandler {
37    /// Create a new paste handler
38    pub fn new() -> Self {
39        Self {
40            state: PasteState::Normal,
41            buffer: Vec::new(),
42            start_sequence: vec![0x1B, 0x5B, 0x32, 0x30, 0x30, 0x7E], // \x1b[200~
43            end_sequence: vec![0x1B, 0x5B, 0x32, 0x30, 0x31, 0x7E],   // \x1b[201~
44        }
45    }
46
47    /// Reset to normal mode
48    pub fn reset(&mut self) {
49        self.state = PasteState::Normal;
50        self.buffer.clear();
51    }
52
53    /// Check current state
54    pub fn state(&self) -> PasteState {
55        self.state.clone()
56    }
57
58    /// Get collected paste buffer
59    pub fn buffer(&self) -> &[u8] {
60        &self.buffer
61    }
62
63    /// Process a byte, returning Some(byte) if it should be passed through
64    /// or None if it was part of a paste sequence
65    pub fn process_byte(&mut self, byte: u8) -> Option<u8> {
66        match self.state {
67            PasteState::Normal => {
68                if (self.buffer.is_empty() && byte == 0x1B)
69                    || (!self.buffer.is_empty() && self.buffer[0] == 0x1B && byte == 0x5B)
70                    || (self.buffer.len() >= 2
71                        && self.buffer[0] == 0x1B
72                        && self.buffer[1] == 0x5B
73                        && byte == 0x32)
74                    || (self.buffer.len() >= 3
75                        && self.buffer[0] == 0x1B
76                        && self.buffer[1] == 0x5B
77                        && self.buffer[2] == 0x32
78                        && byte == 0x30)
79                    || (self.buffer.len() >= 4
80                        && self.buffer[0] == 0x1B
81                        && self.buffer[1] == 0x5B
82                        && self.buffer[2] == 0x32
83                        && self.buffer[3] == 0x30
84                        && byte == 0x30)
85                {
86                    self.buffer.push(byte);
87                    None
88                } else if self.buffer.len() >= 5
89                    && self.buffer[0] == 0x1B
90                    && self.buffer[1] == 0x5B
91                    && self.buffer[2] == 0x32
92                    && self.buffer[3] == 0x30
93                    && self.buffer[4] == 0x30
94                    && byte == 0x7E
95                {
96                    // Paste start detected
97                    self.buffer.clear();
98                    self.state = PasteState::Pasting;
99                    None
100                } else {
101                    let first_byte = self.buffer.first().copied();
102                    self.buffer.clear();
103                    first_byte
104                }
105            }
106            PasteState::Pasting => {
107                if (self.buffer.is_empty() && byte == 0x1B)
108                    || (!self.buffer.is_empty() && self.buffer[0] == 0x1B && byte == 0x5B)
109                    || (self.buffer.len() >= 2
110                        && self.buffer[0] == 0x1B
111                        && self.buffer[1] == 0x5B
112                        && byte == 0x32)
113                    || (self.buffer.len() >= 3
114                        && self.buffer[0] == 0x1B
115                        && self.buffer[1] == 0x5B
116                        && self.buffer[2] == 0x32
117                        && byte == 0x30)
118                    || (self.buffer.len() >= 4
119                        && self.buffer[0] == 0x1B
120                        && self.buffer[1] == 0x5B
121                        && self.buffer[2] == 0x32
122                        && self.buffer[3] == 0x30
123                        && byte == 0x31)
124                {
125                    self.buffer.push(byte);
126                    None
127                } else if self.buffer.len() >= 5
128                    && self.buffer[0] == 0x1B
129                    && self.buffer[1] == 0x5B
130                    && self.buffer[2] == 0x32
131                    && self.buffer[3] == 0x30
132                    && self.buffer[4] == 0x31
133                    && byte == 0x7E
134                {
135                    // Paste end detected
136                    self.buffer.clear();
137                    self.state = PasteState::Normal;
138                    None
139                } else {
140                    self.buffer.push(byte);
141                    None
142                }
143            }
144        }
145    }
146
147    /// Check if buffer ends with a sequence
148    pub fn ends_with(&self, sequence: &[u8]) -> bool {
149        if self.buffer.len() < sequence.len() {
150            return false;
151        }
152        let end_pos = self.buffer.len() - sequence.len();
153        &self.buffer[end_pos..] == sequence
154    }
155
156    /// Extract image data from clipboard paste
157    pub fn extract_image_data(&self) -> Option<Vec<u8>> {
158        let buffer = self.buffer();
159        if buffer.len() < 8 {
160            return None;
161        }
162
163        // PNG magic bytes
164        if buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
165            return Some(buffer.to_vec());
166        }
167
168        // JPEG magic bytes
169        if buffer.starts_with(&[0xFF, 0xD8, 0xFF]) {
170            return Some(buffer.to_vec());
171        }
172
173        // Binary data heuristic
174        if buffer.iter().take(100).filter(|&&b| b == 0).count() > 5 {
175            return Some(buffer.to_vec());
176        }
177
178        None
179    }
180}
181
182impl Default for PasteHandler {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188// ============================================================================
189// RPC Client (for programmatic access to an oxi RPC server)
190// ============================================================================
191
192/// Configuration for the RPC client.
193#[derive(Debug, Clone)]
194pub struct RpcClientConfig {
195    /// Path to the oxi binary
196    pub binary_path: String,
197    /// Working directory for the agent
198    pub cwd: Option<String>,
199    /// Environment variables
200    pub env: Vec<(String, String)>,
201    /// Provider to use
202    pub provider: Option<String>,
203    /// Model ID to use
204    pub model: Option<String>,
205    /// Additional CLI arguments
206    pub args: Vec<String>,
207}
208
209impl Default for RpcClientConfig {
210    fn default() -> Self {
211        Self {
212            binary_path: "oxi".to_string(),
213            cwd: None,
214            env: Vec::new(),
215            provider: None,
216            model: None,
217            args: Vec::new(),
218        }
219    }
220}
221
222/// Event listener callback type
223pub type BoxedEventListener = Box<dyn Fn(RpcEvent) + Send>;
224
225/// RPC Client for programmatic access to an oxi agent.
226///
227/// Spawns the agent in RPC mode and provides a typed API for all operations.
228pub struct RpcClient {
229    config: RpcClientConfig,
230    child: Option<std::process::Child>,
231    line_reader: JsonlLineReader,
232    pending_requests: HashMap<String, oneshot::Sender<RpcResponse>>,
233    request_counter: u64,
234    event_listeners: Vec<BoxedEventListener>,
235    stderr_buffer: String,
236}
237
238impl RpcClient {
239    /// Create a new RPC client with the given configuration.
240    pub fn new(config: RpcClientConfig) -> Self {
241        Self {
242            config,
243            child: None,
244            line_reader: JsonlLineReader::new(),
245            pending_requests: HashMap::new(),
246            request_counter: 0,
247            event_listeners: Vec::new(),
248            stderr_buffer: String::new(),
249        }
250    }
251
252    /// Start the RPC agent process.
253    pub fn start(&mut self) -> Result<()> {
254        if self.child.is_some() {
255            anyhow::bail!("Client already started");
256        }
257
258        let mut args = vec!["--mode".to_string(), "rpc".to_string()];
259        if let Some(ref provider) = self.config.provider {
260            args.push("--provider".to_string());
261            args.push(provider.clone());
262        }
263        if let Some(ref model) = self.config.model {
264            args.push("--model".to_string());
265            args.push(model.clone());
266        }
267        args.extend(self.config.args.iter().cloned());
268
269        let mut cmd = std::process::Command::new(&self.config.binary_path);
270        cmd.args(&args)
271            .stdin(std::process::Stdio::piped())
272            .stdout(std::process::Stdio::piped())
273            .stderr(std::process::Stdio::piped());
274
275        if let Some(ref cwd) = self.config.cwd {
276            cmd.current_dir(cwd);
277        }
278
279        for (key, value) in &self.config.env {
280            cmd.env(key, value);
281        }
282
283        let child = cmd.spawn().context("Failed to spawn oxi RPC process")?;
284        self.child = Some(child);
285
286        Ok(())
287    }
288
289    /// Stop the RPC agent process.
290    pub fn stop(&mut self) -> Result<()> {
291        if let Some(mut child) = self.child.take() {
292            let _ = child.kill();
293            let _ = child.wait();
294        }
295        self.pending_requests.clear();
296        Ok(())
297    }
298
299    /// Subscribe to agent events.
300    pub fn on_event<F>(&mut self, listener: F)
301    where
302        F: Fn(RpcEvent) + Send + 'static,
303    {
304        self.event_listeners.push(Box::new(listener));
305    }
306
307    /// Get collected stderr output.
308    pub fn stderr(&self) -> &str {
309        &self.stderr_buffer
310    }
311
312    /// Send a prompt to the agent.
313    pub fn prompt(&mut self, message: &str) -> Result<()> {
314        Self::require_success(self.send_and_wait(serde_json::json!({
315            "type": "prompt",
316            "message": message
317        }))?)
318    }
319
320    /// Send a steer message.
321    pub fn steer(&mut self, message: &str) -> Result<()> {
322        Self::require_success(self.send_and_wait(serde_json::json!({
323            "type": "steer",
324            "message": message
325        }))?)
326    }
327
328    /// Send a follow-up message.
329    pub fn follow_up(&mut self, message: &str) -> Result<()> {
330        Self::require_success(self.send_and_wait(serde_json::json!({
331            "type": "follow_up",
332            "message": message
333        }))?)
334    }
335
336    /// Abort current operation.
337    pub fn abort(&mut self) -> Result<()> {
338        Self::require_success(self.send_and_wait(serde_json::json!({ "type": "abort" }))?)
339    }
340
341    /// Start a new session.
342    pub fn new_session(&mut self, parent_session: Option<&str>) -> Result<RpcResponse> {
343        let mut cmd = serde_json::json!({ "type": "new_session" });
344        if let Some(parent) = parent_session {
345            cmd["parent_session"] = Value::String(parent.to_string());
346        }
347        self.send_and_wait(cmd)
348    }
349
350    /// Get current session state.
351    pub fn get_state(&mut self) -> Result<RpcResponse> {
352        self.send_and_wait(serde_json::json!({ "type": "get_state" }))
353    }
354
355    /// Set model by provider and ID.
356    pub fn set_model(&mut self, provider: &str, model_id: &str) -> Result<RpcResponse> {
357        self.send_and_wait(serde_json::json!({
358            "type": "set_model",
359            "provider": provider,
360            "model_id": model_id
361        }))
362    }
363
364    /// Cycle to next model.
365    pub fn cycle_model(&mut self) -> Result<RpcResponse> {
366        self.send_and_wait(serde_json::json!({ "type": "cycle_model" }))
367    }
368
369    /// Get available models.
370    pub fn get_available_models(&mut self) -> Result<RpcResponse> {
371        self.send_and_wait(serde_json::json!({ "type": "get_available_models" }))
372    }
373
374    /// Set thinking level.
375    pub fn set_thinking_level(&mut self, level: &str) -> Result<()> {
376        Self::require_success(self.send_and_wait(serde_json::json!({
377            "type": "set_thinking_level",
378            "level": level
379        }))?)
380    }
381
382    /// Cycle thinking level.
383    pub fn cycle_thinking_level(&mut self) -> Result<RpcResponse> {
384        self.send_and_wait(serde_json::json!({ "type": "cycle_thinking_level" }))
385    }
386
387    /// Set steering mode.
388    pub fn set_steering_mode(&mut self, mode: &str) -> Result<()> {
389        Self::require_success(self.send_and_wait(serde_json::json!({
390            "type": "set_steering_mode",
391            "mode": mode
392        }))?)
393    }
394
395    /// Set follow-up mode.
396    pub fn set_follow_up_mode(&mut self, mode: &str) -> Result<()> {
397        Self::require_success(self.send_and_wait(serde_json::json!({
398            "type": "set_follow_up_mode",
399            "mode": mode
400        }))?)
401    }
402
403    /// Compact session context.
404    pub fn compact(&mut self, custom_instructions: Option<&str>) -> Result<RpcResponse> {
405        let mut cmd = serde_json::json!({ "type": "compact" });
406        if let Some(instructions) = custom_instructions {
407            cmd["custom_instructions"] = Value::String(instructions.to_string());
408        }
409        self.send_and_wait(cmd)
410    }
411
412    /// Set auto-compaction.
413    pub fn set_auto_compaction(&mut self, enabled: bool) -> Result<()> {
414        Self::require_success(self.send_and_wait(serde_json::json!({
415            "type": "set_auto_compaction",
416            "enabled": enabled
417        }))?)
418    }
419
420    /// Set auto-retry.
421    pub fn set_auto_retry(&mut self, enabled: bool) -> Result<()> {
422        Self::require_success(self.send_and_wait(serde_json::json!({
423            "type": "set_auto_retry",
424            "enabled": enabled
425        }))?)
426    }
427
428    /// Abort retry.
429    pub fn abort_retry(&mut self) -> Result<()> {
430        Self::require_success(self.send_and_wait(serde_json::json!({ "type": "abort_retry" }))?)
431    }
432
433    /// Execute a bash command.
434    pub fn bash(&mut self, command: &str) -> Result<RpcResponse> {
435        self.send_and_wait(serde_json::json!({
436            "type": "bash",
437            "command": command
438        }))
439    }
440
441    /// Abort bash.
442    pub fn abort_bash(&mut self) -> Result<()> {
443        Self::require_success(self.send_and_wait(serde_json::json!({ "type": "abort_bash" }))?)
444    }
445
446    /// Get session statistics.
447    pub fn get_session_stats(&mut self) -> Result<RpcResponse> {
448        self.send_and_wait(serde_json::json!({ "type": "get_session_stats" }))
449    }
450
451    /// Export session to HTML.
452    pub fn export_html(&mut self, output_path: Option<&str>) -> Result<RpcResponse> {
453        let mut cmd = serde_json::json!({ "type": "export_html" });
454        if let Some(path) = output_path {
455            cmd["output_path"] = Value::String(path.to_string());
456        }
457        self.send_and_wait(cmd)
458    }
459
460    /// Switch to a different session.
461    pub fn switch_session(&mut self, session_path: &str) -> Result<RpcResponse> {
462        self.send_and_wait(serde_json::json!({
463            "type": "switch_session",
464            "session_path": session_path
465        }))
466    }
467
468    /// Fork from a specific message.
469    pub fn fork(&mut self, entry_id: &str) -> Result<RpcResponse> {
470        self.send_and_wait(serde_json::json!({
471            "type": "fork",
472            "entry_id": entry_id
473        }))
474    }
475
476    /// Clone the current branch.
477    pub fn clone_session(&mut self) -> Result<RpcResponse> {
478        self.send_and_wait(serde_json::json!({ "type": "clone" }))
479    }
480
481    /// Get messages available for forking.
482    pub fn get_fork_messages(&mut self) -> Result<RpcResponse> {
483        self.send_and_wait(serde_json::json!({ "type": "get_fork_messages" }))
484    }
485
486    /// Get text of last assistant message.
487    pub fn get_last_assistant_text(&mut self) -> Result<RpcResponse> {
488        self.send_and_wait(serde_json::json!({ "type": "get_last_assistant_text" }))
489    }
490
491    /// Set the session display name.
492    pub fn set_session_name(&mut self, name: &str) -> Result<()> {
493        Self::require_success(self.send_and_wait(serde_json::json!({
494            "type": "set_session_name",
495            "name": name
496        }))?)
497    }
498
499    /// Get all messages.
500    pub fn get_messages(&mut self) -> Result<RpcResponse> {
501        self.send_and_wait(serde_json::json!({ "type": "get_messages" }))
502    }
503
504    /// Get available commands.
505    pub fn get_commands(&mut self) -> Result<RpcResponse> {
506        self.send_and_wait(serde_json::json!({ "type": "get_commands" }))
507    }
508
509    // ── Internal helpers ─────────────────────────────────────────
510
511    fn require_success(response: RpcResponse) -> Result<()> {
512        match response {
513            RpcResponse::Response { success: true, .. } => Ok(()),
514            RpcResponse::Response { error, command, .. } => anyhow::bail!(
515                "RPC command {command} failed: {}",
516                error.unwrap_or_else(|| "unknown error".to_string())
517            ),
518            RpcResponse::ExtensionUiRequest(_) => {
519                anyhow::bail!("unexpected extension UI request response")
520            }
521        }
522    }
523
524    /// Generate the next request ID.
525    fn next_request_id(&mut self) -> String {
526        self.request_counter += 1;
527        format!("req_{}", self.request_counter)
528    }
529
530    /// Send a command and wait for the response.
531    fn send_and_wait(&mut self, mut command: Value) -> Result<RpcResponse> {
532        let id = self.next_request_id();
533        if let Some(obj) = command.as_object_mut() {
534            obj.insert("id".to_string(), Value::String(id.clone()));
535        }
536
537        let line = serialize_json_line(&command);
538
539        // Write command
540        {
541            let child = self.child.as_mut().context("Client not started")?;
542            if let Some(ref mut stdin) = child.stdin {
543                stdin
544                    .write_all(line.as_bytes())
545                    .context("Failed to write to stdin")?;
546                stdin.flush().context("Failed to flush stdin")?;
547            }
548        }
549
550        // Read responses until we find ours
551        let child = self.child.as_mut().context("Client not started")?;
552        if let Some(ref mut stdout) = child.stdout {
553            let mut buf_reader = std::io::BufReader::new(std::io::BufReader::new(stdout));
554            let mut buf = String::new();
555            loop {
556                buf.clear();
557                match buf_reader.read_line(&mut buf) {
558                    Ok(0) => anyhow::bail!("EOF while waiting for response"),
559                    Ok(_) => {
560                        let trimmed = buf.trim();
561                        if trimmed.is_empty() {
562                            continue;
563                        }
564                        match parse_json_line(trimmed) {
565                            Ok(value) => {
566                                if let Some(obj) = value.as_object() {
567                                    // Check if it's our response
568                                    if obj.get("type").and_then(|v| v.as_str()) == Some("response")
569                                        && obj.get("id").and_then(|v| v.as_str())
570                                            == Some(id.as_str())
571                                    {
572                                        let success = obj
573                                            .get("success")
574                                            .and_then(|v| v.as_bool())
575                                            .unwrap_or(false);
576                                        let cmd_name = obj
577                                            .get("command")
578                                            .and_then(|v| v.as_str())
579                                            .unwrap_or("")
580                                            .to_string();
581                                        let data = obj.get("data").cloned();
582                                        let error = obj
583                                            .get("error")
584                                            .and_then(|v| v.as_str())
585                                            .map(|s| s.to_string());
586                                        return Ok(RpcResponse::Response {
587                                            id: Some(id.clone()),
588                                            command: cmd_name,
589                                            success,
590                                            data,
591                                            error,
592                                        });
593                                    }
594
595                                    // Otherwise it's an event — parse and notify listeners
596                                    let event_type = obj
597                                        .get("type")
598                                        .and_then(|v: &Value| v.as_str())
599                                        .unwrap_or("");
600                                    let event = match event_type {
601                                        "agent_start" => Some(RpcEvent::AgentStart),
602                                        "agent_end" => Some(RpcEvent::AgentEnd),
603                                        "thinking" => Some(RpcEvent::Thinking),
604                                        "error" => {
605                                            let msg = obj
606                                                .get("message")
607                                                .and_then(|v: &Value| v.as_str())
608                                                .unwrap_or("")
609                                                .to_string();
610                                            Some(RpcEvent::Error { message: msg })
611                                        }
612                                        "text_chunk" => {
613                                            let text = obj
614                                                .get("text")
615                                                .and_then(|v: &Value| v.as_str())
616                                                .unwrap_or("")
617                                                .to_string();
618                                            Some(RpcEvent::TextChunk { text })
619                                        }
620                                        "tool_start" => {
621                                            let tool = obj
622                                                .get("tool")
623                                                .and_then(|v: &Value| v.as_str())
624                                                .unwrap_or("")
625                                                .to_string();
626                                            Some(RpcEvent::ToolStart { tool })
627                                        }
628                                        "tool_end" => {
629                                            let tool = obj
630                                                .get("tool")
631                                                .and_then(|v: &Value| v.as_str())
632                                                .unwrap_or("")
633                                                .to_string();
634                                            Some(RpcEvent::ToolEnd { tool })
635                                        }
636                                        _ => None,
637                                    };
638                                    if let Some(event) = event {
639                                        for listener in &self.event_listeners {
640                                            listener(event.clone());
641                                        }
642                                    }
643                                }
644                            }
645                            Err(_) => continue,
646                        }
647                    }
648                    Err(e) => anyhow::bail!("Error reading stdout: {}", e),
649                }
650            }
651        } else {
652            anyhow::bail!("No stdout available");
653        }
654    }
655
656    /// Handle an incoming JSON value during send_and_wait.
657    /// Returns Ok(response) if it matches our request ID, otherwise processes as event.
658    fn handle_incoming_value(&mut self, expected_id: &str, value: Value) -> Result<RpcResponse> {
659        if let Some(obj) = value.as_object() {
660            // Check if it's our response
661            if obj.get("type").and_then(|v| v.as_str()) == Some("response")
662                && obj.get("id").and_then(|v| v.as_str()) == Some(expected_id)
663            {
664                let success = obj
665                    .get("success")
666                    .and_then(|v| v.as_bool())
667                    .unwrap_or(false);
668                let command = obj
669                    .get("command")
670                    .and_then(|v| v.as_str())
671                    .unwrap_or("")
672                    .to_string();
673                let data = obj.get("data").cloned();
674                let error = obj
675                    .get("error")
676                    .and_then(|v| v.as_str())
677                    .map(|s| s.to_string());
678                return Ok(RpcResponse::Response {
679                    id: Some(expected_id.to_string()),
680                    command,
681                    success,
682                    data,
683                    error,
684                });
685            }
686
687            // Otherwise it's an event — try to parse and notify listeners
688            let event_type = obj
689                .get("type")
690                .and_then(|v: &Value| v.as_str())
691                .unwrap_or("");
692            let event = match event_type {
693                "agent_start" => Some(RpcEvent::AgentStart),
694                "agent_end" => Some(RpcEvent::AgentEnd),
695                "thinking" => Some(RpcEvent::Thinking),
696                "error" => {
697                    let msg = obj
698                        .get("message")
699                        .and_then(|v: &Value| v.as_str())
700                        .unwrap_or("")
701                        .to_string();
702                    Some(RpcEvent::Error { message: msg })
703                }
704                "text_chunk" => {
705                    let text = obj
706                        .get("text")
707                        .and_then(|v: &Value| v.as_str())
708                        .unwrap_or("")
709                        .to_string();
710                    Some(RpcEvent::TextChunk { text })
711                }
712                "tool_start" => {
713                    let tool = obj
714                        .get("tool")
715                        .and_then(|v: &Value| v.as_str())
716                        .unwrap_or("")
717                        .to_string();
718                    Some(RpcEvent::ToolStart { tool })
719                }
720                "tool_end" => {
721                    let tool = obj
722                        .get("tool")
723                        .and_then(|v: &Value| v.as_str())
724                        .unwrap_or("")
725                        .to_string();
726                    Some(RpcEvent::ToolEnd { tool })
727                }
728                _ => None,
729            };
730            if let Some(event) = event {
731                for listener in &self.event_listeners {
732                    listener(event.clone());
733                }
734            }
735        }
736
737        // Not our response — loop continues (caller will read next line)
738        // This is a hack: we return an error to signal "continue reading"
739        // Better approach: use a loop inside send_and_wait
740        anyhow::bail!("Internal: not our response, caller should retry")
741    }
742}
743
744impl Drop for RpcClient {
745    fn drop(&mut self) {
746        let _ = self.stop();
747    }
748}