1use 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#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum PasteState {
18 Normal,
20 Pasting,
22}
23
24pub struct PasteHandler {
26 pub state: PasteState,
28 pub buffer: Vec<u8>,
30 start_sequence: Vec<u8>,
32 end_sequence: Vec<u8>,
34}
35
36impl PasteHandler {
37 pub fn new() -> Self {
39 Self {
40 state: PasteState::Normal,
41 buffer: Vec::new(),
42 start_sequence: vec![0x1B, 0x5B, 0x32, 0x30, 0x30, 0x7E], end_sequence: vec![0x1B, 0x5B, 0x32, 0x30, 0x31, 0x7E], }
45 }
46
47 pub fn reset(&mut self) {
49 self.state = PasteState::Normal;
50 self.buffer.clear();
51 }
52
53 pub fn state(&self) -> PasteState {
55 self.state.clone()
56 }
57
58 pub fn buffer(&self) -> &[u8] {
60 &self.buffer
61 }
62
63 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 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 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 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 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 if buffer.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
165 return Some(buffer.to_vec());
166 }
167
168 if buffer.starts_with(&[0xFF, 0xD8, 0xFF]) {
170 return Some(buffer.to_vec());
171 }
172
173 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#[derive(Debug, Clone)]
194pub struct RpcClientConfig {
195 pub binary_path: String,
197 pub cwd: Option<String>,
199 pub env: Vec<(String, String)>,
201 pub provider: Option<String>,
203 pub model: Option<String>,
205 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
222pub type BoxedEventListener = Box<dyn Fn(RpcEvent) + Send>;
224
225pub 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 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 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 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 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 pub fn stderr(&self) -> &str {
309 &self.stderr_buffer
310 }
311
312 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 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 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 pub fn abort(&mut self) -> Result<()> {
338 Self::require_success(self.send_and_wait(serde_json::json!({ "type": "abort" }))?)
339 }
340
341 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 pub fn get_state(&mut self) -> Result<RpcResponse> {
352 self.send_and_wait(serde_json::json!({ "type": "get_state" }))
353 }
354
355 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 pub fn cycle_model(&mut self) -> Result<RpcResponse> {
366 self.send_and_wait(serde_json::json!({ "type": "cycle_model" }))
367 }
368
369 pub fn get_available_models(&mut self) -> Result<RpcResponse> {
371 self.send_and_wait(serde_json::json!({ "type": "get_available_models" }))
372 }
373
374 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 pub fn cycle_thinking_level(&mut self) -> Result<RpcResponse> {
384 self.send_and_wait(serde_json::json!({ "type": "cycle_thinking_level" }))
385 }
386
387 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 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 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 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 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 pub fn abort_retry(&mut self) -> Result<()> {
430 Self::require_success(self.send_and_wait(serde_json::json!({ "type": "abort_retry" }))?)
431 }
432
433 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 pub fn abort_bash(&mut self) -> Result<()> {
443 Self::require_success(self.send_and_wait(serde_json::json!({ "type": "abort_bash" }))?)
444 }
445
446 pub fn get_session_stats(&mut self) -> Result<RpcResponse> {
448 self.send_and_wait(serde_json::json!({ "type": "get_session_stats" }))
449 }
450
451 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 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 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 pub fn clone_session(&mut self) -> Result<RpcResponse> {
478 self.send_and_wait(serde_json::json!({ "type": "clone" }))
479 }
480
481 pub fn get_fork_messages(&mut self) -> Result<RpcResponse> {
483 self.send_and_wait(serde_json::json!({ "type": "get_fork_messages" }))
484 }
485
486 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 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 pub fn get_messages(&mut self) -> Result<RpcResponse> {
501 self.send_and_wait(serde_json::json!({ "type": "get_messages" }))
502 }
503
504 pub fn get_commands(&mut self) -> Result<RpcResponse> {
506 self.send_and_wait(serde_json::json!({ "type": "get_commands" }))
507 }
508
509 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 fn next_request_id(&mut self) -> String {
526 self.request_counter += 1;
527 format!("req_{}", self.request_counter)
528 }
529
530 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 {
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 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 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 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 fn handle_incoming_value(&mut self, expected_id: &str, value: Value) -> Result<RpcResponse> {
659 if let Some(obj) = value.as_object() {
660 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 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 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}