Skip to main content

opencode/
lib.rs

1#![forbid(unsafe_code)]
2//! Async helper around the OpenCode CLI (`opencode`) focused on the canonical
3//! `opencode run --format json` surface only.
4
5use std::{
6    future::Future,
7    pin::Pin,
8    sync::{
9        atomic::{AtomicBool, Ordering},
10        Arc,
11    },
12};
13
14use futures_core::Stream;
15use tokio::sync::Notify;
16
17mod builder;
18mod client;
19mod error;
20mod run_json;
21
22pub use builder::OpencodeClientBuilder;
23pub use client::OpencodeClient;
24pub use error::OpencodeError;
25pub use run_json::{
26    parse_run_json_lines, OpencodeRunCompletion, OpencodeRunJsonErrorCode, OpencodeRunJsonEvent,
27    OpencodeRunJsonLine, OpencodeRunJsonLineOutcome, OpencodeRunJsonParseError,
28    OpencodeRunJsonParser, OpencodeRunRequest,
29};
30
31pub type DynOpencodeRunJsonEventStream =
32    Pin<Box<dyn Stream<Item = Result<OpencodeRunJsonEvent, OpencodeRunJsonParseError>> + Send>>;
33
34pub type DynOpencodeRunJsonCompletion =
35    Pin<Box<dyn Future<Output = Result<OpencodeRunCompletion, OpencodeError>> + Send>>;
36
37#[derive(Clone)]
38pub struct OpencodeTerminationHandle {
39    inner: Arc<OpencodeTerminationInner>,
40}
41
42#[derive(Debug)]
43struct OpencodeTerminationInner {
44    requested: AtomicBool,
45    notify: Notify,
46}
47
48impl OpencodeTerminationHandle {
49    fn new() -> Self {
50        Self {
51            inner: Arc::new(OpencodeTerminationInner {
52                requested: AtomicBool::new(false),
53                notify: Notify::new(),
54            }),
55        }
56    }
57
58    pub fn request_termination(&self) {
59        if !self.inner.requested.swap(true, Ordering::SeqCst) {
60            self.inner.notify.notify_waiters();
61        }
62    }
63
64    fn is_requested(&self) -> bool {
65        self.inner.requested.load(Ordering::SeqCst)
66    }
67
68    async fn requested(&self) {
69        if self.is_requested() {
70            return;
71        }
72
73        let notified = self.inner.notify.notified();
74        if self.is_requested() {
75            return;
76        }
77
78        notified.await;
79    }
80}
81
82impl std::fmt::Debug for OpencodeTerminationHandle {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("OpencodeTerminationHandle")
85            .field("requested", &self.is_requested())
86            .finish()
87    }
88}
89
90pub struct OpencodeRunJsonHandle {
91    pub events: DynOpencodeRunJsonEventStream,
92    pub completion: DynOpencodeRunJsonCompletion,
93}
94
95impl std::fmt::Debug for OpencodeRunJsonHandle {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("OpencodeRunJsonHandle")
98            .field("events", &"<stream>")
99            .field("completion", &"<future>")
100            .finish()
101    }
102}
103
104pub struct OpencodeRunJsonControlHandle {
105    pub events: DynOpencodeRunJsonEventStream,
106    pub completion: DynOpencodeRunJsonCompletion,
107    pub termination: OpencodeTerminationHandle,
108}
109
110impl std::fmt::Debug for OpencodeRunJsonControlHandle {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("OpencodeRunJsonControlHandle")
113            .field("events", &"<stream>")
114            .field("completion", &"<future>")
115            .field("termination", &self.termination)
116            .finish()
117    }
118}